diff --git a/tools/security-bootstrap-console/security_bootstrap_console.py b/tools/security-bootstrap-console/security_bootstrap_console.py index 60a7936..e1f6129 100755 --- a/tools/security-bootstrap-console/security_bootstrap_console.py +++ b/tools/security-bootstrap-console/security_bootstrap_console.py @@ -381,6 +381,17 @@ def build_gates(data: dict[str, Any]) -> list[Gate]: "human" if not yes(data, "openbao_initialized") else "done", "Human-attended ceremony only. This console will not run init.", ), + Gate( + "OpenBao initial configuration", + ( + "done" + if yes(data, "openbao_initial_config_applied") + else "human" + if yes(data, "openbao_initialized") + else "blocked" + ), + "Apply first auth, mount, and policy configuration; audit may be a declarative follow-up.", + ), Gate( "Root-token disposition", "done" if data.get("root_token_disposition") in {"revoked", "offline-sealed"} else "blocked", @@ -579,6 +590,7 @@ def merged_approval_metadata( "openbao_init_output_produced", "openbao_initialized", "openbao_post_unseal_verified", + "openbao_initial_config_applied", "openbao_trial_material_exposed", "openbao_compromise_response_complete", "openbao_unseal_keys_rotated", @@ -795,6 +807,7 @@ def metadata_template() -> dict[str, Any]: "openbao_init_output_produced": False, "openbao_initialized": False, "openbao_post_unseal_verified": False, + "openbao_initial_config_applied": False, "openbao_trial_material_exposed": False, "openbao_compromise_response_complete": False, "openbao_unseal_keys_rotated": False, @@ -1052,6 +1065,18 @@ def integration_payloads(data: dict[str, Any]) -> list[dict[str, str]]: }, openbao_direct_taint, ), + add_taint( + { + "name": "OpenBao initial configuration", + "description": "First auth, mount, and policy configuration after unseal.", + "subsystem": "Railiance OpenBao", + "responsibility": "openbao-ceremony-operator", + "email": role_email(data, "role_openbao_operator_email"), + "location": "../railiance-platform openbao-configure-initial", + "state": state_value(yes(data, "openbao_initial_config_applied"), yes(data, "openbao_initialized")), + }, + openbao_trial_taint(data, "downstream") if yes(data, "openbao_initialized") else {}, + ), ] @@ -1185,6 +1210,7 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: init_output = yes(data, "openbao_init_output_produced") initialized = yes(data, "openbao_initialized") post_unseal_verified = yes(data, "openbao_post_unseal_verified") + 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") @@ -1225,12 +1251,15 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: unseal_state = "blocked" unseal_reason = "OpenBao init output must be produced first." - config_state = "done" if root_disposed else "todo" - config_reason = "Initial configuration and root-token disposition are recorded." - if not root_disposed: - config_reason = "Configure OpenBao, then revoke or offline-seal the root token." + 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: + config_reason = "Configure OpenBao, then record this non-secret completion flag." if trial_exposed and initialized and not response_complete: - config_reason = "Tainted by trial key-material exposure. Operator may proceed, but record the taint and complete rotation, reset, or another compromise response before production trust." + if initial_config_applied: + config_reason = "Initial configuration is recorded on a tainted workpath. Complete root-token disposition and compromise response before production trust." + else: + config_reason = "Tainted by trial key-material exposure. Operator may proceed, but record the taint and complete rotation, reset, or another compromise response before production trust." if not initialized: config_state = "blocked" config_reason = "OpenBao must be initialized and unsealed first." @@ -2293,6 +2322,7 @@ def ui_html() -> str: +