#!/usr/bin/env bash
# scripts/maas-region-power-key.sh <check|install> <site> <rack-virsh-uri> [--commit]
#
# Install and VERIFY the per-DC MAAS->libvirt power key inside a MAAS region's
# snap. Runs ON the region host (the snap's own machine). The private key is
# read from STDIN on `install`, so it is never written to an intermediate host
# and never appears in a process argument.
#
# check <site> <uri> READ-ONLY. Exit 0 only if the key is present
# with 0600, the ssh config block targets the
# right host with IdentitiesOnly, AND a REAL
# `virsh -c <uri> version` succeeds as the snap
# runs it (root).
# install <site> <uri> --commit Idempotent. Key on stdin. DRY without
# --commit.
#
# WHY THIS EXISTS (2026-07-30, D-132 q1 per-DC MAAS regions).
# `runbooks/dc-dc-phase2-tofu-dc-substrate.md` prerequisite 5 carries this as
# PROSE only -- and prose is not a gate. It has to be re-run at every DC standup
# and, per the SAME prerequisite's own FRAGILITY note, after EVERY MAAS snap
# refresh, because the key and config live under per-revision
# `/var/snap/maas/current/`. A per-DC secret with a documented re-assert
# requirement and no tool is the class this repo ruled against on 2026-07-30
# ("every per-DC secret needs a rotation tool, not just a generation recipe").
#
# SEC-012 (dc0) / SEC-016 (dc1): each DC gets its OWN power key, never a
# cross-DC reuse. This script does not mint -- it installs the key it is given
# and REFUSES to overwrite a DIFFERENT key already in place (that would be a
# silent cross-DC swap), so rotation is deliberate rather than accidental.
#
# ASSERT ON THE ARTIFACT, NEVER THE CONFIG: `check` ends on a real virsh call.
# A config file that names the right key proves nothing about whether libvirt
# accepts it -- the 2026-07-30 Octavia PKI work paid for this lesson twice.
#
# Exit: 0 all assertions pass | 1 assertion(s) failed | 2 could not evaluate.
# ASCII + LF.
set -uo pipefail
ACTION="${1:-}"; SITE="${2:-}"; URI="${3:-}"
COMMIT=0
if [ "$#" -gt 3 ]; then shift 3; else set --; fi
for a in "$@"; do
case "$a" in
--commit) COMMIT=1 ;;
*) echo "FAIL: unknown option '$a'" >&2; exit 2 ;;
esac
done
usage() {
echo "usage: maas-region-power-key.sh <check|install> <site> <rack-virsh-uri> [--commit]" >&2
echo " e.g. maas-region-power-key.sh check vr1-dc0 qemu+ssh://jessea123@10.12.8.2/system" >&2
echo " maas-region-power-key.sh install vr1-dc0 qemu+ssh://jessea123@10.12.8.2/system --commit < key" >&2
exit 2
}
[ -n "$ACTION" ] && [ -n "$SITE" ] && [ -n "$URI" ] || usage
case "$ACTION" in check|install) ;; *) usage ;; esac
# Site token shape: <region>-<dc>. DERIVE the label, never type it (the
# transposition class the octavia-pki work was hardened against).
case "$SITE" in
*-*) DCLABEL="${SITE#*-}" ;;
*) echo "FAIL: site '$SITE' is not <region>-<dc> (e.g. vr1-dc0)" >&2; exit 2 ;;
esac
[ -n "$DCLABEL" ] || { echo "FAIL: could not derive a DC label from site '$SITE'" >&2; exit 2; }
# Parse user@host out of qemu+ssh://user@host/system. Refuse anything else --
# a bare host would silently dial as root.
case "$URI" in
qemu+ssh://*@*/*) ;;
*) echo "FAIL: uri '$URI' is not qemu+ssh://<user>@<host>/<path>" >&2; exit 2 ;;
esac
_rest="${URI#qemu+ssh://}"
USERHOST="${_rest%%/*}"
SSHUSER="${USERHOST%@*}"
RACKHOST="${USERHOST#*@}"
[ -n "$SSHUSER" ] && [ -n "$RACKHOST" ] || { echo "FAIL: could not parse user/host from '$URI'" >&2; exit 2; }
# MAAS_SNAP_ROOT exists so the harness can exercise the derivation and parsing
# logic without a snap. In production it is never set and the default is the
# per-revision path the FRAGILITY note warns about.
SNAPBASE="${MAAS_SNAP_ROOT:-/var/snap/maas/current}"
SNAPROOT="$SNAPBASE/root/.ssh"
KEY="$SNAPROOT/id_${DCLABEL}_power"
CFG="$SNAPROOT/config"
# Echo the DERIVED values before any privileged call, so a transposition is
# visible in the capture even when a precondition later refuses.
echo "maas-region-power-key $ACTION: site=$SITE dc=$DCLABEL rack=$RACKHOST user=$SSHUSER"
echo " key=$KEY"
command -v sudo >/dev/null 2>&1 || { echo "REFUSE: no sudo on this host" >&2; exit 2; }
sudo -n true 2>/dev/null || { echo "REFUSE: passwordless sudo unavailable -- cannot read the snap's root-owned ssh dir" >&2; exit 2; }
[ -d "$SNAPBASE" ] || { echo "REFUSE: $SNAPBASE absent -- is the MAAS snap installed on this host?" >&2; exit 2; }
# ASSERT WITH THE BINARY MAAS ACTUALLY USES. The region host has no `virsh` of
# its own (measured on vr1-dc0-maas-01, 2026-07-30) -- the snap ships one, and
# that is what MAAS's virsh power driver drives, against the snap's own ssh
# config. Checking with a host-installed virsh would grade a different program
# reading a different config, which is precisely the "assert the artifact, never
# the config" failure this repo keeps paying for.
#
# A SNAP HAS TWO ROOTS AND THEY ARE NOT INTERCHANGEABLE -- measured the hard way
# on the first live run, which REFUSED with "no virsh binary found":
# /var/snap/<name>/current WRITABLE data (root/.ssh lives here)
# /snap/<name>/current READ-ONLY squashfs (the binaries live here)
# The first version of this script looked for virsh under the DATA root and
# found nothing, while `find /snap/maas/current -name virsh` had already located
# it. SNAPEXEC is derived from SNAPBASE rather than typed, so the harness
# override still works and the two cannot drift apart.
# AND THE SNAP'S BINARY MUST BE RUN INSIDE THE SNAP'S RUNTIME. Invoking
# $SNAPEXEC/usr/bin/virsh directly fails -- measured on the second live run:
# "error while loading shared libraries: libvirt-lxc.so.0"
# because the snap's libraries are not on the host's loader path. `snap run
# --shell <snap> -c 'virsh ...'` executes it with the snap's environment AND its
# ssh config, which is exactly the context MAAS's own power driver uses.
SNAPEXEC="${MAAS_SNAP_EXEC:-${SNAPBASE#/var}}"
_sb="${SNAPBASE%/current}"; SNAPNAME="${_sb##*/}"
VIRSH_MODE=""
if [ -n "${VIRSH_BIN:-}" ]; then
VIRSH_MODE="direct" # harness override
elif command -v snap >/dev/null 2>&1 && [ -x "$SNAPEXEC/usr/bin/virsh" ]; then
VIRSH_MODE="snap"
elif command -v virsh >/dev/null 2>&1; then
VIRSH_MODE="host"; VIRSH_BIN="$(command -v virsh)"
fi
# virsh_run <args...> -- dispatch to whichever mode was resolved.
virsh_run() {
case "$VIRSH_MODE" in
direct) sudo -n "$VIRSH_BIN" "$@" ;;
host) sudo -n "$VIRSH_BIN" "$@" ;;
snap) sudo -n snap run --shell "$SNAPNAME" -c "virsh $*" ;;
*) return 127 ;;
esac
}
virsh_label() {
case "$VIRSH_MODE" in
snap) echo "snap run --shell $SNAPNAME -c virsh" ;;
*) echo "$VIRSH_BIN" ;;
esac
}
# DERIVE the public half FROM the private key, then fingerprint that.
# `ssh-keygen -lf <privkey>` reads an ADJACENT <privkey>.pub when one exists and
# reports ITS fingerprint -- so a stale sibling .pub makes the comparison answer
# about the wrong key entirely. Found by this script's own harness, 2026-07-30,
# where a leftover .pub let a freshly-installed key read back as a different one.
# `-y` reads the private key and cannot be fooled by a neighbouring file.
fp_of_privkey() { # $1 = path (root-owned); prints the SHA256 fingerprint only
sudo -n ssh-keygen -y -f "$1" 2>/dev/null | ssh-keygen -lf - 2>/dev/null | awk '{print $2}'
}
fp_of_local_privkey() { # $1 = path readable by this user
ssh-keygen -y -f "$1" 2>/dev/null | ssh-keygen -lf - 2>/dev/null | awk '{print $2}'
}
# ---------------------------------------------------------------- check ----
do_check() {
local pass=0 fail=0
ck() { if [ "$1" = 0 ]; then echo " [ok] $2"; pass=$((pass+1)); else echo " [FAIL] $2"; fail=$((fail+1)); fi; }
sudo -n test -f "$KEY"; ck $? "power key present"
if ! sudo -n test -f "$KEY"; then
echo "REFUSE: no key to evaluate -- 'could not look' is not 'nothing wrong'" >&2
echo "maas-region-power-key: $pass passed, $fail failed"; return 2
fi
local mode; mode="$(sudo -n stat -c %a "$KEY" 2>/dev/null)"
[ "$mode" = "600" ]; ck $? "key mode is 0600 (got ${mode:-unknown})"
local owner; owner="$(sudo -n stat -c %U "$KEY" 2>/dev/null)"
[ "$owner" = "root" ]; ck $? "key owned by root (got ${owner:-unknown})"
local fp; fp="$(fp_of_privkey "$KEY")"
[ -n "$fp" ]; ck $? "key parses as a valid PRIVATE key${fp:+ ($fp)}"
sudo -n test -f "$CFG"; ck $? "ssh config present"
# Assert the config actually BINDS this rack host to this key. A config that
# merely mentions the key proves nothing -- match the Host stanza.
local blk
blk="$(sudo -n awk -v h="$RACKHOST" '
$1=="Host" { inblk = 0; for (i=2;i<=NF;i++) if ($i==h) inblk=1 }
inblk { print }
' "$CFG" 2>/dev/null)"
printf '%s' "$blk" | grep -q "IdentityFile[[:space:]]\+$KEY"; ck $? "config binds Host $RACKHOST -> $KEY"
printf '%s' "$blk" | grep -qi "IdentitiesOnly[[:space:]]\+yes"; ck $? "config sets IdentitiesOnly yes for $RACKHOST"
# THE ARTIFACT ASSERTION. Everything above is configuration; only this proves
# the region can actually drive power on that rack.
if [ -z "$VIRSH_MODE" ]; then
echo "REFUSE: no usable virsh found (looked for the $SNAPNAME snap's own, and PATH)" >&2
echo " the config assertions above cannot substitute for the artifact test" >&2
echo "maas-region-power-key: $pass passed, $fail failed"; return 2
fi
local vout vrc
vout="$(virsh_run -c "$URI" version 2>&1)"; vrc=$?
[ "$vrc" -eq 0 ]; ck $? "REAL virsh ($(virsh_label)) connect to $URI succeeds"
if [ "$vrc" -ne 0 ]; then
echo " virsh said: $(printf '%s' "$vout" | tr '\n' ' ' | cut -c1-200)"
fi
# And that it can enumerate domains -- a connect that lists nothing would let a
# power op no-op silently.
local n
n="$(virsh_run -c "$URI" list --all --name 2>/dev/null | sed '/^$/d' | wc -l)"
[ "${n:-0}" -gt 0 ]; ck $? "virsh enumerates domains on the rack (got ${n:-0})"
echo "maas-region-power-key: $pass passed, $fail failed"
[ "$fail" -eq 0 ] || return 1
return 0
}
# -------------------------------------------------------------- install ----
do_install() {
if [ -t 0 ]; then
echo "FAIL: install reads the private key from STDIN -- redirect one in" >&2
exit 2
fi
local tmp; tmp="$(mktemp)"; chmod 600 "$tmp"
# shellcheck disable=SC2064
trap "shred -u '$tmp' 2>/dev/null || rm -f '$tmp'" EXIT
cat > "$tmp"
[ -s "$tmp" ] || { echo "FAIL: empty key on stdin" >&2; exit 2; }
local newfp; newfp="$(fp_of_local_privkey "$tmp")"
[ -n "$newfp" ] || { echo "FAIL: stdin is not a readable private key" >&2; exit 2; }
# REFUSE a silent cross-DC swap: if a DIFFERENT key already sits here, stop.
if sudo -n test -f "$KEY"; then
local curfp; curfp="$(fp_of_privkey "$KEY")"
if [ "$curfp" = "$newfp" ]; then
echo "[idempotent] $KEY already holds this key ($newfp)"
else
echo "REFUSE: $KEY already holds a DIFFERENT key" >&2
echo " present: ${curfp:-unparseable}" >&2
echo " offered: $newfp" >&2
echo " Overwriting would silently re-point this region's power path." >&2
echo " Move the existing key aside deliberately if rotation is intended." >&2
exit 1
fi
fi
if [ "$COMMIT" -eq 0 ]; then
echo "DRY RUN (no --commit). Would:"
echo " install -d -m 0700 -o root -g root $SNAPROOT"
echo " install -m 0600 -o root -g root <stdin-key> $KEY # fp $newfp"
echo " append Host $RACKHOST block to $CFG -> IdentityFile $KEY"
return 0
fi
sudo -n install -d -m 0700 -o root -g root "$SNAPROOT" || { echo "FAIL: could not create $SNAPROOT" >&2; exit 1; }
sudo -n install -m 0600 -o root -g root "$tmp" "$KEY" || { echo "FAIL: could not install $KEY" >&2; exit 1; }
sudo -n touch "$CFG" && sudo -n chmod 0644 "$CFG" && sudo -n chown root:root "$CFG"
# Idempotent: only append if this Host has no stanza yet.
if sudo -n awk -v h="$RACKHOST" '$1=="Host"{for(i=2;i<=NF;i++) if($i==h) found=1} END{exit !found}' "$CFG" 2>/dev/null; then
echo "[idempotent] $CFG already has a Host $RACKHOST stanza -- left as-is"
else
printf '\nHost %s\n IdentityFile %s\n IdentitiesOnly yes\n StrictHostKeyChecking accept-new\n' \
"$RACKHOST" "$KEY" | sudo -n tee -a "$CFG" >/dev/null
echo "appended Host $RACKHOST stanza to $CFG"
fi
echo "installed. verifying:"
do_check
return $?
}
case "$ACTION" in
check) do_check; exit $? ;;
install) do_install; exit $? ;;
esac