diff --git a/tools/security-bootstrap-console/security_bootstrap_console.py b/tools/security-bootstrap-console/security_bootstrap_console.py index e1f6129..181eeed 100755 --- a/tools/security-bootstrap-console/security_bootstrap_console.py +++ b/tools/security-bootstrap-console/security_bootstrap_console.py @@ -594,6 +594,7 @@ def merged_approval_metadata( "openbao_trial_material_exposed", "openbao_compromise_response_complete", "openbao_unseal_keys_rotated", + "openbao_emergency_lockdown_drilled", "restore_drill_passed", ): if field in payload: @@ -811,6 +812,7 @@ def metadata_template() -> dict[str, Any]: "openbao_trial_material_exposed": False, "openbao_compromise_response_complete": False, "openbao_unseal_keys_rotated": False, + "openbao_emergency_lockdown_drilled": False, "root_token_disposition": "", "restore_drill_passed": False, "cleanup_complete": False, @@ -1339,6 +1341,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]: 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") @@ -1357,6 +1360,12 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]: 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 = "Requires an unsealed OpenBao instance and a token with root or sudo on sys/seal." + if not initialized: + lockdown_status = "blocked" + lockdown_location = "OpenBao is not recorded as unsealed; sealing is only meaningful while it is serving requests." + return [ add_taint( { @@ -1382,6 +1391,18 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]: }, openbao_downstream_taint if trial_exposed and not response_complete else {}, ), + add_taint( + { + "name": "Emergency lock-down", + "description": "Seal Railiance OpenBao to stop access to stored OpenBao assets until a later unseal quorum reopens it.", + "subsystem": "Railiance OpenBao", + "responsibility": "openbao-ceremony-operator", + "email": role_email(data, "role_openbao_operator_email"), + "location": lockdown_location, + "state": lockdown_status, + }, + openbao_downstream_taint if initialized else {}, + ), ] @@ -1391,6 +1412,7 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: 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") @@ -1418,6 +1440,20 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: 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" + ) + return [ add_taint( { @@ -1479,6 +1515,36 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: }, openbao_downstream_taint if trial_exposed and not response_complete else {}, ), + 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 {}, + ), + 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 {}, + ), + 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 {}, + ), ] @@ -1490,24 +1556,36 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]: runbook_rows = runbook_payloads(data) artifact_rows = artifact_payloads(data) return [ + { + "key": "intro", + "name": "Introduction & Actors", + "status": "ok", + "reason": "NetKingdom purpose, global actors, and supported custody shapes are visible.", + }, + { + "key": "subsystems", + "name": "Subsystems & Scopes", + "status": "ok" if all(row["state"] in {"ok", "set"} for row in subsystem_rows) else "err", + "reason": "Subsystems have install/access evidence." if all(row["state"] != "nil" for row in subsystem_rows) else "Complete subsystem setup fields and confirmations.", + }, { "key": "roles", "name": "Roles & Responsibilities", "status": "ok" if role_ok else "set", "reason": "All active bootstrap roles have a designated email." if role_ok else "Assign an email to each active bootstrap role.", }, - { - "key": "subsystems", - "name": "Subsystems & Scope", - "status": "ok" if all(row["state"] in {"ok", "set"} for row in subsystem_rows) else "err", - "reason": "Subsystems have install/access evidence." if all(row["state"] != "nil" for row in subsystem_rows) else "Complete subsystem setup fields and confirmations.", - }, { "key": "integrations", "name": "Integration & Tests", "status": "ok" if all(row["state"] == "ok" for row in integration_rows[:4]) else "set", "reason": "Identity and OpenBao preflight checks are done." if all(row["state"] == "ok" for row in integration_rows[:4]) else "Run or confirm the remaining integration checks.", }, + { + "key": "artifacts", + "name": "Artefacts & Locations", + "status": "ok" if all(row["state"] != "nil" for row in artifact_rows[:10]) else "set", + "reason": "Core custody artefacts have locations or confirmations." if all(row["state"] != "nil" for row in artifact_rows[:10]) else "Record missing artefact locations and confirmations.", + }, { "key": "runbooks", "name": "Usecases & Runbooks", @@ -1515,10 +1593,10 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]: "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.", }, { - "key": "artifacts", - "name": "Artefacts & Locations", - "status": "ok" if all(row["state"] != "nil" for row in artifact_rows[:10]) else "set", - "reason": "Core custody artefacts have locations or confirmations." if all(row["state"] != "nil" for row in artifact_rows[:10]) else "Record missing artefact locations and confirmations.", + "key": "terminology", + "name": "Terminology & Patterns", + "status": "ok", + "reason": "Shared NetKingdom security terms and patterns are documented for operators.", }, ] @@ -1815,6 +1893,18 @@ def ui_html() -> str: background: var(--paper); padding: 18px; } + #approval-form { + display: flex; + flex-direction: column; + } + .workflow-section[data-section="intro"] { order: 1; } + .workflow-section[data-section="subsystems"] { order: 2; } + .workflow-section[data-section="roles"] { order: 3; } + .workflow-section[data-section="integrations"] { order: 4; } + .workflow-section[data-section="artifacts"] { order: 5; } + .workflow-section[data-section="runbooks"] { order: 6; } + .workflow-section[data-section="terminology"] { order: 7; } + .workflow-actions { order: 8; } .panel + .panel { margin-top: 18px; } h2 { margin: 0 0 14px; @@ -2179,8 +2269,26 @@ def ui_html() -> str:
These terms apply across NetKingdom. Subsystems may have their own names, but the control surface keeps the cross-subsystem security pattern visible.
+