From e9928fa3d92f106f83086eacbe6add7861a30217 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 28 Oct 2026 00:46:02 +0100 Subject: [PATCH] NK-WP-0055-T13: king-path drill passed; post-event review record Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 4515@bnt-lap001 Assistant-Session: 0bf96127-6e04-41be-bfda-d11717c3f354 --- docs/crisis-custody-list.md | 2 +- docs/evidence/2026-10-28-king-path-drill.md | 49 +++++++++++++++++++ ...-security-platform-production-readiness.md | 2 + 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 docs/evidence/2026-10-28-king-path-drill.md diff --git a/docs/crisis-custody-list.md b/docs/crisis-custody-list.md index b417ec0..4b59ba6 100644 --- a/docs/crisis-custody-list.md +++ b/docs/crisis-custody-list.md @@ -32,7 +32,7 @@ circular dependency and is recorded as a gap, not as custody. | 1 | **`railiance-backup` age private key** (recipient `age1zvry…50syr3tsz`). Not the custodian key (item 1b) | Decrypts the identity backup bundle and the break-glass copy on Storage Share (`net-kingdom-kingpath-*.tar.age`) | Founder, password safe (stored as `railiance-backup.key`) | Yes, offline | Identity stages 2026-10-24; king-path verify drill 2026-10-27 (the helper refused the custodian key three times before the correct key was entered, as designed) | `TO CONFIRM`: number of copies, who else could recover if the founder cannot | | 1b | **Custodian age private key** (recipient `age1aq8t…qdepsh4`, `keys/age.pub` and `.sops.yaml`). Not the backup key | Decrypts the repository's SOPS custody files and greenfield bootstrap bundles (`docs/security-bootstrap-age-custody.md`) | Founder, password safe (stored as the custodian age private key) | Yes, offline | `TO CONFIRM` last proven use | Labels in the safe should name the recipient prefix so the two keys cannot be confused | | 2 | Storage Share `Backup` account app password | Downloads the protected ciphertext without OpenBao or SSO | Founder, independent custody `TO CONFIRM` | Yes, provider is outside the platform | Fresh retrieval 2026-10-24 | One provider copy only; residual risk accepted until 2027-01-31 (G1, NK-WP-0053) | -| 3 | OpenBao unseal shares (2 of 3 required in the restore drill) | Unseals a restored or production OpenBao; with the shares, `generate-root` mints a root token for break-glass (ceremony in `railiance-platform/docs/openbao.md`; production loopback listener 8210 live since 2026-10-28) | Founder as `platform-root` holds all three shares (single-holder custody: accepted exception, hub decision `37464824`, 2026-10-27), in the founder's replicated browser password safe under the `kingpath.credentials` pseudo-URL entry (confirmed 2026-10-27) | Yes: the safe is independent of NetKingdom SSO and OpenBao; it depends on the browser vendor's sync account and its own factor | Original shares used in the isolated restore RPF-WP-0064-T07, 2026-10-24 | Single holder = single point of loss (reviewed at NK-WP-0055-T09). The founder holds the initial root token offline (2026-10-27); its validity is checked in the king-path drill (NK-WP-0055-T13) against an isolated restored OpenBao, never production | +| 3 | OpenBao unseal shares (2 of 3 required in the restore drill) | Unseals a restored or production OpenBao; with the shares, `generate-root` mints a root token for break-glass (ceremony in `railiance-platform/docs/openbao.md`; production loopback listener 8210 live since 2026-10-28) | Founder as `platform-root` holds all three shares (single-holder custody: accepted exception, hub decision `37464824`, 2026-10-27), in the founder's replicated browser password safe under the `kingpath.credentials` pseudo-URL entry (confirmed 2026-10-27) | Yes: the safe is independent of NetKingdom SSO and OpenBao; it depends on the browser vendor's sync account and its own factor | Original shares used in the isolated restore RPF-WP-0064-T07, 2026-10-24 | Single holder = single point of loss (reviewed at NK-WP-0055-T09). The founder holds the initial root token offline; the king-path drill (2026-10-28) found it **valid** as of the 2026-10-14 snapshot (root policy, no TTL, orphan). Revoke on production or keep escrowed: founder decision pending | | 4 | `platform-root` / king account and factors | Final authority; break-glass | Independently held password safe or offline packet (Standard §4; custody doc) | **No if used through KeyCape**: its login and OTP are validated by the services being recovered | OTP validated against restored privacyIDEA 2026-10-24 (isolated) | `TO CONFIRM` the offline path that works with SSO down (OpenBao shares, item 3) and record it | | 5 | Founder admission signing key (SSH ed25519, principal `bernd`, fingerprint `SHA256:NekDABfw8K8IBYznjREg7jPpcK/BzCgO1pH1ebT/FXM`) | Signs restore and drill admissions for the OpenBao isolated restore (namespace `railiance-openbao-restore-attempt`) and secrets-engine admissions | Private key: founder workstation `~/.ssh/gexec_approver_ed25519` (passphrase-protected; signed the T40, `-02` and `-03` admissions); founder decision 2026-10-28: also keep it in the password safe under the kingpath entry, **not** in the Storage Share bundle, so the backup age key alone cannot admit a restore. Public anchor (non-secret, needed on any recovery workstation): `/etc/railiance-openbao-restore/allowed_signers` and `/etc/secrets-engine/admission/allowed_signers`, one line each: `bernd namespaces="railiance-openbao-restore-attempt" ssh-ed25519 ssh-ed25519` ; the secrets-engine file uses namespace `secrets-engine-recovery-attempt` | Yes, offline | Signed the T40 admission 2026-10-23 | Store the private key in the safe; re-create both anchor files from this row on a recovery workstation. The file name says `gexec_approver`, but the key's role is admission signing | | 6 | `admin` password and OTP token | Day-to-day platform administration and, per intent, recovery operation | Founder; OTP enrolled 2026-10-24 | **No**: login and token checks need KeyCape, LLDAP and privacyIDEA | Token not yet in any restored backup; first restore proof after the 2026-10-25 03:45 UTC capture | Same account as the directory bind used by Authelia and privacyIDEA until the split in NK-WP-0054-T05 | diff --git a/docs/evidence/2026-10-28-king-path-drill.md b/docs/evidence/2026-10-28-king-path-drill.md new file mode 100644 index 0000000..e1da85b --- /dev/null +++ b/docs/evidence/2026-10-28-king-path-drill.md @@ -0,0 +1,49 @@ +# King-path drill — post-event review record (NK-WP-0055-T13) + +Value-free record. No token, share, key or OTP appears here or in any linked receipt. + +| | | +|---|---| +| When | 2026-10-27/28 (UTC), founder workstation | +| Who | Founder (operator and authorizer); claude-code prepared commands and verified receipts | +| Why | Drill: prove that the final authority can act with SSO, KeyCape and OpenBao logins unavailable | +| Production touched | Only by the separate, earlier rollout of the loopback `generate-root` listener (RAPP-OPENBAO-WP-0003-T02). The drill itself ran on an isolated, networkless restore. | + +## Part 1: break-glass material from Storage Share + +`kingpath-offsite.py --verify` fetched `net-kingdom-kingpath-20261027T223050Z-8c9d48421f1b.tar.age` +from the Storage Share `Backup` account. ETag, size and digest matched the upload receipt. An unrelated +key was refused, and the content decrypted in memory only. `admin-kubeconfig` (kubeconfig shape) and +`tunnel-ssh-key` (SSH private key header) were intact. The custody helper refused the custodian age key +until the `railiance-backup` key was entered, as designed. Crisis list item 1 was then split so the two +keys cannot be confused again. + +## Part 2: OpenBao break-glass on an isolated restore (RPF-WP-0064-T10) + +- **Run:** attempt `RPF-WP-0064-T10-20261027-01`, admission `sha256:741fa571…ce7a`, adapter `3727f8b`, + snapshot `openbao-raft-snapshot-rpf-wp-0015-t03-reboot-20261014-01-20261014t181053z.snap.age` + (plaintext `a6312444…590b`). +- **Result:** `drilled` (`break_glass_drill_complete`) in 101 s. The receipt + `sha256:e4591346e99b23e405f1cb69f6f5158dbdb7b5d69b2d1d7709c563a74cc18397` stays outside Git and was + independently verified (`PASS`) from a clean clone. +- **Held initial root token:** valid as of the snapshot. It carries `root` policy, no TTL, no expiry, + and is an orphan token. +- **Fresh `generate-root`:** the shares were re-entered and the OTP decode worked. The generated token + carried `root`, the read-only `sys/auth` action succeeded, `revoke-self` returned 204, and the + follow-up lookup returned 403. +- **Cancel:** a started attempt was cancelled cleanly. +- **Teardown:** the container was removed and verified absent, no volumes were created, the memfds were + closed, and the readback credential was revoked. + +## Findings and follow-ups + +1. **A standing, non-expiring root token very likely exists on production.** It was valid at the + 2026-10-14 snapshot and has not been revoked since. The architecture asks that the initial root token + be revoked, or escrowed as break-glass material. The `generate-root` path now works on production and + is drilled, so it can replace that token. Founder decision needed: revoke it on production, or keep it + escrowed and record why. +2. **Production read-only SSO-down walk:** still to run (RPF-WP-0064-T10 runbook). +3. **Admission signing key:** it is `~/.ssh/gexec_approver_ed25519`. It needs a copy in the password safe + under the kingpath entry (crisis list item 5). +4. **Not drilled:** using the restored kubeconfig and SSH key against the cluster. Provider console + access is not drilled either. diff --git a/workplans/NK-WP-0055-security-platform-production-readiness.md b/workplans/NK-WP-0055-security-platform-production-readiness.md index cb4679b..579dd52 100644 --- a/workplans/NK-WP-0055-security-platform-production-readiness.md +++ b/workplans/NK-WP-0055-security-platform-production-readiness.md @@ -326,6 +326,8 @@ root token against an isolated restored OpenBao (never production); use produces 2026-10-28: founder rolled out the loopback `generate-root` listener (RAPP-OPENBAO-WP-0003-T02): 200 on 8210 without a token, 405 on the main listener 8200. The break-glass ceremony can now run on production with SSO down. railiance-platform built and rehearsed the drill mode for part 2 (RPF-WP-0064-T10, `a2da408`: 34 offline tests, native rehearsal 16/16); its production read-only walk must be updated to expect 200 on 8210. 2026-10-28: crisis list item 5 now records the admission signing key's fingerprint and both public anchor lines, so a recovery workstation can re-create them; founder decision: the private key goes to the password safe kingpath entry, not the Storage Share bundle. Its file on the workstation is still being located. + +2026-10-28 king-path drill part 2 (founder-run): RPF-WP-0064-T10 attempt `-20261027-01` `drilled` in 101 s, receipt `e4591346…8397` independently verified (PASS). Held initial root token valid as of the 2026-10-14 snapshot; fresh `generate-root` with re-entered shares, read-only action, revoke 204, lookup 403, cancel drilled, full teardown. Post-event record: [2026-10-28 king-path drill](../docs/evidence/2026-10-28-king-path-drill.md). Open: founder decision on the production root token, the read-only production SSO-down walk, the signing key copy in the safe. ## Policy import pipeline and platform/tenant package split ```task