From cd043ca471b3bf2aeb493e204b584f1b36ac3559 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 25 May 2026 23:10:02 +0200 Subject: [PATCH] Refine bootstrap actions and runbook templates --- .../security_bootstrap_console.py | 414 ++++++------------ ...-custody-and-openbao-identity-bootstrap.md | 9 + 2 files changed, 152 insertions(+), 271 deletions(-) diff --git a/tools/security-bootstrap-console/security_bootstrap_console.py b/tools/security-bootstrap-console/security_bootstrap_console.py index 9cd82ec..6358e50 100755 --- a/tools/security-bootstrap-console/security_bootstrap_console.py +++ b/tools/security-bootstrap-console/security_bootstrap_console.py @@ -1216,20 +1216,9 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: initial_config_applied = yes(data, "openbao_initial_config_applied") trial_exposed = yes(data, "openbao_trial_material_exposed") response_complete = yes(data, "openbao_compromise_response_complete") - keys_rotated = yes(data, "openbao_unseal_keys_rotated") - root_disposed = data.get("root_token_disposition") in {"revoked", "offline-sealed"} openbao_direct_taint = openbao_trial_taint(data, "direct") openbao_downstream_taint = openbao_trial_taint(data, "downstream") - status_state = "todo" - status_reason = "Run any time to inspect the current OpenBao deployment state." - if preflight_done: - status_state = "done" - status_reason = "Deployment and pre-init status were verified." - if (init_output or initialized) and not root_disposed: - status_state = "redo" - status_reason = "OpenBao changed during init/unseal; rerun status before root-token disposition." - preflight_state = "done" if preflight_done else "todo" preflight_reason = "Safe preflight passed." if not preflight_done: @@ -1246,14 +1235,6 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: init_state = "blocked" init_reason = "OpenBao preflight must pass first." - unseal_state = "done" if initialized else "todo" - unseal_reason = "OpenBao is recorded as initialized and unsealed." - if not initialized: - unseal_reason = "Provide threshold shares by prompt, not as command arguments." - if not (init_output or initialized): - unseal_state = "blocked" - unseal_reason = "OpenBao init output must be produced first." - config_state = "done" if initial_config_applied else "todo" config_reason = "Initial configuration is recorded. Root-token disposition remains a separate gate." if not initial_config_applied: @@ -1276,13 +1257,6 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: verify_reason = "OpenBao must be initialized and unsealed first." return [ - { - "name": "OpenBao status", - "description": "Show pod, service, PVC, and seal/init status.", - "status": status_state, - "status_reason": status_reason, - "command": "make -C ../railiance-platform openbao-status", - }, { "name": "OpenBao preflight", "description": "Run safe status and verification checks. Does not initialize OpenBao.", @@ -1303,16 +1277,6 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: }, openbao_direct_taint if init_output or initialized else {}, ), - add_taint( - { - "name": "OpenBao unseal prompt", - "description": "Enter unseal shares by interactive terminal prompt. Do not place shares on the command line.", - "status": unseal_state, - "status_reason": unseal_reason, - "command": "kubectl exec -it -n openbao openbao-0 -- bao operator unseal", - }, - openbao_direct_taint if initialized else {}, - ), add_taint( { "name": "OpenBao initial configuration", @@ -1337,43 +1301,30 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]: - init_output = yes(data, "openbao_init_output_produced") initialized = yes(data, "openbao_initialized") initial_config_applied = yes(data, "openbao_initial_config_applied") - restore_done = yes(data, "restore_drill_passed") trial_exposed = yes(data, "openbao_trial_material_exposed") response_complete = yes(data, "openbao_compromise_response_complete") - keys_rotated = yes(data, "openbao_unseal_keys_rotated") - lockdown_drilled = yes(data, "openbao_emergency_lockdown_drilled") openbao_direct_taint = openbao_trial_taint(data, "direct") openbao_downstream_taint = openbao_trial_taint(data, "downstream") - key_compromise_status = "done" if response_complete else "todo" - key_compromise_location = "Use for trial output exposure, screenshots, chat paste, shell history, or lost custody." - if not trial_exposed: - key_compromise_status = "blocked" if not init_output else "todo" - key_compromise_location = "Mark trial key material exposed before running the response checklist." + key_compromise_location = "Template: record the exposure, choose reset versus rotation, inspect affected paths, and record only the non-secret outcome." + if trial_exposed and not response_complete: + key_compromise_location = "Active template: trial material is marked exposed; choose reset versus rotation before production trust." + elif response_complete: + key_compromise_location = "Template retained for future incidents; the current non-secret compromise response is marked complete." - rotate_status = "done" if keys_rotated else "todo" - rotate_location = "Run only after OpenBao is unsealed and existing exposed shares are available for quorum." + rotate_location = "Template: unseal OpenBao, start rotate-keys, submit current shares by prompt, route new shares to custody holders, then record confirmation." if not initialized: - rotate_status = "blocked" - rotate_location = "Unseal OpenBao first; rotate-keys needs a quorum of current unseal shares." - if not trial_exposed and not keys_rotated: - rotate_status = "blocked" - rotate_location = "Record the key-compromise condition or schedule a normal rotation first." + rotate_location = "Template: prepare now; execution needs an unsealed OpenBao instance and a quorum of current shares." - lockdown_status = "done" if lockdown_drilled else "todo" - lockdown_location = "Requires an unsealed OpenBao instance and a token with root or sudo on sys/seal." + lockdown_location = "Template: use a root/sudo-capable token, run emergency seal, confirm Sealed true, then record the drill or incident outcome." if not initialized: - lockdown_status = "blocked" - lockdown_location = "OpenBao is not recorded as unsealed; sealing is only meaningful while it is serving requests." + lockdown_location = "Template: prepare now; execution only changes availability while OpenBao is unsealed and serving requests." - restore_status = "done" if restore_done else "todo" - restore_location = "Create, encrypt, and isolated-restore a Railiance OpenBao Raft snapshot before live secrets move in." + restore_location = "Template: prepare workspace, snapshot, encrypt to age recipient, restore in isolation, verify, destroy drill environment, then record evidence." if not initial_config_applied: - restore_status = "blocked" - restore_location = "Apply OpenBao initial configuration before proving backup and restore." + restore_location = "Template: prepare now; execute after initial OpenBao configuration exists and before live secrets move in." return [ add_taint( @@ -1384,7 +1335,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]: "responsibility": "openbao-ceremony-operator", "email": role_email(data, "role_openbao_operator_email"), "location": key_compromise_location, - "state": key_compromise_status, + "state": "template", }, openbao_direct_taint if trial_exposed and not response_complete else {}, ), @@ -1396,7 +1347,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]: "responsibility": "openbao-ceremony-operator", "email": role_email(data, "role_openbao_operator_email"), "location": rotate_location, - "state": rotate_status, + "state": "template", }, openbao_downstream_taint if trial_exposed and not response_complete else {}, ), @@ -1408,7 +1359,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]: "responsibility": "openbao-ceremony-operator", "email": role_email(data, "role_openbao_operator_email"), "location": lockdown_location, - "state": lockdown_status, + "state": "template", }, openbao_downstream_taint if initialized else {}, ), @@ -1420,7 +1371,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]: "responsibility": "openbao-ceremony-operator", "email": role_email(data, "role_openbao_operator_email"), "location": restore_location, - "state": restore_status, + "state": "template", }, openbao_downstream_taint if initialized else {}, ), @@ -1428,61 +1379,40 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]: def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: - init_output = yes(data, "openbao_init_output_produced") initialized = yes(data, "openbao_initialized") - initial_config_applied = yes(data, "openbao_initial_config_applied") - restore_done = yes(data, "restore_drill_passed") trial_exposed = yes(data, "openbao_trial_material_exposed") response_complete = yes(data, "openbao_compromise_response_complete") - keys_rotated = yes(data, "openbao_unseal_keys_rotated") - lockdown_drilled = yes(data, "openbao_emergency_lockdown_drilled") openbao_direct_taint = openbao_trial_taint(data, "direct") openbao_downstream_taint = openbao_trial_taint(data, "downstream") - exposure_status = "done" if trial_exposed else "todo" - exposure_reason = "Trial key-material exposure is recorded in non-secret metadata." if trial_exposed else "Record that the trial init output escaped custody before using affected material." + def token_prompt_command(bao_command: str) -> str: + return ( + "printf 'OpenBao token: ' >&2\n" + "read -rs OPENBAO_TOKEN\n" + "printf '\\n' >&2\n" + "printf '%s\\n' \"$OPENBAO_TOKEN\" | kubectl exec -i -n openbao openbao-0 -- " + f"sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; {bao_command}'\n" + "unset OPENBAO_TOKEN" + ) - response_status = "done" if response_complete else "todo" - response_reason = "Compromise response was recorded." if response_complete else "Stop production use of exposed material, decide rotate-vs-reset, and record non-secret evidence." - if not trial_exposed: - response_status = "blocked" - response_reason = "Record the key-material exposure first." + def action(name: str, description: str, command: str, taint: dict[str, str] | None = None) -> dict[str, str]: + return add_taint( + { + "name": name, + "description": description, + "command": command, + }, + taint or {}, + ) - unseal_status = "done" if initialized else "todo" - unseal_reason = "OpenBao is unsealed." if initialized else "Unseal by hidden prompt before rotating unseal keys." - if not init_output: - unseal_status = "blocked" - unseal_reason = "OpenBao init output must exist first." - - rotate_status = "done" if keys_rotated else "todo" - rotate_reason = "New unseal keys are recorded as generated." if keys_rotated else "Start rotation, then submit current shares by prompt until quorum completes." - if not initialized: - rotate_status = "blocked" - rotate_reason = "OpenBao must be unsealed before rotate-keys can run." - if not trial_exposed and not keys_rotated: - rotate_status = "blocked" - rotate_reason = "Record exposure or schedule a normal rotation before generating new shares." - - lockdown_status = "done" if lockdown_drilled else "todo" - lockdown_reason = "Emergency lock-down drill is recorded." if lockdown_drilled else "Seals OpenBao immediately. Use only during an emergency or scheduled drill." - if not initialized: - lockdown_status = "blocked" - lockdown_reason = "OpenBao must be unsealed before emergency seal changes availability." - seal_command = ( - "printf 'OpenBao token: ' >&2\n" - "read -rs OPENBAO_TOKEN\n" - "printf '\\n' >&2\n" - "printf '%s\\n' \"$OPENBAO_TOKEN\" | kubectl exec -i -n openbao openbao-0 -- " - "sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; bao operator seal'\n" - "unset OPENBAO_TOKEN" - ) - - restore_status = "done" if restore_done else "todo" - restore_reason = "Restore drill is recorded." if restore_done else "Create encrypted snapshot evidence and complete an isolated restore proof." - if not initial_config_applied: - restore_status = "blocked" - restore_reason = "OpenBao initial configuration must be applied before the restore drill." - restore_taint = openbao_downstream_taint if initialized else {} + seal_command = token_prompt_command("bao operator seal") + audit_list_command = token_prompt_command("bao audit list") + secrets_list_command = token_prompt_command("bao secrets list") + auth_list_command = token_prompt_command("bao auth list") + openbao_status_command = "kubectl exec -n openbao openbao-0 -- bao status" + direct_taint = openbao_direct_taint if initialized else {} + downstream_taint = openbao_downstream_taint if initialized else {} + compromise_taint = openbao_downstream_taint if trial_exposed and not response_complete else {} public_key = extract_age_public_key(data.get("custodian_age_public_key")) quoted_public_key = shlex.quote(public_key if public_key else "") snapshot_workspace_command = ( @@ -1505,8 +1435,6 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: 'kubectl exec -n openbao openbao-0 -- rm -f /tmp/openbao-raft.snap\n' 'sha256sum "$RESTORE_DRILL_DIR/openbao-raft.snap" | tee "$RESTORE_DRILL_DIR/openbao-raft.snap.sha256"' ) - encrypt_snapshot_status = restore_status if public_key else "blocked" - encrypt_snapshot_reason = restore_reason if public_key else "Record the custodian public age recipient before encrypting snapshot custody material." encrypt_snapshot_command = ( 'export RESTORE_DRILL_DIR="${RESTORE_DRILL_DIR:-/tmp/netkingdom-openbao-restore-drill}"\n' f'age -r {quoted_public_key} -o "$RESTORE_DRILL_DIR/openbao-raft.snap.age" "$RESTORE_DRILL_DIR/openbao-raft.snap"\n' @@ -1531,155 +1459,95 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: ) return [ - add_taint( - { - "name": "Record key exposure", - "description": "Non-secret metadata checkbox in this UI; do not paste exposed values.", - "status": exposure_status, - "status_reason": exposure_reason, - "command": "Use the checkbox: Trial key material exposed", - }, - openbao_direct_taint if trial_exposed and not response_complete else {}, + action( + "OpenBao status", + "Show seal, initialization, storage, and HA state for the OpenBao pod. This command does not require a token.", + openbao_status_command, + downstream_taint, ), - add_taint( - { - "name": "Unseal by prompt", - "description": "Provide threshold shares interactively. Never put shares on the command line.", - "status": unseal_status, - "status_reason": unseal_reason, - "command": "kubectl exec -it -n openbao openbao-0 -- bao operator unseal", - }, - openbao_direct_taint if initialized else {}, + action( + "Unseal by prompt", + "Provide threshold shares interactively. Never put shares on the command line.", + "kubectl exec -it -n openbao openbao-0 -- bao operator unseal", + direct_taint, ), - add_taint( - { - "name": "Start unseal-key rotation", - "description": "Generate a new 3-share, threshold-2 Shamir split after compromise or planned migration.", - "status": rotate_status, - "status_reason": rotate_reason, - "command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -init -key-shares=3 -key-threshold=2", - }, - openbao_downstream_taint if trial_exposed and not response_complete else {}, + action( + "bao audit list", + "List OpenBao audit devices using a token entered by local hidden prompt.", + audit_list_command, + downstream_taint, ), - add_taint( - { - "name": "Submit current shares for rotation", - "description": "Repeat by prompt until the required threshold completes. Use the nonce from rotation init.", - "status": rotate_status, - "status_reason": rotate_reason, - "command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -nonce=", - }, - openbao_downstream_taint if trial_exposed and not response_complete else {}, + action( + "bao secrets list", + "List enabled OpenBao secrets engines using a token entered by local hidden prompt.", + secrets_list_command, + downstream_taint, ), - add_taint( - { - "name": "Cancel key rotation", - "description": "Abort a started rotation if the nonce, share handling, or ceremony context is wrong.", - "status": "todo" if initialized and not keys_rotated else "blocked", - "status_reason": "Available while a rotation is in progress." if initialized and not keys_rotated else "No active rotation expected.", - "command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -cancel", - }, - openbao_downstream_taint if trial_exposed and not response_complete else {}, + action( + "bao auth list", + "List enabled OpenBao auth methods using a token entered by local hidden prompt.", + auth_list_command, + downstream_taint, ), - add_taint( - { - "name": "Record compromise response complete", - "description": "Non-secret metadata checkbox after exposed material is rotated or the trial environment was reset.", - "status": response_status, - "status_reason": response_reason, - "command": "Use the checkbox: Compromise response complete", - }, - openbao_downstream_taint if trial_exposed and not response_complete else {}, + action( + "Start unseal-key rotation", + "Generate a new 3-share, threshold-2 Shamir split after compromise or planned migration.", + "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -init -key-shares=3 -key-threshold=2", + compromise_taint, ), - add_taint( - { - "name": "Emergency seal OpenBao", - "description": "Prompt locally for an OpenBao token and seal Railiance OpenBao without placing the token on the command line.", - "status": lockdown_status, - "status_reason": lockdown_reason, - "command": seal_command, - }, - openbao_downstream_taint if initialized else {}, + action( + "Submit current shares for rotation", + "Repeat by prompt until the required threshold completes. Use the nonce from rotation init.", + "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -nonce=", + compromise_taint, ), - add_taint( - { - "name": "Confirm sealed status", - "description": "Check that Railiance OpenBao reports Sealed true after an emergency seal.", - "status": "todo" if initialized else "blocked", - "status_reason": "Run after emergency seal; expect Sealed true." if initialized else "OpenBao must be initialized before status confirms lock-down.", - "command": "kubectl exec -n openbao openbao-0 -- bao status", - }, - openbao_downstream_taint if initialized else {}, + action( + "Cancel key rotation", + "Abort a started rotation if the nonce, share handling, or ceremony context is wrong.", + "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -cancel", + compromise_taint, ), - add_taint( - { - "name": "Record emergency lock-down drill", - "description": "Non-secret metadata checkbox after an emergency seal drill or real lock-down is confirmed.", - "status": lockdown_status, - "status_reason": lockdown_reason, - "command": "Use the checkbox: Emergency lock-down drill recorded", - }, - openbao_downstream_taint if initialized else {}, + action( + "Emergency seal OpenBao", + "Prompt locally for an OpenBao token and seal Railiance OpenBao without placing the token on the command line.", + seal_command, + downstream_taint, ), - add_taint( - { - "name": "Prepare restore drill workspace", - "description": "Create a local restricted directory for temporary snapshot evidence.", - "status": restore_status, - "status_reason": restore_reason, - "command": snapshot_workspace_command, - }, - restore_taint, + action( + "Confirm sealed status", + "Check that Railiance OpenBao reports Sealed true after an emergency seal.", + openbao_status_command, + downstream_taint, ), - add_taint( - { - "name": "Create encrypted-restore snapshot source", - "description": "Prompt locally for an OpenBao token, create a Raft snapshot in the pod, copy it out, remove the pod copy, and record a plaintext hash before encryption.", - "status": restore_status, - "status_reason": restore_reason, - "command": snapshot_command, - }, - restore_taint, + action( + "Prepare restore drill workspace", + "Create a local restricted directory for temporary snapshot evidence.", + snapshot_workspace_command, + downstream_taint, ), - add_taint( - { - "name": "Encrypt restore snapshot", - "description": "Encrypt the Raft snapshot to the custodian age recipient and remove the local plaintext snapshot.", - "status": encrypt_snapshot_status, - "status_reason": encrypt_snapshot_reason, - "command": encrypt_snapshot_command, - }, - restore_taint, + action( + "Create encrypted-restore snapshot source", + "Prompt locally for an OpenBao token, create a Raft snapshot in the pod, copy it out, remove the pod copy, and record a plaintext hash before encryption.", + snapshot_command, + downstream_taint, ), - add_taint( - { - "name": "Run isolated restore proof", - "description": "Checklist for proving the snapshot can restore into an isolated OpenBao instance before live secrets move in.", - "status": restore_status, - "status_reason": restore_reason, - "command": isolated_restore_command, - }, - restore_taint, + action( + "Encrypt restore snapshot", + "Encrypt the Raft snapshot to the custodian age recipient and remove the local plaintext snapshot. Replace if no recipient is recorded.", + encrypt_snapshot_command, + downstream_taint, ), - add_taint( - { - "name": "Run post-restore readiness check", - "description": "Re-run the Railiance post-unseal checks after restore evidence has been captured.", - "status": restore_status, - "status_reason": restore_reason, - "command": "make -C ../railiance-platform openbao-verify-post-unseal", - }, - restore_taint, + action( + "Run isolated restore proof", + "Checklist for proving the snapshot can restore into an isolated OpenBao instance before live secrets move in.", + isolated_restore_command, + downstream_taint, ), - add_taint( - { - "name": "Record restore drill passed", - "description": "Non-secret metadata checkbox after encrypted snapshot evidence and isolated restore proof are complete.", - "status": restore_status, - "status_reason": restore_reason, - "command": "Use the checkbox: Restore drill passed", - }, - restore_taint, + action( + "Run post-restore readiness check", + "Re-run the Railiance post-unseal checks after restore evidence has been captured.", + "make -C ../railiance-platform openbao-verify-post-unseal", + downstream_taint, ), ] @@ -1689,7 +1557,6 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]: role_ok = all(row["state"] != "nil" for row in role_rows[:5]) subsystem_rows = subsystem_payloads(data) integration_rows = integration_payloads(data) - runbook_rows = runbook_payloads(data) artifact_rows = artifact_payloads(data) return [ { @@ -1725,8 +1592,8 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]: { "key": "runbooks", "name": "Usecases & Runbooks", - "status": "ok" if all(row["state"] in {"done", "blocked"} for row in runbook_rows) else "set", - "reason": "Runbook states are recorded." if all(row["state"] in {"done", "blocked"} for row in runbook_rows) else "Review active runbooks and record non-secret outcomes.", + "status": "ok", + "reason": "Reusable actions and runbook templates are available; execution state is tracked by Integration & Tests and explicit confirmations.", }, { "key": "terminology", @@ -2268,6 +2135,7 @@ def ui_html() -> str: .state.todo { background: var(--warn); } .state.err { background: var(--bad); } .state.blocked { background: var(--bad); } + .state.template { background: #f1eee5; } .role-chip { display: inline-flex; max-width: 100%; @@ -2553,7 +2421,7 @@ def ui_html() -> str:
4. Integration & Testsnil
Loading integration gate.
-

This section connects the subsystems and shows every console command as a copyable block. Commands still run outside this browser so secret output never enters the control surface.

+

This section tracks stateful integration runbooks and gates. Status belongs to these tasks; reusable command-only actions live below in Usecases & Runbooks.

Start OIDC login check @@ -2580,20 +2448,6 @@ def ui_html() -> str:
-
- 6. Usecases & Runbooksnil -
Loading runbook gate.
-

Use these routines when the ceremony path changes, trial secrets are exposed, or custody material must be regenerated. The UI records only non-secret outcomes.

-
-
- - - - -
-
-
-
5. Artefacts & Locationsnil
Loading artefact gate.
@@ -2622,6 +2476,20 @@ def ui_html() -> str:
+
+ 6. Usecases & Runbooksnil +
Loading runbook gate.
+

This section contains reusable actions and runbook templates. Action cards are copyable command references without task status; runbook task state belongs to Integration & Tests or to the explicit confirmation gates.

+
+
+ + + + +
+
+
+
7. Terminology & Patternsnil
Loading terminology gate.
@@ -2842,10 +2710,13 @@ def ui_html() -> str: const description = document.createElement("div"); description.className = "record-description"; description.textContent = item.description; - const statusReason = document.createElement("div"); - statusReason.className = "record-description"; - statusReason.textContent = item.status_reason || ""; - title.append(name, description, statusReason); + title.append(name, description); + if (item.status_reason) { + const statusReason = document.createElement("div"); + statusReason.className = "record-description"; + statusReason.textContent = item.status_reason; + title.append(statusReason); + } const taintNote = makeTaintNote(item); if (taintNote) title.append(taintNote); @@ -2857,7 +2728,8 @@ def ui_html() -> str: button.dataset.command = item.command; const commandActions = document.createElement("div"); commandActions.className = "inline-actions"; - commandActions.append(makeStateBadge(item.status), button); + if (item.status) commandActions.append(makeStateBadge(item.status)); + commandActions.append(button); head.append(title, commandActions); const code = document.createElement("code"); diff --git a/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md b/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md index 3973047..5949569 100644 --- a/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md +++ b/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md @@ -269,6 +269,15 @@ create/copy/hash an OpenBao Raft snapshot, encrypt it to the custodian age recipient, complete an isolated restore proof, rerun post-unseal verification, and record only non-secret completion evidence. +**2026-05-25:** Refined the action/runbook model in the control surface: +Integration & Tests now carries stateful runbook tasks and gates, while +Usecases & Runbooks contains status-less action cards and neutral runbook +templates. Added copyable OpenBao inspection actions for `bao audit list`, +`bao secrets list`, and `bao auth list` with local hidden token prompts, +removed duplicate OpenBao status/unseal cards from the stateful Integration +command list, and restored Artefacts & Locations above Usecases & Runbooks in +the workflow. + **2026-05-24:** Stepped back from ad hoc secret rollout and added the custodian age-key bootstrap model to the control surface. The UI now records the custodian public age recipient, a derived fingerprint, and a non-secret