generated from coulomb/repo-seed
T2: greenfield live proof against a fresh uninitialized OpenBao 2.5.5 — caught and fixed 'bao operator unseal -' not reading stdin (now 'bao write sys/unseal key=-'); init and reseal-replay paths proven. T3: attended-ceremony selectable — runbook, non-secret ceremony-record template + validator, and a lab/production deployment profile that blocks sops-held-automation in console selection, gates, and the init script. T4: console gate + evidence flags for auto-unseal-transit (Helm seal stanza prepared in railiance-platform). Also: SCOPE.md refreshed to current repo state; adhoc fix for the broken check-secrets Make target (unescaped $). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
41 lines
2.0 KiB
Markdown
41 lines
2.0 KiB
Markdown
# OpenBao greenfield init/unseal proof (NET-WP-0020 T2)
|
|
|
|
Date: 2026-07-02
|
|
Scope: `sso-mfa/bootstrap/openbao-init-unseal.sh` proven live against a
|
|
genuinely uninitialized OpenBao 2.5.5 instance.
|
|
|
|
## Method
|
|
|
|
A fresh local `bao server` (file storage, throwaway scratchpad dir) was started
|
|
uninitialized and sealed. A `kubectl` shim forwarded the script's
|
|
`kubectl -n openbao exec openbao-0 -- bao …` calls to the local instance, so
|
|
the script ran byte-for-byte unmodified logic against real OpenBao. Console
|
|
metadata lived in a scratch file with `sops-held-automation` selected.
|
|
|
|
This substitutes for the "rebuild slate" run: it exercises the exact
|
|
init/unseal/verify code paths on a greenfield instance. The first 3-node
|
|
rebuild will re-run the same script through `creds-bootstrap-agent.sh`
|
|
Phase 7b.
|
|
|
|
## Results
|
|
|
|
1. **Custody gate refusal** — with no model selected, the script refused
|
|
(`unseal custody model is 'unselected'`). Matches earlier negative tests.
|
|
2. **Bug found and fixed** — `bao operator unseal -` does **not** read the
|
|
share from stdin (OpenBao treats `-` as the literal key: *"'key' must be a
|
|
valid hex or base64 string"*; without an argument it demands a TTY). The
|
|
live cluster never hit this because it was already unsealed. Fixed to
|
|
`bao write sys/unseal key=-`, which reads the value from stdin — shares
|
|
still never touch argv or logs.
|
|
3. **Greenfield init path** — uninitialized → `operator init` (3 shares,
|
|
threshold 2) → init.json written 0600 into the age-custody secrets dir →
|
|
share replay stopped at threshold (share 2 of 3) → post-unseal verified.
|
|
Evidence: `openbao_did_init_this_run=true`, `openbao_initialized=true`,
|
|
`openbao_post_unseal_verified=true`.
|
|
4. **Restart/reseal path** — server restarted (initialized + sealed) →
|
|
script skipped init, replayed SOPS-held shares to threshold → verified.
|
|
Evidence: `openbao_did_init_this_run=false`, both flags true.
|
|
|
|
Test server, storage, and init material were destroyed after the proof
|
|
(init.json shredded).
|