RPF-WP-0015-T03: attended OpenBao reboot snapshot lane with mandatory off-OpenBao escrow

Silent contained child: raft snapshot via tunnel, age to offsite (+custodian)
recipients, in-memory escrow decrypt proof (founder decision recorded),
If-None-Match upload to openbao/, receipt self-validated. Preflight now rejects
snapshot receipts without a decryption key held outside OpenBao.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 680842@bnt-lap001
Assistant-Session: 54eb9e8e-7d6b-4f0c-b7a6-a6db62075883
This commit is contained in:
codex 2026-10-14 19:49:02 +02:00
parent 64292531ce
commit 133628c573
8 changed files with 855 additions and 4 deletions

View file

@ -129,6 +129,44 @@ State Hub.
Pass that receipt to the preflight with
`--openbao-snapshot-evidence /path/to/receipt.json`; a merely existing,
stale, placeholder, wrong-cluster, unencrypted, or on-host receipt is denied.
The reviewed lane is `make openbao-reboot-snapshot` (offline tests:
`make openbao-reboot-snapshot-test`). It runs
`scripts/openbao-reboot-snapshot.py` as the single silent child of the
contained `openbao-platform-admin-login` (via
`scripts/openbao-attended-exec.py`):
```bash
make openbao-reboot-snapshot \
SNAPSHOT_SCENARIO_ID=RPF-WP-0015-T03-<scenario> \
SNAPSHOT_RECEIPT=/abs/new/receipt.json \
SNAPSHOT_CUSTODIAN_ESCROW_REF=<where a decryption key is held outside OpenBao> \
SNAPSHOT_CUSTODIAN_RECIPIENT=age1... # only if that offline key differs from the offsite recipient
SNAPSHOT_PRIVATE_DIR=/abs/0700/dir # optional; default is the session HOME (prefer tmpfs)
```
Recoverability requires a decryption key held outside OpenBao: the escrow
reference is mandatory and the preflight rejects a receipt without one. If the
custodian's offline key is the offsite recipient, the reference alone is
recorded; otherwise the snapshot is also encrypted to
`SNAPSHOT_CUSTODIAN_RECIPIENT` (age: any one recipient key decrypts). The
receipt lists all recipients. Reading the offsite `AGE_PRIVATE_KEY` for the
decrypt-stream proof is a founder decision (in memory only), recorded in
`reviews/RPF-WP-0015-T03-founder-go-20261014.json#escrow_key_read` and cited by
the receipt. Snapshots upload to the founder-created `openbao/` folder.
In order it reads `bao status` (initialized, unsealed, Raft), saves the
snapshot through the tunnel into a fresh 0700 temp dir outside Git, re-reads
status (same `cluster_id`, index not behind), age-encrypts to the offsite
lane recipient declared in `lib/railiance-backup-common.sh` (plus the custodian
recipient, if given), proves the
lane's escrow key derives that recipient and decrypt-streams the ciphertext
to the plaintext sha256, then uploads ciphertext only to the governed
Nextcloud lane with `If-None-Match: *`. The temp dir is always shredded.
The receipt (0600, `encrypted_location_ref`
`offhost-custody:<scenario>:<ts>`) is self-checked with the preflight's own
validator before it is written; on any failure the file holds a value-safe
`status: failed` record instead, which the preflight rejects.
7. Have both Shamir share custodians present. Prove provider-console access and
SSH access (same-day `RAIL01-CONSOLE-*` receipt). Record the exact restart
operator, abort operator, and the railiance-infra independent abort owner.