From 3894dc7c54fe3ec8d15b4adfa65281f46fdb0fa1 Mon Sep 17 00:00:00 2001 From: codex Date: Fri, 23 Oct 2026 20:15:24 +0200 Subject: [PATCH] RPF-WP-0064: propose isolated original-OpenBao restore for SECRETS-WP-0015-T04 Accepts hub request 41e9af3e as source preparation. T01 source survey recorded and done; restore and consumer continuity stay unclaimed. Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 4384@bnt-lap001 Assistant-Session: 80d1f3da-7a91-4519-9737-9ba128b4da64 --- WORK-RECORDS.md | 9 + ...-0064-isolated-original-openbao-restore.md | 262 ++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 workplans/RPF-WP-0064-isolated-original-openbao-restore.md diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index c7b1c5d..38d4cc0 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -43,6 +43,7 @@ | workplan | RPF-WP-0061 | active | — | workplans/RPF-WP-0061-self-service-ccr-intake.md | | workplan | RPF-WP-0062 | finished | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md | | workplan | RPF-WP-0063 | finished | — | workplans/RPF-WP-0063-wave2-resource-right-sizing.md | +| workplan | RPF-WP-0064 | proposed | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | | task | RPF-WP-ADHOC-2026-10-10-T01 | done | — | workplans/ADHOC-2026-10-10.md | | task | RPF-WP-ADHOC-2026-10-11-T01 | done | — | workplans/ADHOC-2026-10-11.md | | task | RPF-WP-ADHOC-2026-10-11-T02 | done | — | workplans/ADHOC-2026-10-11.md | @@ -192,6 +193,14 @@ | task | RPF-WP-0062-T02 | done | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md | | task | RPF-WP-0062-T03 | done | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md | | task | RPF-WP-0063-T01 | done | — | workplans/RPF-WP-0063-wave2-resource-right-sizing.md | +| task | RPF-WP-0064-T01 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | +| task | RPF-WP-0064-T02 | todo | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | +| task | RPF-WP-0064-T03 | todo | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | +| task | RPF-WP-0064-T04 | todo | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | +| task | RPF-WP-0064-T05 | wait | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | +| task | RPF-WP-0064-T06 | wait | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | +| task | RPF-WP-0064-T07 | wait | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | +| task | RPF-WP-0064-T08 | wait | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | | intake | RPF-IN-0001 | closed | — | intakes/intakes.md | | intake | RPF-IN-0002 | open | — | intakes/intakes.md | | intake | RPF-IN-0003 | open | — | intakes/intakes.md | diff --git a/workplans/RPF-WP-0064-isolated-original-openbao-restore.md b/workplans/RPF-WP-0064-isolated-original-openbao-restore.md new file mode 100644 index 0000000..5a11bee --- /dev/null +++ b/workplans/RPF-WP-0064-isolated-original-openbao-restore.md @@ -0,0 +1,262 @@ +--- +id: RPF-WP-0064 +type: workplan +title: "Isolated original-OpenBao restore for secrets-engine consumer continuity" +domain: financials +repo: railiance-platform +status: proposed +flavor: implementation +owner: claude +created: "2026-10-23" +updated: "2026-10-23" +related: + - SECRETS-WP-0015 + - NK-WP-0049 + - RPF-WP-0015 + - RPF-WP-0036 + - RPF-WP-0035 + - RAPP-OPENBAO-WP-0001 +--- + +# RPF-WP-0064 — Isolated original-OpenBao restore for secrets-engine consumer continuity + +Receiving workplan for hub message `41e9af3e` from net-kingdom (case +`secrets15-nk49-readiness`). The founder accepted on 2026-10-23 that +railiance-platform owns this as source preparation. + +NK-WP-0049-T18 (net-kingdom `a09bf20`, verified receipt sha256 `16d72f7c…1651`, +secrets-engine `bfa1b3b`) proved in isolation that the original KeyCape issuer, its +JWKS key and the archived `secrets-engine-openbao` registration are intact. +SECRETS-WP-0015-T04 also needs the original OpenBao, recovered on its own into an +isolated, network-contained target. That recovery uses the original custody and +unseal path. It must show the KV registration +`platform/workloads/keycape/secrets-engine-openbao`, the narrow secrets-engine +JWT role and the operator auth path. There is no production redirect, no fresh +backend standing in for the original and no injected secret. + +Scope of this plan: the restore and its evidence only. The secrets-engine +consumer run (attended in-memory secret source, KeyCape exchange, OpenBao JWT +login, `openbao:admin` refusal, session revocation) is a separate +founder-admitted attempt owned by secrets-engine. This plan prepares its target. + +Why not RPF-WP-0036-T03: that task owns *recurring* backup/restore assurance and +waits on the activity-core scheduler (ACTIVITY-WP-0042). This is a one-off +attended restore with no scheduler, so a separate plan avoids taking on that +dependency. Its evidence can later feed T03 as one isolated restore receipt. +RPF-WP-0015 keeps the live snapshot experiments. Its snapshot receipts authorize +no restore. + +Nothing in this plan claims restore capability or consumer continuity. Every +live step stays `wait` until attended evidence exists. + +## T01 — Source survey and recorded findings + +```task +id: RPF-WP-0064-T01 +status: done +priority: high +``` + +### Findings (2026-10-23, read-only source survey; no cluster, OpenBao or snapshot access) + +1. **Snapshot capture and location.** The procedure is `docs/openbao.md` §Backup: + `bao operator raft snapshot save` on `openbao-0`, then age encryption before the + snapshot leaves the operator machine. The latest recorded capture is the + attended RPF-WP-0015-T03 hold point on 2026-10-14. That snapshot was encrypted + to the offsite recipient derived from the founder's offline + `railiance-backup.key`, decrypt-verified in memory and uploaded to + `railiance-backups/openbao/` (HTTP 201), and the plaintext was shredded from + tmpfs. An earlier 2026-08-23 drill receipt is at + `reviews/WARDEN-WP-0027-T02-DRILL-20260822-01-openbao-snapshot-receipt.json`. + Receipts are validated against `docs/openbao-reboot-snapshot-receipt.example.json` + (24 h freshness, cluster id, applied index). Those receipts authorize no + restore. +2. **Freshness against the needed objects.** The KV path was corrected to + `platform/workloads/keycape/secrets-engine-openbao` on 2026-10-06, and the JWT + role was proven live on 2026-10-10 (CCR-2026-0038). The 2026-10-14 snapshot + should therefore contain both. This is an inference until the restored + metadata readback confirms it. If they are missing, T07 stops; it does not + repair anything. +3. **Version pin.** Chart `openbao/openbao` `0.28.2` (`Makefile` + `OPENBAO_CHART_VERSION`); image `quay.io/openbao/openbao:2.5.4` + (`helm/openbao-values.yaml`; `docs/openbao.md` still says app `v2.5.3`, which + is stale doc text). The restore target must use the same image, pinned by + digest at rehearsal time. Storage is integrated Raft. +4. **Unseal and custody model in use.** Shamir 2-of-3, unsealed by the founder in + attendance (the 2026-10-14 post-reboot unseal was done outside the transcript). + The transit auto-unseal stanza is still commented out. A Raft snapshot restore + brings back the *original* barrier keyring, so the restored target unseals only + with the original shares. That is the "original unseal path" the request asks + for. Restoring needs a throwaway initialization of the empty target first. Its + throwaway keys and token stay in process memory, are never recorded, and stop + working once `snapshot restore -force` completes. This is not a "fresh backend" + in the request's sense, because no state from it survives. +5. **Role, KV and auth definitions are owned here, not in rapp-openbao.** The + sources are `openbao/auth/secrets-engine-jwt-role.json`, + `openbao/auth/keycape-services-config.json` (mount `keycape-services`), + `docs/contracts/secrets-engine-openbao-jwt-login.yaml`, the operator path + `openbao/auth/netkingdom-oidc-config.json` plus + `netkingdom-platform-admin-role.json` (mount `netkingdom`, role + `platform-admin`), and the workload KV lane (`docs/workload-kv-access-lanes.md`, + KeyCape provisioning window 2026-10-04). rapp-openbao's SCOPE excludes policy, + custody and workload KV lanes. It owns only the package (chart, values, UI + overlay), and neither of its workplans has a restore task. rapp-openbao only + needs to confirm the image/chart pin used by the target. +6. **Isolation constraint found.** `keycape-services` validates JWTs through + `jwks_url: https://kc.coulomb.social/jwks`, and `netkingdom` uses OIDC + discovery at `https://kc.coulomb.social`. A networkless OpenBao can restore, + unseal and read back configuration and metadata. However, a live JWT login + (the consumer run) needs that hostname to resolve inside the contained network + to the *isolated* NK49 KeyCape, with a trusted TLS chain. It must never reach + the production KeyCape. An interactive OIDC operator login is out of scope for + the isolated target. The operator path is proven by restored config and role + readback, plus root-less login refusal where applicable. +7. **Reusable patterns.** These are reused as patterns, not copied: + - Existing in-repo template `docs/openbao-restore-drill-evidence.example.json` + and its validator `make openbao-validate-restore-evidence`. + - net-kingdom `docs/bootstrap-recovery-ceremony.md` and + `tools/bootstrap-recovery/isolated-*.py`: attended, hidden-key, fail-closed + setup with a Docker `--network none` target, a safe receipt and an + independent verifier. + - secrets-engine `docs/founder-admission.md` for the founder SSH-signed + admission with a time window. + +## T02 — Isolated target and adapter design + +```task +id: RPF-WP-0064-T02 +status: todo +priority: high +depends_on: [RPF-WP-0064-T01] +``` + +Design `scripts/openbao-isolated-restore/` (adapter plus doc). Fixed properties: + +- **Target:** a disposable Docker container on the founder workstation, running + the T01 image digest with an in-container Raft data directory on tmpfs. It is + never on railiance01 or the cluster, and it has no kubeconfig, no host + networking and no published ports. +- **Network containment:** phase A (restore + readback) uses `--network none`. + Phase B (handed to the consumer run) uses a single `--internal` Docker network + that has only the isolated KeyCape from NK49 and this OpenBao. In phase B, + `kc.coulomb.social` resolves to the isolated KeyCape, and egress is checked + to be refused before admission. Phase B is offered, not run, by this plan. +- **Custody inputs**, all founder-attended and never through argv, files in the + repo, env dumps or logs: + - (a) the encrypted snapshot object from `railiance-backups/openbao/`, named by + its ciphertext sha256; + - (b) the offline `railiance-backup.key` age identity, used for decryption + straight into the tmpfs stream; + - (c) 2 of 3 original Shamir shares, entered through a hidden prompt. + The restore needs no root token and no secret value is read. Readback uses a + token created in the restored target by an attended `generate-root`, used in + memory only and revoked before teardown, or uses the restored operator path. + Which of the two is decided in T02 review. +- **Readback (metadata only):** `sys/seal-status`, Raft applied index and cluster + id; the mounts `keycape-services` and `netkingdom` and their configs (issuer + and JWKS URL); role `secrets-engine-openbao` (bound claims and policies compared + to the source JSON hash); `platform/workloads/keycape/secrets-engine-openbao` + KV **metadata** (versions and timestamps, never `data`); and policy names. No + KV `data` read. +- **Timeout/abort/cleanup:** a hard wall-clock limit of 45 minutes from the first + custody input. Any fail-closed check, a missing object, an unexpected network + interface, or the founder pressing abort leads to `docker rm -f`, tmpfs + unmount, shredding of the decrypted stream, and a recorded `aborted` receipt. + Teardown also runs on success, unless phase B was admitted in the same window. + The plaintext snapshot never touches persistent disk. + +## T03 — Safe receipt schema and independent verifier + +```task +id: RPF-WP-0064-T03 +status: todo +priority: high +depends_on: [RPF-WP-0064-T02] +``` + +Extend `docs/openbao-restore-drill-evidence.example.json` into a v2 receipt: +- attempt id and admission hash; +- image digest; +- ciphertext sha256 of the snapshot and the source cluster id and applied index + (from the RPF-WP-0015 receipt); +- restored cluster id and index; +- a network-namespace proof (interfaces and the egress-refusal result); +- presence and source-hash match for each required object; +- result `restored | refused | aborted`; +- the teardown proof; +- `no_secret_material_recorded`. + +Add a verifier that runs separately from the adapter. It rejects placeholders, +unknown fields, missing teardown, `data` fields, and anything shaped like a +token or share. Validate it with adversarial fixtures. + +## T04 — Staged rehearsal with synthetic material + +```task +id: RPF-WP-0064-T04 +status: todo +priority: high +depends_on: [RPF-WP-0064-T03] +``` + +Run the full adapter against a synthetic OpenBao, initialized on purpose with +generated Shamir shares and seeded with stand-in mounts, role and KV metadata +that mirror the T01 source files. Snapshot it, age-encrypt it to a generated +identity, restore it into the T02 target, unseal it with the synthetic shares, +read it back, and tear it down. Rehearse each abort path (timeout, wrong +ciphertext hash, network interface present, missing object). This step uses no +real material. + +## T05 — Founder-signed attempt admission + +```task +id: RPF-WP-0064-T05 +status: wait +priority: high +needs_human: true +depends_on: [RPF-WP-0064-T04] +blocking_reason: "Founder must sign (SSH, secrets-engine founder-admission pattern) an attempt admission naming the snapshot ciphertext sha256, image digest, adapter commit, window start/end, abort operator and phase A only (phase B separately). Needs T04 rehearsal receipt first." +``` + +## T06 — Owner confirmation of package pin + +```task +id: RPF-WP-0064-T06 +status: wait +priority: low +depends_on: [RPF-WP-0064-T02] +blocking_reason: "rapp-openbao confirms the chart 0.28.2 / image 2.5.4 pin (and digest) used by production at the 2026-10-14 snapshot time is the one the isolated target runs. Role/KV/auth definitions stay railiance-platform-owned; no rapp-openbao change requested." +``` + +## T07 — Attended original restore (phase A) + +```task +id: RPF-WP-0064-T07 +status: wait +priority: high +needs_human: true +depends_on: [RPF-WP-0064-T05, RPF-WP-0064-T06] +blocking_reason: "Requires the founder in attendance holding the offline railiance-backup.key, 2 of 3 original Shamir shares and the encrypted snapshot from railiance-backups/openbao/, inside the T05-signed window. No agent handles custody material." +``` + +Run the admitted adapter once. The outcome is a T03 receipt verified +independently. The receipt is kept outside Git and referenced by its hash. A +missing object or failed check is a valid `refused` outcome; it is not repaired +in this attempt. + +## T08 — Bounded evidence handoff + +```task +id: RPF-WP-0064-T08 +status: wait +priority: medium +depends_on: [RPF-WP-0064-T07] +blocking_reason: "Needs the verified T07 receipt." +``` + +Hand to secrets-engine (SECRETS-WP-0015-T04) and net-kingdom (NK-WP-0049-T18) +the receipt hash, the verifier result, the image digest and the recovered-object +list. Also hand over the phase B offer terms (contained network with the +isolated KeyCape, same custody, its own admission). The handoff makes no +continuity claim. A pointer to the receipt goes to RPF-WP-0036-T03 as one +isolated restore receipt; it does not count as recurring assurance.