Refine bootstrap actions and runbook templates

This commit is contained in:
2026-05-25 23:10:02 +02:00
parent 20fd300e88
commit cd043ca471
2 changed files with 152 additions and 271 deletions

View File

@@ -1216,20 +1216,9 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
initial_config_applied = yes(data, "openbao_initial_config_applied") initial_config_applied = yes(data, "openbao_initial_config_applied")
trial_exposed = yes(data, "openbao_trial_material_exposed") trial_exposed = yes(data, "openbao_trial_material_exposed")
response_complete = yes(data, "openbao_compromise_response_complete") 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_direct_taint = openbao_trial_taint(data, "direct")
openbao_downstream_taint = openbao_trial_taint(data, "downstream") 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_state = "done" if preflight_done else "todo"
preflight_reason = "Safe preflight passed." preflight_reason = "Safe preflight passed."
if not preflight_done: if not preflight_done:
@@ -1246,14 +1235,6 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
init_state = "blocked" init_state = "blocked"
init_reason = "OpenBao preflight must pass first." 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_state = "done" if initial_config_applied else "todo"
config_reason = "Initial configuration is recorded. Root-token disposition remains a separate gate." config_reason = "Initial configuration is recorded. Root-token disposition remains a separate gate."
if not initial_config_applied: 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." verify_reason = "OpenBao must be initialized and unsealed first."
return [ 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", "name": "OpenBao preflight",
"description": "Run safe status and verification checks. Does not initialize OpenBao.", "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 {}, 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( add_taint(
{ {
"name": "OpenBao initial configuration", "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]]: def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
init_output = yes(data, "openbao_init_output_produced")
initialized = yes(data, "openbao_initialized") initialized = yes(data, "openbao_initialized")
initial_config_applied = yes(data, "openbao_initial_config_applied") initial_config_applied = yes(data, "openbao_initial_config_applied")
restore_done = yes(data, "restore_drill_passed")
trial_exposed = yes(data, "openbao_trial_material_exposed") trial_exposed = yes(data, "openbao_trial_material_exposed")
response_complete = yes(data, "openbao_compromise_response_complete") 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_direct_taint = openbao_trial_taint(data, "direct")
openbao_downstream_taint = openbao_trial_taint(data, "downstream") openbao_downstream_taint = openbao_trial_taint(data, "downstream")
key_compromise_status = "done" if response_complete else "todo" key_compromise_location = "Template: record the exposure, choose reset versus rotation, inspect affected paths, and record only the non-secret outcome."
key_compromise_location = "Use for trial output exposure, screenshots, chat paste, shell history, or lost custody." if trial_exposed and not response_complete:
if not trial_exposed: key_compromise_location = "Active template: trial material is marked exposed; choose reset versus rotation before production trust."
key_compromise_status = "blocked" if not init_output else "todo" elif response_complete:
key_compromise_location = "Mark trial key material exposed before running the response checklist." 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 = "Template: unseal OpenBao, start rotate-keys, submit current shares by prompt, route new shares to custody holders, then record confirmation."
rotate_location = "Run only after OpenBao is unsealed and existing exposed shares are available for quorum."
if not initialized: if not initialized:
rotate_status = "blocked" rotate_location = "Template: prepare now; execution needs an unsealed OpenBao instance and a quorum of current shares."
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."
lockdown_status = "done" if lockdown_drilled else "todo" lockdown_location = "Template: use a root/sudo-capable token, run emergency seal, confirm Sealed true, then record the drill or incident outcome."
lockdown_location = "Requires an unsealed OpenBao instance and a token with root or sudo on sys/seal."
if not initialized: if not initialized:
lockdown_status = "blocked" lockdown_location = "Template: prepare now; execution only changes availability while OpenBao is unsealed and serving requests."
lockdown_location = "OpenBao is not recorded as unsealed; sealing is only meaningful while it is serving requests."
restore_status = "done" if restore_done else "todo" restore_location = "Template: prepare workspace, snapshot, encrypt to age recipient, restore in isolation, verify, destroy drill environment, then record evidence."
restore_location = "Create, encrypt, and isolated-restore a Railiance OpenBao Raft snapshot before live secrets move in."
if not initial_config_applied: if not initial_config_applied:
restore_status = "blocked" restore_location = "Template: prepare now; execute after initial OpenBao configuration exists and before live secrets move in."
restore_location = "Apply OpenBao initial configuration before proving backup and restore."
return [ return [
add_taint( add_taint(
@@ -1384,7 +1335,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
"responsibility": "openbao-ceremony-operator", "responsibility": "openbao-ceremony-operator",
"email": role_email(data, "role_openbao_operator_email"), "email": role_email(data, "role_openbao_operator_email"),
"location": key_compromise_location, "location": key_compromise_location,
"state": key_compromise_status, "state": "template",
}, },
openbao_direct_taint if trial_exposed and not response_complete else {}, 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", "responsibility": "openbao-ceremony-operator",
"email": role_email(data, "role_openbao_operator_email"), "email": role_email(data, "role_openbao_operator_email"),
"location": rotate_location, "location": rotate_location,
"state": rotate_status, "state": "template",
}, },
openbao_downstream_taint if trial_exposed and not response_complete else {}, 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", "responsibility": "openbao-ceremony-operator",
"email": role_email(data, "role_openbao_operator_email"), "email": role_email(data, "role_openbao_operator_email"),
"location": lockdown_location, "location": lockdown_location,
"state": lockdown_status, "state": "template",
}, },
openbao_downstream_taint if initialized else {}, 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", "responsibility": "openbao-ceremony-operator",
"email": role_email(data, "role_openbao_operator_email"), "email": role_email(data, "role_openbao_operator_email"),
"location": restore_location, "location": restore_location,
"state": restore_status, "state": "template",
}, },
openbao_downstream_taint if initialized else {}, 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]]: 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") 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") trial_exposed = yes(data, "openbao_trial_material_exposed")
response_complete = yes(data, "openbao_compromise_response_complete") 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_direct_taint = openbao_trial_taint(data, "direct")
openbao_downstream_taint = openbao_trial_taint(data, "downstream") openbao_downstream_taint = openbao_trial_taint(data, "downstream")
exposure_status = "done" if trial_exposed else "todo" def token_prompt_command(bao_command: str) -> str:
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." 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" def action(name: str, description: str, command: str, taint: dict[str, str] | None = None) -> dict[str, str]:
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." return add_taint(
if not trial_exposed: {
response_status = "blocked" "name": name,
response_reason = "Record the key-material exposure first." "description": description,
"command": command,
},
taint or {},
)
unseal_status = "done" if initialized else "todo" seal_command = token_prompt_command("bao operator seal")
unseal_reason = "OpenBao is unsealed." if initialized else "Unseal by hidden prompt before rotating unseal keys." audit_list_command = token_prompt_command("bao audit list")
if not init_output: secrets_list_command = token_prompt_command("bao secrets list")
unseal_status = "blocked" auth_list_command = token_prompt_command("bao auth list")
unseal_reason = "OpenBao init output must exist first." openbao_status_command = "kubectl exec -n openbao openbao-0 -- bao status"
direct_taint = openbao_direct_taint if initialized else {}
rotate_status = "done" if keys_rotated else "todo" downstream_taint = openbao_downstream_taint if initialized else {}
rotate_reason = "New unseal keys are recorded as generated." if keys_rotated else "Start rotation, then submit current shares by prompt until quorum completes." compromise_taint = openbao_downstream_taint if trial_exposed and not response_complete else {}
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 {}
public_key = extract_age_public_key(data.get("custodian_age_public_key")) public_key = extract_age_public_key(data.get("custodian_age_public_key"))
quoted_public_key = shlex.quote(public_key if public_key else "<age-recipient>") quoted_public_key = shlex.quote(public_key if public_key else "<age-recipient>")
snapshot_workspace_command = ( 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' '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"' '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 = ( encrypt_snapshot_command = (
'export RESTORE_DRILL_DIR="${RESTORE_DRILL_DIR:-/tmp/netkingdom-openbao-restore-drill}"\n' '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' 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 [ return [
add_taint( action(
{ "OpenBao status",
"name": "Record key exposure", "Show seal, initialization, storage, and HA state for the OpenBao pod. This command does not require a token.",
"description": "Non-secret metadata checkbox in this UI; do not paste exposed values.", openbao_status_command,
"status": exposure_status, downstream_taint,
"status_reason": exposure_reason,
"command": "Use the checkbox: Trial key material exposed",
},
openbao_direct_taint if trial_exposed and not response_complete else {},
), ),
add_taint( action(
{ "Unseal by prompt",
"name": "Unseal by prompt", "Provide threshold shares interactively. Never put shares on the command line.",
"description": "Provide threshold shares interactively. Never put shares on the command line.", "kubectl exec -it -n openbao openbao-0 -- bao operator unseal",
"status": unseal_status, direct_taint,
"status_reason": unseal_reason,
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator unseal",
},
openbao_direct_taint if initialized else {},
), ),
add_taint( action(
{ "bao audit list",
"name": "Start unseal-key rotation", "List OpenBao audit devices using a token entered by local hidden prompt.",
"description": "Generate a new 3-share, threshold-2 Shamir split after compromise or planned migration.", audit_list_command,
"status": rotate_status, downstream_taint,
"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 {},
), ),
add_taint( action(
{ "bao secrets list",
"name": "Submit current shares for rotation", "List enabled OpenBao secrets engines using a token entered by local hidden prompt.",
"description": "Repeat by prompt until the required threshold completes. Use the nonce from rotation init.", secrets_list_command,
"status": rotate_status, downstream_taint,
"status_reason": rotate_reason,
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -nonce=<nonce-from-rotation-init>",
},
openbao_downstream_taint if trial_exposed and not response_complete else {},
), ),
add_taint( action(
{ "bao auth list",
"name": "Cancel key rotation", "List enabled OpenBao auth methods using a token entered by local hidden prompt.",
"description": "Abort a started rotation if the nonce, share handling, or ceremony context is wrong.", auth_list_command,
"status": "todo" if initialized and not keys_rotated else "blocked", downstream_taint,
"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 {},
), ),
add_taint( action(
{ "Start unseal-key rotation",
"name": "Record compromise response complete", "Generate a new 3-share, threshold-2 Shamir split after compromise or planned migration.",
"description": "Non-secret metadata checkbox after exposed material is rotated or the trial environment was reset.", "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -init -key-shares=3 -key-threshold=2",
"status": response_status, compromise_taint,
"status_reason": response_reason,
"command": "Use the checkbox: Compromise response complete",
},
openbao_downstream_taint if trial_exposed and not response_complete else {},
), ),
add_taint( action(
{ "Submit current shares for rotation",
"name": "Emergency seal OpenBao", "Repeat by prompt until the required threshold completes. Use the nonce from rotation init.",
"description": "Prompt locally for an OpenBao token and seal Railiance OpenBao without placing the token on the command line.", "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -nonce=<nonce-from-rotation-init>",
"status": lockdown_status, compromise_taint,
"status_reason": lockdown_reason,
"command": seal_command,
},
openbao_downstream_taint if initialized else {},
), ),
add_taint( action(
{ "Cancel key rotation",
"name": "Confirm sealed status", "Abort a started rotation if the nonce, share handling, or ceremony context is wrong.",
"description": "Check that Railiance OpenBao reports Sealed true after an emergency seal.", "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -cancel",
"status": "todo" if initialized else "blocked", compromise_taint,
"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 {},
), ),
add_taint( action(
{ "Emergency seal OpenBao",
"name": "Record emergency lock-down drill", "Prompt locally for an OpenBao token and seal Railiance OpenBao without placing the token on the command line.",
"description": "Non-secret metadata checkbox after an emergency seal drill or real lock-down is confirmed.", seal_command,
"status": lockdown_status, downstream_taint,
"status_reason": lockdown_reason,
"command": "Use the checkbox: Emergency lock-down drill recorded",
},
openbao_downstream_taint if initialized else {},
), ),
add_taint( action(
{ "Confirm sealed status",
"name": "Prepare restore drill workspace", "Check that Railiance OpenBao reports Sealed true after an emergency seal.",
"description": "Create a local restricted directory for temporary snapshot evidence.", openbao_status_command,
"status": restore_status, downstream_taint,
"status_reason": restore_reason,
"command": snapshot_workspace_command,
},
restore_taint,
), ),
add_taint( action(
{ "Prepare restore drill workspace",
"name": "Create encrypted-restore snapshot source", "Create a local restricted directory for temporary snapshot evidence.",
"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.", snapshot_workspace_command,
"status": restore_status, downstream_taint,
"status_reason": restore_reason,
"command": snapshot_command,
},
restore_taint,
), ),
add_taint( action(
{ "Create encrypted-restore snapshot source",
"name": "Encrypt restore snapshot", "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.",
"description": "Encrypt the Raft snapshot to the custodian age recipient and remove the local plaintext snapshot.", snapshot_command,
"status": encrypt_snapshot_status, downstream_taint,
"status_reason": encrypt_snapshot_reason,
"command": encrypt_snapshot_command,
},
restore_taint,
), ),
add_taint( action(
{ "Encrypt restore snapshot",
"name": "Run isolated restore proof", "Encrypt the Raft snapshot to the custodian age recipient and remove the local plaintext snapshot. Replace <age-recipient> if no recipient is recorded.",
"description": "Checklist for proving the snapshot can restore into an isolated OpenBao instance before live secrets move in.", encrypt_snapshot_command,
"status": restore_status, downstream_taint,
"status_reason": restore_reason,
"command": isolated_restore_command,
},
restore_taint,
), ),
add_taint( action(
{ "Run isolated restore proof",
"name": "Run post-restore readiness check", "Checklist for proving the snapshot can restore into an isolated OpenBao instance before live secrets move in.",
"description": "Re-run the Railiance post-unseal checks after restore evidence has been captured.", isolated_restore_command,
"status": restore_status, downstream_taint,
"status_reason": restore_reason,
"command": "make -C ../railiance-platform openbao-verify-post-unseal",
},
restore_taint,
), ),
add_taint( action(
{ "Run post-restore readiness check",
"name": "Record restore drill passed", "Re-run the Railiance post-unseal checks after restore evidence has been captured.",
"description": "Non-secret metadata checkbox after encrypted snapshot evidence and isolated restore proof are complete.", "make -C ../railiance-platform openbao-verify-post-unseal",
"status": restore_status, downstream_taint,
"status_reason": restore_reason,
"command": "Use the checkbox: Restore drill passed",
},
restore_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]) role_ok = all(row["state"] != "nil" for row in role_rows[:5])
subsystem_rows = subsystem_payloads(data) subsystem_rows = subsystem_payloads(data)
integration_rows = integration_payloads(data) integration_rows = integration_payloads(data)
runbook_rows = runbook_payloads(data)
artifact_rows = artifact_payloads(data) artifact_rows = artifact_payloads(data)
return [ return [
{ {
@@ -1725,8 +1592,8 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
{ {
"key": "runbooks", "key": "runbooks",
"name": "Usecases & Runbooks", "name": "Usecases & Runbooks",
"status": "ok" if all(row["state"] in {"done", "blocked"} for row in runbook_rows) else "set", "status": "ok",
"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.", "reason": "Reusable actions and runbook templates are available; execution state is tracked by Integration & Tests and explicit confirmations.",
}, },
{ {
"key": "terminology", "key": "terminology",
@@ -2268,6 +2135,7 @@ def ui_html() -> str:
.state.todo { background: var(--warn); } .state.todo { background: var(--warn); }
.state.err { background: var(--bad); } .state.err { background: var(--bad); }
.state.blocked { background: var(--bad); } .state.blocked { background: var(--bad); }
.state.template { background: #f1eee5; }
.role-chip { .role-chip {
display: inline-flex; display: inline-flex;
max-width: 100%; max-width: 100%;
@@ -2553,7 +2421,7 @@ def ui_html() -> str:
<details class="panel workflow-section" data-section="integrations" open> <details class="panel workflow-section" data-section="integrations" open>
<summary><span class="summary-title">4. Integration & Tests</span><span class="state nil" data-section-state="integrations">nil</span></summary> <summary><span class="summary-title">4. Integration & Tests</span><span class="state nil" data-section-state="integrations">nil</span></summary>
<div class="section-gate" data-section-gate="integrations">Loading integration gate.</div> <div class="section-gate" data-section-gate="integrations">Loading integration gate.</div>
<p class="notice">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.</p> <p class="notice">This section tracks stateful integration runbooks and gates. Status belongs to these tasks; reusable command-only actions live below in Usecases & Runbooks.</p>
<div id="integrations-records" class="record-list"></div> <div id="integrations-records" class="record-list"></div>
<div class="inline-actions"> <div class="inline-actions">
<a class="button-link" href="/oidc/start" target="_blank" rel="noreferrer" title="Start the bootstrap-console OIDC authorization flow through KeyCape.">Start OIDC login check</a> <a class="button-link" href="/oidc/start" target="_blank" rel="noreferrer" title="Start the bootstrap-console OIDC authorization flow through KeyCape.">Start OIDC login check</a>
@@ -2580,20 +2448,6 @@ def ui_html() -> str:
<div id="command-list" class="command-list"></div> <div id="command-list" class="command-list"></div>
</details> </details>
<details class="panel workflow-section" data-section="runbooks" open>
<summary><span class="summary-title">6. Usecases & Runbooks</span><span class="state nil" data-section-state="runbooks">nil</span></summary>
<div class="section-gate" data-section-gate="runbooks">Loading runbook gate.</div>
<p class="notice">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.</p>
<div id="runbooks-records" class="record-list"></div>
<div class="choice-list">
<label class="choice"><input id="openbao_trial_material_exposed" type="checkbox"><span><strong>Trial key material exposed</strong><span>Init output, unseal shares, or root-token material escaped the custody boundary during a trial.</span></span></label>
<label class="choice"><input id="openbao_compromise_response_complete" type="checkbox"><span><strong>Compromise response complete</strong><span>Exposed material was rotated or the trial environment was reset. No secret values are recorded here.</span></span></label>
<label class="choice"><input id="openbao_unseal_keys_rotated" type="checkbox"><span><strong>New unseal keys generated</strong><span>OpenBao generated replacement unseal shares under the current runbook.</span></span></label>
<label class="choice"><input id="openbao_emergency_lockdown_drilled" type="checkbox"><span><strong>Emergency lock-down drill recorded</strong><span>Railiance OpenBao was sealed and status-confirmed during a drill or real lock-down. No token or share is recorded here.</span></span></label>
</div>
<div id="runbook-command-list" class="command-list"></div>
</details>
<details class="panel workflow-section" data-section="artifacts" open> <details class="panel workflow-section" data-section="artifacts" open>
<summary><span class="summary-title">5. Artefacts & Locations</span><span class="state nil" data-section-state="artifacts">nil</span></summary> <summary><span class="summary-title">5. Artefacts & Locations</span><span class="state nil" data-section-state="artifacts">nil</span></summary>
<div class="section-gate" data-section-gate="artifacts">Loading artefact gate.</div> <div class="section-gate" data-section-gate="artifacts">Loading artefact gate.</div>
@@ -2622,6 +2476,20 @@ def ui_html() -> str:
</div> </div>
</details> </details>
<details class="panel workflow-section" data-section="runbooks" open>
<summary><span class="summary-title">6. Usecases & Runbooks</span><span class="state nil" data-section-state="runbooks">nil</span></summary>
<div class="section-gate" data-section-gate="runbooks">Loading runbook gate.</div>
<p class="notice">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.</p>
<div id="runbooks-records" class="record-list"></div>
<div class="choice-list">
<label class="choice"><input id="openbao_trial_material_exposed" type="checkbox"><span><strong>Trial key material exposed</strong><span>Init output, unseal shares, or root-token material escaped the custody boundary during a trial.</span></span></label>
<label class="choice"><input id="openbao_compromise_response_complete" type="checkbox"><span><strong>Compromise response complete</strong><span>Exposed material was rotated or the trial environment was reset. No secret values are recorded here.</span></span></label>
<label class="choice"><input id="openbao_unseal_keys_rotated" type="checkbox"><span><strong>New unseal keys generated</strong><span>OpenBao generated replacement unseal shares under the current runbook.</span></span></label>
<label class="choice"><input id="openbao_emergency_lockdown_drilled" type="checkbox"><span><strong>Emergency lock-down drill recorded</strong><span>Railiance OpenBao was sealed and status-confirmed during a drill or real lock-down. No token or share is recorded here.</span></span></label>
</div>
<div id="runbook-command-list" class="command-list"></div>
</details>
<details class="panel workflow-section" data-section="terminology" open> <details class="panel workflow-section" data-section="terminology" open>
<summary><span class="summary-title">7. Terminology & Patterns</span><span class="state nil" data-section-state="terminology">nil</span></summary> <summary><span class="summary-title">7. Terminology & Patterns</span><span class="state nil" data-section-state="terminology">nil</span></summary>
<div class="section-gate" data-section-gate="terminology">Loading terminology gate.</div> <div class="section-gate" data-section-gate="terminology">Loading terminology gate.</div>
@@ -2842,10 +2710,13 @@ def ui_html() -> str:
const description = document.createElement("div"); const description = document.createElement("div");
description.className = "record-description"; description.className = "record-description";
description.textContent = item.description; description.textContent = item.description;
const statusReason = document.createElement("div"); title.append(name, description);
statusReason.className = "record-description"; if (item.status_reason) {
statusReason.textContent = item.status_reason || ""; const statusReason = document.createElement("div");
title.append(name, description, statusReason); statusReason.className = "record-description";
statusReason.textContent = item.status_reason;
title.append(statusReason);
}
const taintNote = makeTaintNote(item); const taintNote = makeTaintNote(item);
if (taintNote) title.append(taintNote); if (taintNote) title.append(taintNote);
@@ -2857,7 +2728,8 @@ def ui_html() -> str:
button.dataset.command = item.command; button.dataset.command = item.command;
const commandActions = document.createElement("div"); const commandActions = document.createElement("div");
commandActions.className = "inline-actions"; 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); head.append(title, commandActions);
const code = document.createElement("code"); const code = document.createElement("code");

View File

@@ -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, recipient, complete an isolated restore proof, rerun post-unseal verification,
and record only non-secret completion evidence. 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 **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 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 the custodian public age recipient, a derived fingerprint, and a non-secret