diff --git a/.claude/skills/panama/SKILL.md b/.claude/skills/panama/SKILL.md index 904f09d..0288896 100644 --- a/.claude/skills/panama/SKILL.md +++ b/.claude/skills/panama/SKILL.md @@ -92,4 +92,17 @@ it against the real system. ## Root work -Never bare `sudo` — load the `panama-sudo` skill. Migrations already follow the rule. +Never bare `sudo`. Load the `panama-sudo` skill first. Migrations already follow the rule. + +`boot --server` is the one exception where a new Fedora VPS may begin as root. +Before it offers SSH hardening, it copies a safe root key when possible or +verifies the target key. The target user's `.ssh` must be owned by that user at +`0700`, and `authorized_keys` must be owned by that user at `0600`. Without a +verified target key, SSH hardening is unavailable and the bootstrap continues. + +Accepted hardening uses an atomic same-directory `sshd_config.d` drop-in, +runs `sshd -t`, then reloads the detected SSH unit. Validation or reload +failure restores the previous drop-in before it retries validation and reload; +failed recovery stops the handoff with manual recovery instructions. The +fixture contracts exercise those branches. `panama test --safe` never reloads +a live daemon, so it is not live-host proof. diff --git a/README.md b/README.md index af73cee..d7b2544 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,22 @@ question entirely: bash <(curl -fsSL https://git.gbrown.org/gib/Panama/raw/branch/main/boot) --server ``` -That command also works from a brand-new VPS's **root** login: it creates -your user with sudo, moves the SSH key over, offers to harden sshd, and hands -off to a normal install as that user. +That command also works from a brand-new VPS's **root** login. It creates or +reuses your sudo-enabled user, then copies a safe root key when it can or +verifies the target key before offering SSH hardening. A verified target key +means the target user owns `.ssh` with mode `0700` and `authorized_keys` with +mode `0600`. SSH hardening is unavailable without a verified target key, and +the install continues without it. + +When you accept hardening, Panama makes an atomic same-directory drop-in, +validates the complete SSH configuration with `sshd -t`, then reloads the +detected SSH unit. If validation or reload fails, it restores the previous +drop-in and validates and reloads that restored configuration; recovery that +cannot complete stops the handoff and prints the manual recovery command. The +fixture contracts exercise these branches. No real daemon reload runs under +`panama test --safe`, so that suite is not live-host proof. + +After that, it hands off to a normal install as the new user. `install` asks its questions first and then runs the stages in `setup/scripts/` in order, without stopping again: @@ -198,7 +211,7 @@ docs/ Settings reference, and the design specs behind the work ## Tests -186 of them, under `tests/`. `tests/contracts.manifest` classifies every +187 of them, under `tests/`. `tests/contracts.manifest` classifies every contract by the capabilities it needs. Run the hermetic set, or grant a specific external capability when automation needs it: diff --git a/tests/setup/readme-contract b/tests/setup/readme-contract index cb31538..9630956 100755 --- a/tests/setup/readme-contract +++ b/tests/setup/readme-contract @@ -36,6 +36,42 @@ elif (( claimed != actual )); then note "the README says $claimed contracts; there are $actual" fi +# ── Root server bootstrap ─────────────────────────────────────────────────── +# +# This path runs before the repository exists on a fresh VPS. Its safety +# properties need to be stated beside the public `boot --server` example, not +# inferred from the shell implementation or buried in a fixture. + +bootstrap_doc="$(sed -n '/^That command also works from a brand-new VPS/,/^`install` asks/p' "$readme" | tr '\n' ' ')" + +require_bootstrap_doc() { + local pattern="$1" explanation="$2" + grep -qiE "$pattern" <<<"$bootstrap_doc" || note "$explanation" +} + +require_bootstrap_doc 'verified target key' \ + 'the root bootstrap docs do not require a verified target key' +require_bootstrap_doc '700.*600|600.*700' \ + 'the root bootstrap docs do not state the exact SSH ownership and modes' +require_bootstrap_doc 'hardening (is )?unavailable.*(without|until).*key' \ + 'the root bootstrap docs do not say hardening is unavailable without a key' +require_bootstrap_doc 'sshd -t' \ + 'the root bootstrap docs do not name sshd -t validation' +require_bootstrap_doc 'atomic.*same.directory|same.directory.*atomic' \ + 'the root bootstrap docs do not describe the atomic same-directory drop-in' +require_bootstrap_doc 'detected (SSH )?unit.*reload|reload.*detected (SSH )?unit' \ + 'the root bootstrap docs do not describe reloading the detected SSH unit' +require_bootstrap_doc 'restores? (the )?previous drop-in.*(validation|reload)|(validation|reload).*restores? (the )?previous drop-in' \ + 'the root bootstrap docs do not promise rollback on validation or reload failure' +require_bootstrap_doc 'fixture contracts.*(these|this) (path|branch)|fixture contracts.*test' \ + 'the root bootstrap docs do not limit proof to fixture contracts' +require_bootstrap_doc 'no real daemon reload.*panama test --safe|panama test --safe.*no real daemon reload' \ + 'the root bootstrap docs imply a live daemon reload under the safe suite' + +if grep -qiE 'merely writes? (the )?(SSH )?(drop-in|file)|reload failure.*ignored|ignores? .*reload failure' <<<"$bootstrap_doc"; then + note 'the root bootstrap docs weaken the transaction by treating the write or reload failure as harmless' +fi + # ── Documented subcommands exist ───────────────────────────────────────────── # # A README listing a command the dispatcher does not have sends somebody to a