diff --git a/sso-mfa/bootstrap/creds-bootstrap-agent.sh b/sso-mfa/bootstrap/creds-bootstrap-agent.sh index 4c85254..f704883 100755 --- a/sso-mfa/bootstrap/creds-bootstrap-agent.sh +++ b/sso-mfa/bootstrap/creds-bootstrap-agent.sh @@ -298,6 +298,45 @@ else echo " [dry-run] would run: bash creds-verify.sh" fi +# ── Phase 7b: OpenBao init/unseal (sops-held-automation, optional) ─────────── + +step "7b — OpenBao init/unseal (sops-held-automation, optional)" + +# NET-WP-0020 T2: greenfield-rebuild hook. Runs only when the openbao +# namespace exists AND the console has selected sops-held-automation +# (the helper enforces that gate itself and refuses attended-ceremony / +# auto-unseal-transit). Skipped silently on clusters without OpenBao. +if kubectl get namespace openbao &>/dev/null; then + if [[ "$(state_get openbao_post_unseal_verified)" == "true" ]]; then + ok "OpenBao already verified — skipping" + elif [[ "$DRY_RUN" == false ]]; then + if (cd "$SCRIPT_DIR" && bash openbao-init-unseal.sh "$SECRETS_DIR"); then + state_set "openbao_initialized" "true" + state_set "openbao_post_unseal_verified" "true" + ok "OpenBao initialized/unsealed and verified" + + # New init material must reach age custody before cleanup. + if [[ -d "$SECRETS_DIR/openbao" ]]; then + log "encrypting OpenBao init material → secrets.enc/ ..." + (cd "$SCRIPT_DIR" && bash encrypt-secrets.sh \ + "$SECRETS_DIR" "$AGE_KEY" --no-shred) + cd "$REPO_ROOT" + git add sso-mfa/bootstrap/secrets.enc/ \ + sso-mfa/bootstrap/creds-state.yaml + git diff --cached --quiet || git commit -m \ + "chore(creds): encrypted OpenBao init material [agent]" + fi + else + warn "OpenBao init/unseal did not complete — see output" + warn "(gate unselected or pod not ready; bootstrap continues)" + fi + else + echo " [dry-run] would run: openbao-init-unseal.sh $SECRETS_DIR" + fi +else + ok "no openbao namespace in this cluster — skipping" +fi + # ── Phase 8: Ops bundle ──────────────────────────────────────────────────────── step "8 — Create ops bundle (age-encrypted snapshot)" diff --git a/sso-mfa/bootstrap/creds-state.yaml b/sso-mfa/bootstrap/creds-state.yaml index ae07446..4f99eeb 100644 --- a/sso-mfa/bootstrap/creds-state.yaml +++ b/sso-mfa/bootstrap/creds-state.yaml @@ -28,5 +28,12 @@ secrets_applied: enckey_bootstrapped: true pi_admin_created: true +# OpenBao init/unseal (NET-WP-0020 T2, sops-held-automation lane only). +# false here because the current cluster's OpenBao was initialized via the +# attended ceremony (NET-WP-0015–0017), not this automation path. These flip +# to true only when Phase 7b runs on a greenfield rebuild. +openbao_initialized: false +openbao_post_unseal_verified: false + # Derived: all true → bootstrap complete bootstrap_complete: true diff --git a/workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md b/workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md index f5c04a8..8e1208c 100644 --- a/workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md +++ b/workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md @@ -55,16 +55,24 @@ state_hub_task_id: "65407eb1-9d89-4158-aed5-4987badd83fc" (emitted on the script's `EVIDENCE` JSON line) - [x] Integrate with `make openbao-configure-initial` post-unseal (`OPENBAO_RUN_CONFIGURE_INITIAL=1` chains it; default prints the handoff hint) -- [ ] Wire the helper as an optional phase inside `creds-bootstrap-agent.sh` - (agent-policy blocked automated edits to the credential bootstrap script on - 2026-07-02 — operator should add a phase that calls the helper, sets the two - state flags in `creds-state.yaml`, and re-runs `encrypt-secrets.sh` + commit - when `secrets/openbao/` was created) +- [x] Wire the helper as an optional phase inside `creds-bootstrap-agent.sh` + (Phase 7b, reviewed and approved by Bernd 2026-07-02: runs only when the + `openbao` namespace exists, skips when already verified, sets the two + `creds-state.yaml` flags, encrypts + commits new init material, and a + custody-gate refusal warns without aborting the SSO/MFA bootstrap — + dry-run/skip/refusal paths harness-tested) - [ ] Greenfield live proof: run against a sealed/uninitialized OpenBao on a rebuild slate (current cluster is already initialized+unsealed, so only the status/verify path was live-smoked on 2026-07-02; custody-gate refusal was proven for `unselected` and `attended-ceremony`) +**2026-07-02 (later):** Bernd reviewed the helper design (five safety +properties incl. the root-token-in-bundle caveat of the sops-held model) and +approved the Phase 7b wiring as proposed. Applied, `bash -n` clean, all three +conditional paths verified by harness. Pre-existing note: the agent's Phase 0 +cannot dry-run on machines without the age key — unrelated to this change. +Remaining T02 item is only the greenfield live proof. + **2026-07-02:** Helper implemented and smoke-tested: dry-run against the live cluster passed the custody gate (`sops-held-automation` selected) and read `initialized=true sealed=false`; negative tests proved refusal for unselected