diff --git a/tools/security-bootstrap-console/security_bootstrap_console.py b/tools/security-bootstrap-console/security_bootstrap_console.py index fb0e495..8f86cfc 100755 --- a/tools/security-bootstrap-console/security_bootstrap_console.py +++ b/tools/security-bootstrap-console/security_bootstrap_console.py @@ -1466,6 +1466,29 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: "unset OPENBAO_TOKEN" ) + def interactive_token_command(bao_command: str, prompt_nonce: bool = False) -> str: + nonce_prompt = ( + ' printf "Rotation nonce: " >&2\n' + ' read -r ROTATION_NONCE\n' + if prompt_nonce + else "" + ) + return ( + "kubectl exec -it -n openbao openbao-0 -- sh -lc '\n" + " restore_tty() { stty echo 2>/dev/null || true; }\n" + " trap restore_tty EXIT INT TERM\n" + f"{nonce_prompt}" + " printf \"OpenBao token: \" >&2\n" + " stty -echo\n" + " read -r BAO_TOKEN\n" + " stty echo\n" + " printf \"\\n\" >&2\n" + " export BAO_TOKEN\n" + f" {bao_command}\n" + " unset BAO_TOKEN\n" + "'" + ) + def action(name: str, description: str, command: str, taint: dict[str, str] | None = None) -> dict[str, str]: return add_taint( { @@ -1480,6 +1503,14 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: audit_list_command = token_prompt_command("bao audit list") secrets_list_command = token_prompt_command("bao secrets list") auth_list_command = token_prompt_command("bao auth list") + rotate_init_command = interactive_token_command( + "bao operator rotate-keys -init -key-shares=3 -key-threshold=2" + ) + rotate_submit_command = interactive_token_command( + 'bao operator rotate-keys -nonce="$ROTATION_NONCE"', + prompt_nonce=True, + ) + rotate_cancel_command = interactive_token_command("bao operator rotate-keys -cancel") openbao_status_command = "kubectl exec -n openbao openbao-0 -- bao status" direct_taint = openbao_direct_taint if initialized else {} downstream_taint = openbao_downstream_taint if initialized else {} @@ -1562,20 +1593,20 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]: ), action( "Start unseal-key rotation", - "Generate a new 3-share, threshold-2 Shamir split after compromise or planned migration.", - "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -init -key-shares=3 -key-threshold=2", + "Generate a new 3-share, threshold-2 Shamir split. Requires an OpenBao token with root or sudo capability.", + rotate_init_command, compromise_taint, ), action( "Submit current shares for rotation", - "Repeat by prompt until the required threshold completes. Use the nonce from rotation init.", - "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -nonce=", + "Repeat by prompt until the required threshold completes. Enter the nonce from rotation init, then the token and share prompts.", + rotate_submit_command, compromise_taint, ), action( "Cancel key rotation", - "Abort a started rotation if the nonce, share handling, or ceremony context is wrong.", - "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -cancel", + "Abort a started rotation if the nonce, share handling, or ceremony context is wrong. Requires a root/sudo-capable token.", + rotate_cancel_command, compromise_taint, ), action( diff --git a/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md b/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md index 28a287e..e564355 100644 --- a/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md +++ b/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md @@ -284,6 +284,12 @@ state. The stage model now moves from S3 to S4 after OpenBao initial configuration, root-token disposition, and restore drill are complete, then to S5 only when the platform is explicitly reopened under custody. +**2026-05-25:** Corrected the OpenBao rotate-keys action cards after the +operator hit `permission denied` on rotation init. The rotation commands now +open an interactive pod TTY, prompt there for a root/sudo-capable OpenBao +token, keep the token out of the local command line, and then run rotate init, +share submission, or cancel. + **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 the custodian public age recipient, a derived fingerprint, and a non-secret