<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet href="/static/style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://vulnerability.circl.lu/sightings/feed</id>
  <title>Most recent sightings.</title>
  <updated>2026-09-03T20:03:16.963044+00:00</updated>
  <author>
    <name>Vulnerability-Lookup</name>
    <email>info@circl.lu</email>
  </author>
  <link href="https://vulnerability.circl.lu" rel="alternate"/>
  <generator uri="https://lkiesow.github.io/python-feedgen" version="1.0.0">python-feedgen</generator>
  <subtitle>Contains only the most 10 recent sightings.</subtitle>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/4da5acc7-99da-4520-b65c-9587688da2f6/export</id>
    <title>4da5acc7-99da-4520-b65c-9587688da2f6</title>
    <updated>2026-09-03T20:03:16.987039+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "4da5acc7-99da-4520-b65c-9587688da2f6", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2025-30156", "type": "seen", "source": "https://gist.github.com/tchaikov/124b3520bfc90821db2712f076fb1e14", "content": "#!/bin/bash\n#\n# pve-cephx-rotate -- migrate a Proxmox VE Ceph cluster off the insecure CephX\n# \"aes\" cipher onto \"aes256k\" after upgrading to Ceph 20.2.4 (or 19.2.6).\n#\n#   CVE-2025-30156: CephX used AES-128-CBC with a fixed IV and no message\n#   authentication, letting anyone with a low-privilege key forge admin\n#   credentials. 20.2.4 adds the aes256k key type but does NOT re-key anything,\n#   so the monitors raise health warnings until every key is rotated.\n#\n# Upstream procedure:\n#   https://docs.ceph.com/en/latest/rados/configuration/auth-config-ref/#cephx-upgrade\n#\n# Run it as root on EVERY node that runs a Ceph daemon, one node at a time.\n# It is idempotent and resumable: each run does everything that is safe from\n# this node and tells you what is left. On a cluster that is already fully\n# migrated it reports that and exits without touching anything, so it is safe\n# to re-run or to leave in a cron job. Exit codes:\n#\n#   0  this node is done and the cluster is fully migrated\n#   2  this node is done, but other nodes still have work (run it there)\n#   1  something went wrong and the migration stopped\n#\n# Options:\n#   --yes            do not prompt before making changes\n#   --skip-admin     never rotate client.admin\n#   --force-admin    rotate client.admin even if old kernel clients are at risk\n#   --no-lockdown    stop before restricting the cluster to aes256k only\n#   --status         report state and exit without changing anything\n#\nset -euo pipefail\n\nCIPHER=aes256k\nCLUSTER=${CLUSTER:-ceph}\nHOST=$(hostname -s)\nBACKUP=/root/cephx-rotate-$(date +%Y%m%d-%H%M%S)\nCEPH_TIMEOUT=${CEPH_TIMEOUT:-20}\nASSUME_YES=0 SKIP_ADMIN=0 FORCE_ADMIN=0 DO_LOCKDOWN=1 STATUS_ONLY=0\nREMOTE_WORK=0\n\n# Minimum daemon versions carrying the fix.\nMIN_TENTACLE=20.2.4\nMIN_SQUID=19.2.6\n# First mainline kernel whose Ceph client speaks aes256k.\nMIN_KERNEL=7.0\n\nceph() { command ceph --connect-timeout \"$CEPH_TIMEOUT\" \"$@\"; }\n\nRED=''; GRN=''; YLW=''; BLD=''; RST=''\nif [[ -t 1 ]]; then RED=$'\\e[31m'; GRN=$'\\e[32m'; YLW=$'\\e[33m'; BLD=$'\\e[1m'; RST=$'\\e[0m'; fi\nsay()  { echo \"${BLD}==&amp;gt;${RST} $*\"; }\ninfo() { echo \"    $*\"; }\nok()   { echo \"    ${GRN}ok${RST}   $*\"; }\nwarn() { echo \"    ${YLW}warn${RST} $*\"; }\ndie()  {\n  echo; echo \"${RED}FAILED:${RST} $*\" &amp;gt;&amp;amp;2\n  [[ -d $BACKUP ]] &amp;amp;&amp;amp; echo \"Backups of everything touched so far: $BACKUP\" &amp;gt;&amp;amp;2\n  exit 1\n}\n\nconfirm() {\n  [[ $ASSUME_YES -eq 1 ]] &amp;amp;&amp;amp; return 0\n  local a; read -r -p \"    $1 [y/N] \" a /dev/null; }\nhas_check()   { health_json | jq -e --arg c \"$1\" '(.checks // {}) | has($c)' &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; }\n\ninsecure_entities() {\n  health_json | jq -r --arg c \"$1\" '(.checks[$c].detail // [])[]?.message' 2&amp;gt;/dev/null \\\n    | sed -nE 's/^entity ([^ ]+) using insecure key type.*/\\1/p'\n}\n\nBLOCKING='^(PG_AVAILABILITY|PG_UNAVAILABLE|PG_DEGRADED|PG_DAMAGED|OSD_DOWN|OSD_HOST_DOWN|MON_DOWN|MDS_ALL_DOWN|SLOW_OPS|OSD_FLAGS_NOUP)$'\nblocking_now() {\n  health_json | jq -r --arg b \"$BLOCKING\" '(.checks // {}) | keys[] | select(test($b))' 2&amp;gt;/dev/null | tr '\\n' ' '\n}\n\n# Wait for DATA availability. The AUTH_INSECURE_* checks are ERR-level and are\n# what this migration clears, so they must never count as \"unsettled\" -- doing\n# so would make the script wait for a condition only it can satisfy.\nWAIT_TRIES=${WAIT_TRIES:-120}\nwait_available() {\n  local tries=${1:-$WAIT_TRIES} bad\n  for ((i=0; i&amp;amp;2\n  die \"$u did not come back up after rotating its key\"\n}\n\nsave_file() {\n  local f=$1; [[ -f $f ]] || return 0\n  mkdir -m 0700 -p \"$BACKUP\"\n  cp -a \"$f\" \"$BACKUP/$(echo \"${f#/}\" | tr / _).bak\"\n}\n\n# /etc/pve is pmxcfs (FUSE): ownership is fixed at root:www-data and chown fails.\nwrite_file() {\n  local src=$1 dest=$2 mode=${3:-600}\n  save_file \"$dest\"\n  if [[ $dest == /etc/pve/* ]]; then\n    cat \"$src\" &amp;gt; \"$dest\" || die \"could not write $dest\"\n  else\n    install -o ceph -g ceph -m \"$mode\" -D \"$src\" \"$dest\" || die \"could not write $dest\"\n  fi\n  info \"wrote $dest\"\n}\n\n# A cephx key is base64 and always ends in '=' padding, so splitting the line\n# on '=' truncates it. Strip only the \"key =\" prefix.\nkey_in_file() {\n  sed -n 's/^[[:space:]]*key[[:space:]]*=[[:space:]]*//p' \"$1\" 2&amp;gt;/dev/null \\\n    | head -1 | sed 's/[[:space:]]*$//'\n}\n\n# preflight -- every one of these is a hard gate\npreflight_basic() {\n  say \"Checking this node\"\n\n  [[ $(id -u) -eq 0 ]] || die \"must run as root\"\n\n  local missing=()\n  for t in ceph ceph-authtool ceph-volume ceph-bluestore-tool jq systemctl python3; do\n    command -v \"$t\" &amp;gt;/dev/null || missing+=(\"$t\")\n  done\n  [[ ${#missing[@]} -eq 0 ]] || die \"missing required tools: ${missing[*]}\"\n  ok \"required tools present\"\n\n  [[ -d /etc/pve ]] || die \"/etc/pve not found -- this does not look like a Proxmox VE node\"\n  [[ -f /etc/pve/ceph.conf ]] || die \"/etc/pve/ceph.conf not found -- no pveceph-managed cluster here\"\n  ok \"Proxmox VE node: $(pveversion 2&amp;gt;/dev/null | head -1)\"\n\n  ceph -s &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || die \"cannot talk to the cluster ('ceph -s' failed). Fix that first.\"\n  ok \"cluster reachable\"\n}\n\n# Gates that must pass before anything is modified. Split from the checks above\n# so that --status, and an already-migrated cluster, never write anything.\npreflight_gates() {\n  say \"Preflight\"\n\n  # Every daemon must understand aes256k, or rotating its key strands it.\n  local bad_versions=() v\n  while IFS= read -r v; do\n    [[ -z $v ]] &amp;amp;&amp;amp; continue\n    is_fixed_version \"$v\" || bad_versions+=(\"$v\")\n  done &amp;lt; &amp;lt;(ceph versions --format=json 2&amp;gt;/dev/null \\\n           | jq -r 'del(.overall) | to_entries | map(.value | keys) | add // [] | .[]' \\\n           | sed -nE 's/^ceph version ([0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/p' | sort -u)\n  if [[ ${#bad_versions[@]} -gt 0 ]]; then\n    ceph versions\n    die \"daemons still run Ceph ${bad_versions[*]}, which cannot use $CIPHER.\n     Upgrade every daemon to &amp;gt;= $MIN_TENTACLE (or &amp;gt;= $MIN_SQUID on Squid) and\n     restart them before rotating any key.\"\n  fi\n  ok \"all daemons run a fixed Ceph version\"\n\n  # An out-of-quorum monitor never learns the rotated mon. key.\n  local in_q total\n  in_q=$(ceph quorum_status --format=json 2&amp;gt;/dev/null | jq -r '.quorum_names | length')\n  total=$(ceph mon dump --format=json 2&amp;gt;/dev/null | jq -r '.mons | length')\n  [[ -n $in_q &amp;amp;&amp;amp; -n $total &amp;amp;&amp;amp; $in_q -eq $total ]] \\\n    || die \"only $in_q of $total monitors are in quorum; every monitor must be in quorum first\"\n  ok \"$in_q/$total monitors in quorum\"\n\n  ceph mon dump --format=json 2&amp;gt;/dev/null | jq -e \\\n    '.features.persistent // [] | index(\"cephx_auth_aes256k\")' &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 \\\n    || warn \"monmap does not advertise cephx_auth_aes256k yet; it is set on first use\"\n\n  local bad; bad=$(blocking_now)\n  [[ -z ${bad// /} ]] || die \"cluster is not healthy enough to start: $bad\"\n  ok \"no data availability problems\"\n\n  mkdir -m 0700 -p \"$BACKUP\"\n  ceph auth export -o \"$BACKUP/auth-export-all.keyring\" 2&amp;gt;/dev/null \\\n    || die \"could not export the auth database for backup\"\n  chmod 600 \"$BACKUP/auth-export-all.keyring\"\n  ok \"auth database backed up to $BACKUP/auth-export-all.keyring\"\n}\n\n# True when this node and the cluster are fully on the new cipher, so a run\n# would have nothing to change.\n# Reasons a run would still have something to do. Prints one per line; no\n# output means this node and the cluster are finished.\n#\n# AUTH_INSECURE_ROTATING_SERVICE_KEY_TYPE is deliberately excluded: nothing can\n# clear it, it lapses on its own once the existing rotating keys age out, so\n# demanding action for it would make the script never report completion.\nmigration_reasons() {\n  local r=() allowed preferred service\n  for c in AUTH_INSECURE_KEYS_ALLOWED AUTH_INSECURE_KEYS_CREATABLE \\\n           AUTH_INSECURE_SERVICE_KEY_TYPE AUTH_INSECURE_SERVICE_TICKETS \\\n           AUTH_INSECURE_CLIENT_KEY_TYPE AUTH_EMERGENCY_CIPHERS_SET; do\n    has_check \"$c\" &amp;amp;&amp;amp; r+=(\"health check $c is still raised\")\n  done\n  allowed=$(ceph --format=json mon dump | jq -r '(.auth_allowed_ciphers // []) | map(.name) | join(\",\")')\n  preferred=$(ceph --format=json mon dump | jq -r '(.auth_preferred_cipher.name) // \"\"')\n  service=$(ceph --format=json mon dump | jq -r '(.auth_service_cipher.name) // \"\"')\n  [[ $allowed   == \"$CIPHER\" ]] || r+=(\"auth_allowed_ciphers is '$allowed', not '$CIPHER'\")\n  [[ $preferred == \"$CIPHER\" ]] || r+=(\"auth_preferred_cipher is '$preferred', not '$CIPHER'\")\n  [[ $service   == \"$CIPHER\" ]] || r+=(\"auth_service_cipher is '$service', not '$CIPHER'\")\n  if [[ -d /var/lib/ceph/mon/$CLUSTER-$HOST ]]; then\n    local db file\n    db=$(ceph auth get-key 'mon.' 2&amp;gt;/dev/null || true)\n    file=$(key_in_file \"/var/lib/ceph/mon/$CLUSTER-$HOST/keyring\" || true)\n    if [[ -z $db ]]; then\n      r+=(\"cannot read the mon. key from the auth database\")\n    elif [[ $db != \"$file\" ]]; then\n      r+=(\"this monitor's keyring does not match the auth database\")\n    fi\n  fi\n  printf '%s\\n' ${r[@]+\"${r[@]}\"}\n}\n\n# client compatibility -- rotating a key an old kernel cannot read breaks it\nKERNEL_OK=0\nKRBD_USERS=\"\"\nkernel_check() {\n  local kv; kv=$(uname -r | sed -E 's/^([0-9]+\\.[0-9]+).*/\\1/')\n  if ver_ge \"$kv\" \"$MIN_KERNEL\"; then KERNEL_OK=1; fi\n\n  # Which storages authenticate through the *kernel* client as client.admin?\n  KRBD_USERS=$(python3 - &amp;lt;&amp;lt;'PY' || true\nimport re\ncfg = \"/etc/pve/storage.cfg\"\ntry:\n    text = open(cfg).read()\nexcept OSError:\n    raise SystemExit\ncur = None\nsec = {}\norder = []\nfor line in text.splitlines():\n    m = re.match(r'^(\\w+):\\s*(\\S+)', line)\n    if m:\n        cur = {\"type\": m.group(1), \"id\": m.group(2)}\n        sec[m.group(2)] = cur\n        order.append(m.group(2))\n    elif cur is not None and line.strip():\n        parts = line.strip().split(None, 1)\n        cur[parts[0]] = parts[1] if len(parts) &amp;gt; 1 else \"1\"\nout = []\nfor sid in order:\n    s = sec[sid]\n    # A storage with an explicit username uses its own key, not client.admin.\n    if s.get(\"username\", \"admin\") != \"admin\":\n        continue\n    # External clusters are not ours to migrate.\n    if \"monhost\" in s:\n        continue\n    if s[\"type\"] == \"rbd\" and s.get(\"krbd\", \"0\") not in (\"0\", \"\"):\n        out.append(f\"{sid} (rbd, krbd)\")\n    if s[\"type\"] == \"cephfs\" and s.get(\"fuse\", \"0\") in (\"0\", \"\"):\n        out.append(f\"{sid} (cephfs, kernel mount)\")\nprint(\"\\n\".join(out))\nPY\n)\n}\n\nsay \"pve-cephx-rotate on $HOST\"\npreflight_basic\nkernel_check\n\nreport_state() {\n  say \"Cluster state\"\n  ceph --format=json mon dump | jq -r '\n    \"    allowed ciphers   : \" + ((.auth_allowed_ciphers // []) | map(.name) | join(\",\")),\n    \"    preferred cipher  : \" + ((.auth_preferred_cipher.name) // \"?\"),\n    \"    service cipher    : \" + ((.auth_service_cipher.name) // \"?\")'\n  local svc cli\n  svc=$(insecure_entities AUTH_INSECURE_SERVICE_KEY_TYPE | tr '\\n' ' ')\n  cli=$(insecure_entities AUTH_INSECURE_CLIENT_KEY_TYPE  | tr '\\n' ' ')\n  info \"insecure service keys: ${svc:-none}\"\n  info \"insecure client keys : ${cli:-none}\"\n  info \"kernel $(uname -r) -- $([[ $KERNEL_OK -eq 1 ]] &amp;amp;&amp;amp; echo \"supports $CIPHER\" || echo \"too old for $CIPHER (needs &amp;gt;= $MIN_KERNEL)\")\"\n  if [[ -n $KRBD_USERS ]]; then\n    info \"storages using the kernel client as client.admin:\"\n    while IFS= read -r l; do [[ -n $l ]] &amp;amp;&amp;amp; info \"  - $l\"; done &amp;lt;&amp;lt;&amp;lt;\"$KRBD_USERS\"\n  else\n    info \"no storages authenticate through the kernel client as client.admin\"\n  fi\n}\nreport_state\n\nreasons=$(migration_reasons)\nif [[ -z $reasons ]]; then\n  echo\n  echo \"${GRN}Already migrated:${RST} every key and cipher is $CIPHER, and this\"\n  echo \"node's monitor keyring matches. Nothing to do.\"\n  if has_check AUTH_INSECURE_ROTATING_SERVICE_KEY_TYPE; then\n    echo\n    info \"AUTH_INSECURE_ROTATING_SERVICE_KEY_TYPE is still raised. Nothing can\"\n    info \"clear it: the existing rotating service keys simply have to age out,\"\n    info \"within auth_service_ticket_ttl ($(ceph config get mon auth_service_ticket_ttl 2&amp;gt;/dev/null || echo 3600)s).\"\n  fi\n  exit 0\nfi\n\nsay \"Work remaining on this node\"\nwhile IFS= read -r l; do [[ -n $l ]] &amp;amp;&amp;amp; info \"- $l\"; done &amp;lt;&amp;lt;&amp;lt;\"$reasons\"\n[[ $STATUS_ONLY -eq 1 ]] &amp;amp;&amp;amp; exit 0\n\npreflight_gates\n\nif ! confirm \"Proceed with the migration on $HOST?\"; then\n  echo \"    aborted by user\"; exit 0\nfi\n\n# Cluster-wide and idempotent: safe to repeat on every node.\nsay \"1. Enabling $CIPHER\"\nallowed=$(ceph --format=json mon dump | jq -r '(.auth_allowed_ciphers // []) | map(.name) | join(\",\")')\nif [[ $allowed != *\"$CIPHER\"* ]]; then\n  ceph mon set auth_allowed_ciphers \"aes,$CIPHER\" || die \"could not allow $CIPHER\"\n  allowed=$(ceph --format=json mon dump | jq -r '.auth_allowed_ciphers | map(.name) | join(\",\")')\n  [[ $allowed == *\"$CIPHER\"* ]] || die \"auth_allowed_ciphers is still '$allowed' after setting it\"\nfi\nok \"auth_allowed_ciphers = $allowed\"\n\npref=$(ceph --format=json mon dump | jq -r '.auth_preferred_cipher.name')\nif [[ $pref != \"$CIPHER\" ]]; then\n  ceph mon set auth_preferred_cipher \"$CIPHER\" || die \"could not set the preferred cipher\"\n  pref=$(ceph --format=json mon dump | jq -r '.auth_preferred_cipher.name')\n  [[ $pref == \"$CIPHER\" ]] || die \"auth_preferred_cipher is still '$pref'\"\nfi\nok \"auth_preferred_cipher = $pref\"\n\n# 2. rotate the keys of daemons that live on THIS node\nosd_block_device() {\n  ceph-volume lvm list \"$1\" --format json 2&amp;gt;/dev/null \\\n    | jq -r --arg i \"$1\" '.[$i][]? | select(.type==\"block\") | (.lv_path // .path)' | head -1\n}\n\nrotate_osd() {\n  local id=$1 dev kr tmp bare back\n  kr=\"/var/lib/ceph/osd/$CLUSTER-$id/keyring\"\n  dev=$(osd_block_device \"$id\" || true)\n  [[ -n $dev ]] || die \"cannot find the block device for osd.$id; refusing to rotate it\"\n  info \"osd.$id on $dev\"\n\n  systemctl stop \"ceph-osd@$id\" || die \"could not stop ceph-osd@$id\"\n  ceph osd down \"$id\" &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true\n\n  tmp=$(mktemp)\n  ceph auth rotate --key-type=\"$CIPHER\" \"osd.$id\" &amp;gt; \"$tmp\" \\\n    || die \"rotating osd.$id failed\"\n  cp \"$tmp\" \"$BACKUP/osd.$id.keyring\"; chmod 600 \"$BACKUP/osd.$id.keyring\"\n  write_file \"$tmp\" \"$kr\"\n\n  # The OSD data dir is tmpfs. ceph-volume re-primes this keyring from the\n  # bluestore label on every activation, so the label is the authoritative\n  # copy -- skipping it means the OSD reverts to its old key on next boot.\n  bare=$(ceph auth get-key \"osd.$id\") || die \"could not read back the new osd.$id key\"\n  ceph-bluestore-tool --dev \"$dev\" set-label-key --key osd_key --value \"$bare\" &amp;gt;/dev/null \\\n    || die \"could not write osd_key into the bluestore label of $dev\"\n  back=$(ceph-bluestore-tool --dev \"$dev\" show-label 2&amp;gt;/dev/null \\\n         | jq -r --arg d \"$dev\" '(.[$d].osd_key // .devices[$d].osd_key // empty)')\n  [[ $back == \"$bare\" ]] \\\n    || die \"bluestore label on $dev still reads '$back'; osd.$id would revert on reboot\"\n  ok \"bluestore label updated and verified\"\n\n  rm -f \"$tmp\"\n  restart_unit \"ceph-osd@$id\"\n  wait_available\n}\n\nrotate_plain_daemon() {  # mgr / mds\n  local type=$1 id=$2 tmp\n  info \"$type.$id\"\n  systemctl stop \"ceph-$type@$id\" || die \"could not stop ceph-$type@$id\"\n  tmp=$(mktemp)\n  ceph auth rotate --key-type=\"$CIPHER\" \"$type.$id\" &amp;gt; \"$tmp\" || die \"rotating $type.$id failed\"\n  cp \"$tmp\" \"$BACKUP/$type.$id.keyring\"; chmod 600 \"$BACKUP/$type.$id.keyring\"\n  write_file \"$tmp\" \"/var/lib/ceph/$type/$CLUSTER-$id/keyring\"\n  rm -f \"$tmp\"\n  restart_unit \"ceph-$type@$id\"\n}\n\nsay \"2. Rotating daemon keys on $HOST\"\nceph osd set noout &amp;gt;/dev/null || die \"could not set noout\"\ntrap 'echo; ceph osd unset noout &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true' EXIT\n\n# mon. is one key shared by every monitor. Rotate it once, then make sure THIS\n# monitor's on-disk keyring matches the auth database and restart it if not.\n# Running the script on each monitor node converges the whole cluster.\nif [[ -d /var/lib/ceph/mon/$CLUSTER-$HOST ]]; then\n  if insecure_entities AUTH_INSECURE_SERVICE_KEY_TYPE | grep -qx 'mon\\.'; then\n    info \"rotating the shared mon. key\"\n    tmp=$(mktemp)\n    ceph auth rotate --key-type=\"$CIPHER\" 'mon.' &amp;gt; \"$tmp\" || die \"rotating mon. failed\"\n    cp \"$tmp\" \"$BACKUP/mon.keyring\"; chmod 600 \"$BACKUP/mon.keyring\"\n    # PVE keeps a cluster-wide copy in pmxcfs used when adding a monitor. It\n    # also holds client.admin, so merge instead of overwriting.\n    pve_mon=\"/etc/pve/priv/$CLUSTER.mon.keyring\"\n    if [[ -f $pve_mon ]]; then\n      save_file \"$pve_mon\"; merged=$(mktemp); cp \"$pve_mon\" \"$merged\"\n      ceph-authtool \"$merged\" --import-keyring \"$tmp\" &amp;gt;/dev/null \\\n        || die \"could not merge the new mon. key into $pve_mon\"\n      cat \"$merged\" &amp;gt; \"$pve_mon\"; rm -f \"$merged\"\n      ok \"merged new mon. key into $pve_mon\"\n    fi\n    rm -f \"$tmp\"\n  fi\n  # Self-healing: restart this monitor whenever its file disagrees with the db.\n  db_mon=$(ceph auth get-key 'mon.' 2&amp;gt;/dev/null || true)\n  file_mon=$(key_in_file \"/var/lib/ceph/mon/$CLUSTER-$HOST/keyring\" || true)\n  if [[ -n $db_mon &amp;amp;&amp;amp; $db_mon != \"$file_mon\" ]]; then\n    tmp=$(mktemp)\n    printf '[mon.]\\n\\tkey = %s\\n\\tcaps mon = \"allow *\"\\n' \"$db_mon\" &amp;gt; \"$tmp\"\n    write_file \"$tmp\" \"/var/lib/ceph/mon/$CLUSTER-$HOST/keyring\"\n    rm -f \"$tmp\"\n    restart_unit \"ceph-mon@$HOST\"\n    wait_available\n  else\n    ok \"mon. key already current on this node\"\n  fi\nfi\n\nfor id in $(insecure_entities AUTH_INSECURE_SERVICE_KEY_TYPE | sed -n 's/^mgr\\.//p'); do\n  [[ -d /var/lib/ceph/mgr/$CLUSTER-$id ]] || { REMOTE_WORK=1; continue; }\n  rotate_plain_daemon mgr \"$id\"\ndone\nfor id in $(insecure_entities AUTH_INSECURE_SERVICE_KEY_TYPE | sed -n 's/^mds\\.//p'); do\n  [[ -d /var/lib/ceph/mds/$CLUSTER-$id ]] || { REMOTE_WORK=1; continue; }\n  rotate_plain_daemon mds \"$id\"\ndone\nfor id in $(insecure_entities AUTH_INSECURE_SERVICE_KEY_TYPE | sed -n 's/^osd\\.//p' | sort -n); do\n  [[ -d /var/lib/ceph/osd/$CLUSTER-$id ]] || { REMOTE_WORK=1; continue; }\n  rotate_osd \"$id\"\ndone\n\nceph osd unset noout &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true\ntrap - EXIT\n\nremaining_svc=$(insecure_entities AUTH_INSECURE_SERVICE_KEY_TYPE | tr '\\n' ' ')\nif [[ -n ${remaining_svc// /} ]]; then\n  REMOTE_WORK=1\n  warn \"service keys still on the old cipher, on other nodes: $remaining_svc\"\nelse\n  ok \"every service key uses $CIPHER\"\nfi\n\n# Only safe once every service key in the cluster uses the new cipher.\nsay \"3. Service tickets\"\nif [[ -n ${remaining_svc// /} ]]; then\n  warn \"skipped: service keys elsewhere still use the old cipher\"\nelse\n  svc_cipher=$(ceph --format=json mon dump | jq -r '.auth_service_cipher.name')\n  if [[ $svc_cipher != \"$CIPHER\" ]]; then\n    ceph mon set auth_service_cipher \"$CIPHER\" || die \"could not set auth_service_cipher\"\n    svc_cipher=$(ceph --format=json mon dump | jq -r '.auth_service_cipher.name')\n    [[ $svc_cipher == \"$CIPHER\" ]] || die \"auth_service_cipher is still '$svc_cipher'\"\n  fi\n  ok \"auth_service_cipher = $svc_cipher\"\n  info \"the rotating-service-key warning clears by itself within\"\n  info \"auth_service_ticket_ttl ($(ceph config get mon auth_service_ticket_ttl 2&amp;gt;/dev/null || echo 3600)s)\"\nfi\n\n# Safe to rotate unattended: nothing outside the cluster uses these.\nsay \"4. Bootstrap and crash keys\"\nclient_dest() {\n  case $1 in\n    # The monitor auto-creates client.admin and six client.bootstrap-* keys at\n    # cluster creation, so match the whole family, not a hand-picked subset.\n    client.bootstrap-*) echo \"/var/lib/ceph/${1#client.}/$CLUSTER.keyring\" ;;\n    client.rgw.*)       local d=\"/var/lib/ceph/radosgw/$CLUSTER-rgw.${1#client.rgw.}\"\n                        [[ -d $d ]] &amp;amp;&amp;amp; echo \"$d/keyring\" || echo \"\" ;;\n    client.crash)       echo \"/etc/pve/ceph/$CLUSTER.client.crash.keyring\" ;;\n    client.admin)       echo \"/etc/pve/priv/$CLUSTER.client.admin.keyring\" ;;\n    *)                  echo \"\" ;;\n  esac\n}\n\nforeign_clients=()\nfor e in $(insecure_entities AUTH_INSECURE_CLIENT_KEY_TYPE); do\n  [[ $e == client.admin ]] &amp;amp;&amp;amp; continue\n  dest=$(client_dest \"$e\")\n  if [[ -z $dest ]]; then foreign_clients+=(\"$e\"); continue; fi\n  rgw_unit=\"\"\n  if [[ $e == client.rgw.* ]]; then\n    rgw_unit=\"ceph-radosgw@rgw.${e#client.rgw.}\"\n    systemctl stop \"$rgw_unit\" || die \"could not stop $rgw_unit\"\n  fi\n  tmp=$(mktemp)\n  ceph auth rotate --key-type=\"$CIPHER\" \"$e\" &amp;gt; \"$tmp\" || die \"rotating $e failed\"\n  if [[ -d $(dirname \"$dest\") ]]; then\n    write_file \"$tmp\" \"$dest\"\n  else\n    info \"$e rotated (no local keyring directory on this node)\"\n  fi\n  rm -f \"$tmp\"\n  [[ -n $rgw_unit ]] &amp;amp;&amp;amp; restart_unit \"$rgw_unit\"\n  ok \"$e\"\ndone\n[[ ${#foreign_clients[@]} -eq 0 ]] || warn \"not managed by Proxmox, left alone: ${foreign_clients[*]}\"\n\n# The one key that can lock you out, and the one external consumers use.\nsay \"5. Admin key\"\nadmin_insecure=0\nif insecure_entities AUTH_INSECURE_CLIENT_KEY_TYPE | grep -qx 'client\\.admin'; then\n  admin_insecure=1\nfi\n\nadmin_blocked_reason=\"\"\nif [[ $admin_insecure -eq 1 ]]; then\n  if [[ $SKIP_ADMIN -eq 1 ]]; then\n    admin_blocked_reason=\"--skip-admin was given\"\n  elif [[ -n $KRBD_USERS &amp;amp;&amp;amp; $KERNEL_OK -eq 0 &amp;amp;&amp;amp; $FORCE_ADMIN -eq 0 ]]; then\n    admin_blocked_reason=\"kernel $(uname -r) is older than $MIN_KERNEL and these storages authenticate as client.admin through the kernel client, which would stop working: $(echo \"$KRBD_USERS\" | tr '\\n' ' ')\"\n  fi\nfi\n\nupdate_admin_copies() {  # PVE copies the admin key into per-storage files\n  local old=$1 new_kr=$2 new_key=$3 hits=0 f cur\n  shopt -s nullglob\n  for f in /etc/pve/priv/ceph/*.keyring /etc/pve/priv/ceph/*.secret /etc/ceph/*.keyring; do\n    [[ -f $f ]] || continue\n    grep -qF \"$old\" \"$f\" 2&amp;gt;/dev/null || continue\n    save_file \"$f\"\n    if [[ $f == *.secret ]]; then\n      # CephFS storages hold the bare base64 secret, not a keyring.\n      printf '%s\\n' \"$new_key\" &amp;gt; \"$f\" || die \"could not update $f\"\n    else\n      cat \"$new_kr\" &amp;gt; \"$f\" || die \"could not update $f\"\n    fi\n    info \"updated $f\"\n    hits=1\n  done\n  shopt -u nullglob\n  [[ $hits -eq 1 ]] &amp;amp;&amp;amp; ok \"per-storage copies of the admin key refreshed\"\n  return 0\n}\n\nif [[ $admin_insecure -eq 0 ]]; then\n  ok \"client.admin already uses $CIPHER\"\nelif [[ -n $admin_blocked_reason ]]; then\n  warn \"not rotating client.admin: $admin_blocked_reason\"\n  info \"muting AUTH_INSECURE_CLIENT_KEY_TYPE for 8 weeks, as upstream suggests\"\n  ceph health mute AUTH_INSECURE_CLIENT_KEY_TYPE 8w &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true\n  info \"re-run with --force-admin once the kernel is &amp;gt;= $MIN_KERNEL on every node\"\n  info \"that mounts this storage, or switch those storages off krbd.\"\n  DO_LOCKDOWN=0\nelse\n  pve_kr=\"/etc/pve/priv/$CLUSTER.client.admin.keyring\"\n  sys_kr=\"/etc/ceph/$CLUSTER.client.admin.keyring\"\n  fb=\"$BACKUP/client.admin-backup.keyring\"\n  new=\"$BACKUP/client.admin.keyring\"\n  old_key=$(ceph auth get-key client.admin 2&amp;gt;/dev/null || true)\n  [[ -n $old_key ]] || die \"could not read the current client.admin key\"\n\n  info \"creating a temporary fallback credential\"\n  ceph auth get-or-create client.admin-backup mon 'allow *' &amp;gt; \"$fb\" \\\n    || die \"could not create the fallback credential\"\n  chmod 600 \"$fb\"\n  ceph -n client.admin-backup -k \"$fb\" auth ls &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || {\n    ceph auth rm client.admin-backup &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true\n    die \"the fallback credential does not work; client.admin was left alone\"\n  }\n  ok \"fallback verified\"\n\n  save_file \"$pve_kr\"; save_file \"$sys_kr\"\n  ceph auth rotate --key-type=\"$CIPHER\" client.admin &amp;gt; \"$new\" || {\n    ceph auth rm client.admin-backup &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true\n    die \"rotating client.admin failed; nothing was changed\"\n  }\n  chmod 600 \"$new\"\n  new_key=$(key_in_file \"$new\")\n  [[ -n $new_key ]] || die \"could not read the new admin key out of $new\"\n  cat \"$new\" &amp;gt; \"$pve_kr\" || die \"could not write $pve_kr\"\n  install -o ceph -g ceph -m 600 \"$new\" \"$sys_kr\" || die \"could not write $sys_kr\"\n  update_admin_copies \"$old_key\" \"$new\" \"$new_key\"\n\n  if ceph auth ls &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then\n    ok \"new client.admin key works\"\n    if ceph auth rm client.admin-backup &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then\n      info \"removed the fallback credential\"\n    else\n      warn \"could not remove client.admin-backup; delete it by hand once satisfied\"\n    fi\n  else\n    echo \"${RED}    the new admin key does not work -- rolling back${RST}\" &amp;gt;&amp;amp;2\n    for f in \"$pve_kr\" \"$sys_kr\"; do\n      b=\"$BACKUP/$(echo \"${f#/}\" | tr / _).bak\"\n      [[ -f $b ]] &amp;amp;&amp;amp; cat \"$b\" &amp;gt; \"$f\"\n    done\n    cat &amp;gt;&amp;amp;2 &amp;lt;&amp;lt;EOF\n    The auth database now holds the NEW key while the files hold the old one.\n    The fallback credential is still valid and was NOT removed. Recover with:\n      ceph -n client.admin-backup -k $fb auth get client.admin\n    then write that output to $pve_kr and $sys_kr, and re-run this script.\nEOF\n    exit 1\n  fi\nfi\n\n# Refuse to restrict the cluster while any old-cipher key still exists.\nsay \"6. Lockdown\"\nstill_service=$(insecure_entities AUTH_INSECURE_SERVICE_KEY_TYPE | tr '\\n' ' ')\nstill_client=$(insecure_entities AUTH_INSECURE_CLIENT_KEY_TYPE  | tr '\\n' ' ')\nif [[ $DO_LOCKDOWN -eq 0 ]]; then\n  warn \"skipped (not requested, or the admin key could not be rotated)\"\nelif [[ -n ${still_service// /} || -n ${still_client// /} ]]; then\n  warn \"skipped: keys still use the old cipher (${still_service}${still_client})\"\n  info \"restricting the cluster to $CIPHER now would lock those out\"\n  REMOTE_WORK=1\nelse\n  ceph config set mon mon_auth_allow_insecure_key false || die \"could not disable insecure key creation\"\n  ceph mon set auth_allowed_ciphers \"$CIPHER\" || die \"could not restrict auth_allowed_ciphers\"\n  now=$(ceph --format=json mon dump | jq -r '.auth_allowed_ciphers | map(.name) | join(\",\")')\n  [[ $now == \"$CIPHER\" ]] || die \"auth_allowed_ciphers is '$now', expected '$CIPHER'\"\n  ok \"auth_allowed_ciphers = $now, insecure key creation disabled\"\nfi\n\nsay \"Summary\"\nreport_state\necho\nceph -s\necho\ninfo \"backups and rotated keyrings: $BACKUP\"\nif [[ $REMOTE_WORK -eq 1 ]]; then\n  echo\n  echo \"${YLW}Work remains.${RST} Run this same script on the other Ceph nodes,\"\n  echo \"then run it once more here to finish the cluster-wide steps.\"\n  exit 2\nfi\nif has_check AUTH_INSECURE_CLIENT_KEY_TYPE || has_check AUTH_INSECURE_SERVICE_KEY_TYPE; then\n  echo\n  echo \"${YLW}Some keys still use the old cipher${RST} -- see above for why.\"\n  exit 2\nfi\necho\necho \"${GRN}This cluster no longer uses the insecure CephX cipher.${RST}\"\n", "creation_timestamp": "2026-09-03T07:09:46.720605Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/4da5acc7-99da-4520-b65c-9587688da2f6/export"/>
    <published>2026-09-03T07:09:46.720605+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/bd2608af-ab7f-4b1c-bd61-d5d699c99a5b/export</id>
    <title>bd2608af-ab7f-4b1c-bd61-d5d699c99a5b</title>
    <updated>2026-09-03T20:03:16.989908+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "bd2608af-ab7f-4b1c-bd61-d5d699c99a5b", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2025-30156", "type": "seen", "source": "https://infosec.exchange/users/vuldb/statuses/117157711464804130", "content": "The severity is increased for this new vulnerability affecting Linux Kernel (CVE-2025-30156) https://vuldb.com/vuln/395039", "creation_timestamp": "2026-08-25T19:06:47.259766Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/bd2608af-ab7f-4b1c-bd61-d5d699c99a5b/export"/>
    <published>2026-08-25T19:06:47.259766+00:00</published>
  </entry>
  <entry>
    <id>https://vulnerability.circl.lu/sighting/89740595-1290-4a16-a569-f68f0b302e13/export</id>
    <title>89740595-1290-4a16-a569-f68f0b302e13</title>
    <updated>2026-09-03T20:03:16.990014+00:00</updated>
    <author>
      <name>Automation user</name>
      <uri>https://cve.circl.lu/user/automation</uri>
    </author>
    <content>{"uuid": "89740595-1290-4a16-a569-f68f0b302e13", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2025-30156", "type": "seen", "source": "https://bsky.app/profile/o2cloud.bsky.social/post/3mtjh7ac24k26", "content": "\ud83d\udd17 CVE : CVE-2025-30156, CVE-2026-39944, CVE-2026-50152, CVE-2026-54330", "creation_timestamp": "2026-08-20T14:25:25.063862Z"}</content>
    <link href="https://vulnerability.circl.lu/sighting/89740595-1290-4a16-a569-f68f0b302e13/export"/>
    <published>2026-08-20T14:25:25.063862+00:00</published>
  </entry>
</feed>
