diff --git a/tools/security-bootstrap-console/security_bootstrap_console.py b/tools/security-bootstrap-console/security_bootstrap_console.py index f941c13..d228b55 100755 --- a/tools/security-bootstrap-console/security_bootstrap_console.py +++ b/tools/security-bootstrap-console/security_bootstrap_console.py @@ -28,7 +28,7 @@ from typing import Any DEFAULT_STAGE = "S1 - Low-trust assembly" -STAGE_ORDER = ("S1", "S2", "S3", "S4", "S5") +STAGE_ORDER = ("S1", "S2", "S3", "S4", "S5", "S6") DEFAULT_METADATA_PATH = Path("/tmp/net-kingdom-security-bootstrap.json") APPROVAL_PHRASE = "approve custody mode" VALID_STORAGE_CLASSES = {"password-safe", "offline-packet", "hardware-token"} @@ -351,13 +351,11 @@ def custody_mode_reason(data: dict[str, Any]) -> str: def derive_stage(data: dict[str, Any]) -> str: if yes(data, "platform_reopened"): - return "S5 - Reopen under custody" - if ( - yes(data, "openbao_initial_config_applied") - and data.get("root_token_disposition") in {"revoked", "offline-sealed"} - and yes(data, "restore_drill_passed") - ): - return "S4 - Cleanup and hardening" + return "S6 - Reopen under custody" + if yes(data, "cleanup_complete") or yes(data, "openbao_oidc_admin_login_verified"): + return "S5 - Cleanup and hardening" + if yes(data, "openbao_initial_config_applied"): + return "S4 - Admin identity integration" if yes(data, "openbao_initialized"): return "S3 - OpenBao bootstrap" if yes(data, "king_credential_ready") or king_kit_ready(data): @@ -387,17 +385,23 @@ def stage_payloads(data: dict[str, Any]) -> list[dict[str, str]]: ( "S3", "OpenBao bootstrap", - "Initialize, unseal, configure OpenBao, then decide root-token disposition and prove restore.", - "Record root-token disposition and pass the restore drill.", + "Initialize, unseal, rotate trial material, configure OpenBao, and create a temporary admin bridge.", + "Bind normal OpenBao admin access to NetKingdom identity.", ), ( "S4", - "Cleanup and hardening", - "Rotate or retire bootstrap-era access, resolve taint, review audit, and document residual risk.", - "Complete cleanup and mark the platform ready to reopen.", + "Admin identity integration", + "Register OpenBao with KeyCape and map verified NetKingdom admin claims to the platform-admin policy.", + "Verify OIDC-backed OpenBao admin login.", ), ( "S5", + "Cleanup and hardening", + "Retire bootstrap-era access, resolve taint, record root-token disposition, prove restore, and document residual risk.", + "Complete cleanup and mark the platform ready to reopen.", + ), + ( + "S6", "Reopen under custody", "Operate under the approved custody model with break-glass and recovery paths known.", "Review related workplans.", @@ -455,6 +459,21 @@ def build_gates(data: dict[str, Any]) -> list[Gate]: ), "Apply first auth, mount, and policy configuration; audit may be a declarative follow-up.", ), + Gate( + "KeyCape OpenBao client", + "done" if yes(data, "openbao_oidc_client_registered") else "blocked", + "Dedicated OpenBao OIDC client is registered in KeyCape with exact redirect URIs.", + ), + Gate( + "OpenBao OIDC auth", + "done" if yes(data, "openbao_oidc_auth_configured") else "blocked", + "OpenBao OIDC/JWT auth is configured against KeyCape and maps claims to policy.", + ), + Gate( + "OIDC admin login", + "done" if yes(data, "openbao_oidc_admin_login_verified") else "blocked", + "platform-root can obtain OpenBao platform-admin access through KeyCape/MFA.", + ), Gate( "Root-token disposition", "done" if data.get("root_token_disposition") in {"revoked", "offline-sealed"} else "blocked", @@ -519,6 +538,12 @@ def next_action( return "Approve custody strategy" if gate.name == "OpenBao preflight": return "Run OpenBao preflight" + if gate.name == "KeyCape OpenBao client": + return "Register OpenBao OIDC client" + if gate.name == "OpenBao OIDC auth": + return "Configure OpenBao OIDC auth" + if gate.name == "OIDC admin login": + return "Verify OpenBao OIDC admin login" if gate.name == "Root-token disposition": return "Record root-token disposition" if gate.name == "Restore drill": @@ -665,6 +690,9 @@ def merged_approval_metadata( "openbao_compromise_response_complete", "openbao_unseal_keys_rotated", "openbao_emergency_lockdown_drilled", + "openbao_oidc_client_registered", + "openbao_oidc_auth_configured", + "openbao_oidc_admin_login_verified", "restore_drill_passed", "cleanup_complete", "platform_reopened", @@ -885,6 +913,9 @@ def metadata_template() -> dict[str, Any]: "openbao_compromise_response_complete": False, "openbao_unseal_keys_rotated": False, "openbao_emergency_lockdown_drilled": False, + "openbao_oidc_client_registered": False, + "openbao_oidc_auth_configured": False, + "openbao_oidc_admin_login_verified": False, "root_token_disposition": "", "restore_drill_passed": False, "cleanup_complete": False, @@ -1154,6 +1185,48 @@ def integration_payloads(data: dict[str, Any]) -> list[dict[str, str]]: ] +def admin_identity_payloads(data: dict[str, Any]) -> list[dict[str, str]]: + downstream_taint = openbao_trial_taint(data, "downstream") if yes(data, "openbao_initialized") else {} + return [ + add_taint( + { + "name": "OpenBao OIDC client", + "description": "Dedicated KeyCape client for OpenBao admin login with exact localhost callback URIs.", + "subsystem": "KeyCape", + "responsibility": "identity-admin", + "email": role_email(data, "role_identity_admin_email"), + "location": "clientId=openbao-admin; scopes=openid profile email groups", + "state": state_value(yes(data, "openbao_oidc_client_registered"), yes(data, "openbao_initial_config_applied")), + }, + downstream_taint, + ), + add_taint( + { + "name": "OpenBao OIDC auth method", + "description": "OpenBao trusts KeyCape discovery and maps the net-kingdom-admins group to the platform-admin policy.", + "subsystem": "Railiance OpenBao", + "responsibility": "openbao-ceremony-operator", + "email": role_email(data, "role_openbao_operator_email"), + "location": "auth/keycape or equivalent OpenBao OIDC/JWT mount", + "state": state_value(yes(data, "openbao_oidc_auth_configured"), yes(data, "openbao_oidc_client_registered")), + }, + downstream_taint, + ), + add_taint( + { + "name": "OIDC admin login", + "description": "platform-root can obtain OpenBao platform-admin access through KeyCape MFA instead of a manually minted token.", + "subsystem": "KeyCape -> OpenBao", + "responsibility": "platform-root-custodian", + "email": role_email(data, "role_platform_custodian_email"), + "location": "bao login -method=oidc -path=keycape role=platform-admin", + "state": state_value(yes(data, "openbao_oidc_admin_login_verified"), yes(data, "openbao_oidc_auth_configured")), + }, + downstream_taint, + ), + ] + + def artifact_payloads(data: dict[str, Any]) -> list[dict[str, str]]: public_key = extract_age_public_key(data.get("custodian_age_public_key")) state = bootstrap_secret_state() @@ -1675,6 +1748,7 @@ 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) + admin_rows = admin_identity_payloads(data) artifact_rows = artifact_payloads(data) cleanup_done = yes(data, "cleanup_complete") reopened = yes(data, "platform_reopened") @@ -1703,6 +1777,12 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]: "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": "admin-identity", + "name": "Admin Identity Integration", + "status": "ok" if all(row["state"] == "ok" for row in admin_rows) else "set", + "reason": "OpenBao admin access is bound to NetKingdom OIDC claims." if all(row["state"] == "ok" for row in admin_rows) else "Configure and verify the KeyCape-backed OpenBao admin path.", + }, { "key": "artifacts", "name": "Artefacts & Locations", @@ -1766,6 +1846,7 @@ def status_payload(data: dict[str, Any], metadata_path: Path) -> dict[str, Any]: "roles": role_payloads(merged), "subsystems": subsystem_payloads(merged), "integrations": integration_payloads(merged), + "admin_identity_integrations": admin_identity_payloads(merged), "runbooks": runbook_payloads(merged), "artifacts": artifact_payloads(merged), "commands": command_payloads(merged), @@ -2018,7 +2099,7 @@ def ui_html() -> str: } .stage-rail { display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); + grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 10px; margin-top: 14px; } @@ -2073,11 +2154,12 @@ def ui_html() -> str: .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="handover"] { order: 7; } - .workflow-section[data-section="terminology"] { order: 8; } - .workflow-actions { order: 9; } + .workflow-section[data-section="admin-identity"] { order: 5; } + .workflow-section[data-section="artifacts"] { order: 6; } + .workflow-section[data-section="runbooks"] { order: 7; } + .workflow-section[data-section="handover"] { order: 8; } + .workflow-section[data-section="terminology"] { order: 9; } + .workflow-actions { order: 10; } .panel + .panel { margin-top: 18px; } h2 { margin: 0 0 14px; @@ -2619,8 +2701,25 @@ def ui_html() -> str:
+This stage replaces manually minted OpenBao admin tokens as the normal path. KeyCape remains the identity issuer; OpenBao maps verified NetKingdom group claims to the existing platform-admin policy.
+ +openbao-admin with exact localhost OpenBao callback URIs and the groups scope.https://kc.coulomb.social and bind net-kingdom-admins to platform-admin.platform-root can complete MFA-backed OpenBao login before root-token disposition and cleanup.This is the final overview of what has been established. Locations are references only; passwords, OTP seeds, age private keys, unseal shares, and root tokens are never recorded here.
@@ -2648,7 +2747,7 @@ def ui_html() -> str: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.
@@ -2662,7 +2761,7 @@ def ui_html() -> str:This is the line between trial/bootstrap and operating under custody. Mark these only after root-token disposition, restore proof, taint response, and cleanup have been handled outside this UI.
These terms apply across NetKingdom. Subsystems may have their own names, but the control surface keeps the cross-subsystem security pattern visible.