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:
+