13 KiB
SSH hardening transaction implementation plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make boot --server refuse unsafe SSH hardening and atomically roll back failed validation or reloads.
Architecture: Keep production logic in the standalone boot file because it runs before the repository exists. Exercise the public boot --server command through a PTY, a temporary filesystem root, and PATH command adapters; never call private functions directly or touch host SSH state.
Tech Stack: Bash 5, Python 3 standard library for the PTY driver, OpenSSH/systemd command adapters, Panama contract runner.
Spec: docs/superpowers/specs/2026-08-27-secure-bootstrap-privileged-installation-design.md
Global constraints
- Preserve the public
boot --servercommand and the non-root desktop bootstrap path. - Do not reload a real SSH service, edit
/etc/ssh, create a real account, or run a live privileged check. - A missing or unsafe target key keeps root/password access unchanged and continues bootstrap.
- Candidate validation or reload failure restores the prior drop-in and stops before clone/install handoff.
- Exact safe state is target ownership plus
.sshmode0700andauthorized_keysmode0600; symlinks, root UID, relative homes, blank/comment-only keys, and foreign ownership are refused. PANAMA_BOOT_FIXTURE_ROOTis accepted only by a real non-root process whose stubbedid -ureports root. Real root plus that variable must fail closed.- The new public contract is hermetic and must run under
panama test --safe. - Preserve the user's unstaged
/home/gib/.local/share/Panama/config/bash/.bashrcchange outside this worktree.
File map
boot: account/key preconditions, test-only filesystem adapter, SSH unit detection, atomic drop-in transaction, rollback.tests/setup/root-server-bootstrap-contract: PTY and stateful command/filesystem fixture for the real public command.tests/contracts.manifest: onehermeticentry for the new executable contract.tests/setup/boot-contract: retain the non-root clone/handoff behavior; adjust only if the later verified-bootstrap plan changes it.tests/setup/role-contract: retain role/stage assertions.README.md: describe key-gated transactional hardening without claiming a live reload was tested.
Task 1: Gate hardening on a verified login path
Files:
- Create:
tests/setup/root-server-bootstrap-contract - Modify:
boot:52-115 - Modify:
tests/contracts.manifest - Test:
tests/setup/root-server-bootstrap-contract
Interfaces:
-
Consumes: public
boot --server,PANAMA_BOOT_FIXTURE_ROOT, PTY answersgib, password already set, andYfor hardening. -
Produces:
system_path ABSOLUTE_PATH,safe_authorized_keys USER HOME, andharden_server_ssh USER HOME; later tasks extend the last function with the transaction. -
Step 1: Build the public fixture and write the failing precondition table
Create a checked temporary tree with root/etc/ssh/sshd_config.d, root/root/.ssh, root/home/gib/.ssh, bin, calls, and a stub cloned installer. Use Python's pty.openpty() to start:
env = {
**os.environ,
"PATH": f"{stub_dir}:/usr/bin:/bin",
"PANAMA_BOOT_FIXTURE_ROOT": fixture_root,
"PANAMA_PATH": f"{fixture_root}/home/gib/.local/share/Panama",
"HOME": f"{fixture_root}/root",
}
process = subprocess.Popen(
["bash", boot, "--server"],
stdin=slave,
stdout=slave,
stderr=slave,
env=env,
start_new_session=True,
)
os.write(master, b"gib\nY\n")
PATH stubs must log one shell-escaped argv vector per line. id -u with no username reports 0; id -u root reports 0; id -u gib reports 1000; id -nG gib prints gib wheel; passwd -S gib prints gib PS; getent passwd gib prints the logical absolute home /home/gib; runuser materializes the clone/install handoff without changing users. boot resolves that logical home beneath the fixture root through system_path. Stub stat for fixture ownership metadata plus dnf, git, sshd, and systemctl; any unexpected command exits 97.
Run one table row per unsafe state:
missing
empty
comment-only
ssh-directory-symlink
authorized-keys-symlink
directory-wrong-mode
file-wrong-mode
directory-wrong-owner
file-wrong-owner
root-target-account
relative-home
For every row assert status 0, a diagnostic containing SSH hardening unavailable, no sshd -t, no systemctl reload, no drop-in change, and a recorded install handoff. Add safe existing-key and safe root-key-copy rows that currently reach the unsafe direct-write path and therefore fail the new expected command ordering.
- Step 2: Run the contract and confirm the red behavior
Run:
bash -n tests/setup/root-server-bootstrap-contract
tests/setup/root-server-bootstrap-contract
Expected: nonzero findings showing current boot writes/reloads without the required key checks and has no fixture-root support.
- Step 3: Add the guarded filesystem adapter and key checks
Add these shapes near the root branch, using stat -Lc only after rejecting symlinks:
BOOT_ROOT="${PANAMA_BOOT_FIXTURE_ROOT:-}"
if [[ -n "$BOOT_ROOT" && "$EUID" -eq 0 ]]; then
echo "boot: PANAMA_BOOT_FIXTURE_ROOT is test-only" >&2
exit 1
fi
system_path() {
local path="$1"
[[ "$path" == /* ]] || return 2
printf '%s%s\n' "$BOOT_ROOT" "$path"
}
safe_authorized_keys() {
local username="$1" user_home="$2" uid ssh_dir keys
uid="$(id -u "$username")" || return 1
[[ "$uid" =~ ^[0-9]+$ && "$uid" != 0 && "$user_home" == /* ]] || return 1
ssh_dir="$user_home/.ssh"
keys="$ssh_dir/authorized_keys"
[[ -d "$ssh_dir" && ! -L "$ssh_dir" && -f "$keys" && ! -L "$keys" ]] || return 1
[[ "$(stat -Lc '%u:%a' "$ssh_dir")" == "$uid:700" ]] || return 1
[[ "$(stat -Lc '%u:%a' "$keys")" == "$uid:600" ]] || return 1
grep -qEv '^[[:space:]]*(#|$)' "$keys"
}
Resolve the target home, /root/.ssh/authorized_keys, and /etc/ssh/sshd_config.d through system_path. A copyable root key must be a non-symlinked regular file owned by UID 0, mode 0600, with at least one nonblank/non-comment line. Never overwrite an existing authorized_keys. When it is absent, either create a missing .ssh or require an existing .ssh to already be a real directory owned by the target UID with mode 0700; then copy only the key and apply ownership/mode to those two paths. Never use chown -R. If safe_authorized_keys still fails, print the unavailable message and skip the prompt/transaction.
- Step 4: Add the hermetic manifest entry and run focused checks
Insert the sorted manifest line with a directly preceding comment:
# Root bootstrap runs entirely against a temporary filesystem and PATH adapters.
hermetic tests/setup/root-server-bootstrap-contract
Run:
bash -n boot tests/setup/root-server-bootstrap-contract
tests/setup/contract-manifest-contract
tests/setup/root-server-bootstrap-contract
tests/setup/boot-contract
tests/setup/role-contract
./bin/panama test --safe root-server-bootstrap
git diff --check
Expected: all pass; the public fixture proves unsafe keys do not invoke sshd or reload while bootstrap still hands off.
- Step 5: Commit the precondition gate
git add boot tests/setup/root-server-bootstrap-contract tests/contracts.manifest
git commit -m "Fix: Gate SSH hardening on a verified key"
Task 2: Make the drop-in transaction recoverable
Files:
- Modify:
boot:42-137 - Modify:
tests/setup/root-server-bootstrap-contract - Test:
tests/setup/root-server-bootstrap-contract
Interfaces:
-
Consumes:
safe_authorized_keys,system_path, the fixture'ssshd/systemctlstate files, and a detected service name. -
Produces:
detect_ssh_unit,restore_ssh_dropin, and a completeharden_server_ssh USER HOMEtransaction returning 0 only after validation and reload. -
Step 1: Add failing success and rollback scenarios
Extend the command state with SSHD_RESULTS and RELOAD_RESULTS, consumed one result per call. Add exact cases:
success-without-prior-dropin: validate=0 reload=0
success-replaces-prior-dropin: validate=0 reload=0
candidate-invalid: validate=1,0 reload=<none>
candidate-reload-fails: validate=0,0 reload=1,0
rollback-validation-fails: validate=0,1 reload=1
rollback-reload-fails: validate=0,0 reload=1,1
Assert the desired two-line content, validation before reload, only the detected unit, byte-for-byte restoration, restored validation/reload ordering, nonzero status and no install handoff on every transactional failure, no *.tmp/*.backup residue on success, and retained backup plus recovery commands when rollback fails.
- Step 2: Run the focused contract and confirm it fails on current code
Run:
tests/setup/root-server-bootstrap-contract
Expected: nonzero because current code writes the final path directly, never validates, guesses units through reload failure, and reports success after failed reload.
- Step 3: Implement unit detection, atomic install, and rollback
Use explicit transaction state and traps. The core shape is:
detect_ssh_unit() {
local unit
for unit in sshd.service ssh.service; do
systemctl cat "$unit" >/dev/null 2>&1 && { printf '%s\n' "$unit"; return 0; }
done
return 1
}
restore_ssh_dropin() {
if [[ -n "${ssh_backup:-}" && -e "$ssh_backup" ]]; then
local restore
restore="$(mktemp --tmpdir="$sshd_dir" .90-panama.XXXXXX.restore)" || return 1
cp -a -- "$ssh_backup" "$restore"
mv -f -- "$restore" "$ssh_dropin"
else
rm -f -- "$ssh_dropin"
fi
}
Create the candidate with umask 077; mktemp --tmpdir="$sshd_dir" .90-panama.XXXXXX.tmp, write exact desired content, and preserve an existing final file in a collision-safe same-directory mktemp name ending .backup, not .conf. Save any prior EXIT, INT, and TERM traps, then arm transaction traps before mv -f activates the candidate. The EXIT handler restores only while ssh_transaction_active=1; every success or handled failure path restores the prior traps before returning.
After activation:
if ! sshd -t; then
restore_ssh_dropin
sshd -t || rollback_failed=1
return 1
fi
if ! systemctl reload "$ssh_unit"; then
restore_ssh_dropin
sshd -t || rollback_failed=1
systemctl reload "$ssh_unit" || rollback_failed=1
return 1
fi
On clean success set ssh_transaction_active=0, clear the local traps, and remove the backup. On rollback failure keep the backup and print its absolute path plus sshd -t and systemctl reload UNIT recovery commands. Do not continue to clone/install after a hardening transaction returns nonzero.
- Step 4: Verify failure status, cleanup, and old public behavior
Run:
bash -n boot tests/setup/root-server-bootstrap-contract
tests/setup/root-server-bootstrap-contract
tests/setup/boot-contract
tests/setup/role-contract
./bin/panama test --safe root-server-bootstrap
git diff --check
Expected: all pass; every rollback scenario preserves the previous drop-in and the safe success path validates before one reload.
- Step 5: Commit the transaction
git add boot tests/setup/root-server-bootstrap-contract
git commit -m "Fix: Roll back failed SSH hardening"
Task 3: Synchronize operator documentation and close the SSH plan
Files:
- Modify:
README.md:40-48 - Modify:
.claude/skills/panama/SKILL.md - Modify:
skills/panama-desktop/SKILL.mdonly if it describes root bootstrap - Test:
tests/setup/readme-contract
Interfaces:
-
Consumes: the landed
boot --serverbehavior from Tasks 1-2. -
Produces: accurate user-facing preconditions, skip behavior, rollback behavior, and no claim of live-host proof.
-
Step 1: Write the documentation assertions first
Extend tests/setup/readme-contract to require nearby root-bootstrap prose containing all of: verified target key, sshd -t, atomic drop-in, reload rollback, and hardening unavailable without a key. Reject wording that says Panama merely writes the file or that reload failure is ignored.
- Step 2: Run the README contract and confirm the old prose fails
Run:
tests/setup/readme-contract
Expected: nonzero until README describes the transactional behavior.
- Step 3: Update the documentation without claiming a live reload
State plainly that Panama copies or verifies the target key, offers hardening only with exact safe ownership/modes, validates the complete config, reloads the detected unit, and restores the previous drop-in on failure. State that fixture contracts test these paths and no real daemon reload runs under panama test --safe.
- Step 4: Run the plan gate
bash -n boot tests/setup/root-server-bootstrap-contract
tests/setup/root-server-bootstrap-contract
tests/setup/boot-contract
tests/setup/role-contract
tests/setup/readme-contract
./bin/panama test --safe
git diff --check
Expected: 132 hermetic contracts pass after adding the new contract; non-hermetic skip counts remain unchanged.
- Step 5: Commit the synchronized documentation
git add README.md .claude/skills/panama/SKILL.md skills/panama-desktop/SKILL.md tests/setup/readme-contract
git commit -m "Docs: Explain transactional SSH hardening"