generated from coulomb/repo-seed
Add OpenBao compromise runbooks to bootstrap UI
This commit is contained in:
@@ -84,7 +84,9 @@ The web UI is structured as:
|
||||
privacyIDEA, KeyCape, the custodian age envelope, and Railiance OpenBao.
|
||||
3. **Integration & Tests** - OIDC and OpenBao preflight checks, with every
|
||||
operator command shown as a copyable console block.
|
||||
4. **Artefacts & Locations** - final non-secret overview of established
|
||||
4. **Usecases & Runbooks** - guided routines for key-material compromise,
|
||||
trial-output exposure, and generating replacement unseal keys.
|
||||
5. **Artefacts & Locations** - final non-secret overview of established
|
||||
artefacts and where to find their custody references.
|
||||
|
||||
Role, subsystem, integration, and artefact records use the same fields:
|
||||
@@ -99,6 +101,11 @@ role metadata and Cancel restores the last loaded values. Command cards use
|
||||
available, needs to be run, should be repeated after a state change, or has
|
||||
already succeeded.
|
||||
|
||||
The **Key material compromised** runbook is also useful for trial ceremonies:
|
||||
mark the trial output as exposed, stop treating the generated unseal shares or
|
||||
root token as production material, then either rotate unseal keys after unseal
|
||||
or reset the trial environment before any live secrets are migrated.
|
||||
|
||||
The UI is a guide and approval surface, not the identity provider. Current
|
||||
lightweight-mode credential placement is:
|
||||
|
||||
|
||||
@@ -419,10 +419,16 @@ def kit_next_action(kit_gates: list[Gate]) -> str:
|
||||
return "Define king credential kit"
|
||||
|
||||
|
||||
def next_action(gates: list[Gate], kit_gates: list[Gate] | None = None) -> str:
|
||||
def next_action(
|
||||
gates: list[Gate],
|
||||
kit_gates: list[Gate] | None = None,
|
||||
data: dict[str, Any] | None = None,
|
||||
) -> str:
|
||||
for gate in gates:
|
||||
if gate.status == "human":
|
||||
if gate.name == "OpenBao init ceremony":
|
||||
if data and yes(data, "openbao_init_output_produced") and not yes(data, "openbao_initialized"):
|
||||
return "Run OpenBao unseal prompt"
|
||||
return "Run attended OpenBao init ceremony"
|
||||
return gate.name
|
||||
if gate.status == "blocked":
|
||||
@@ -456,7 +462,7 @@ def print_status(data: dict[str, Any]) -> None:
|
||||
print(derive_stage(merged))
|
||||
print("")
|
||||
print("Next safe action")
|
||||
print(next_action(gates, kit_gates))
|
||||
print(next_action(gates, kit_gates, merged))
|
||||
print("")
|
||||
print("Key custody")
|
||||
public_key = extract_age_public_key(merged.get("custodian_age_public_key"))
|
||||
@@ -570,7 +576,11 @@ def merged_approval_metadata(
|
||||
"oidc_login_verified",
|
||||
"password_safe_confirmed",
|
||||
"openbao_preflight_passed",
|
||||
"openbao_init_output_produced",
|
||||
"openbao_initialized",
|
||||
"openbao_trial_material_exposed",
|
||||
"openbao_compromise_response_complete",
|
||||
"openbao_unseal_keys_rotated",
|
||||
"restore_drill_passed",
|
||||
):
|
||||
if field in payload:
|
||||
@@ -781,7 +791,11 @@ def metadata_template() -> dict[str, Any]:
|
||||
"metadata_updated_at": "",
|
||||
"progress_scope": "",
|
||||
"openbao_preflight_passed": False,
|
||||
"openbao_init_output_produced": False,
|
||||
"openbao_initialized": False,
|
||||
"openbao_trial_material_exposed": False,
|
||||
"openbao_compromise_response_complete": False,
|
||||
"openbao_unseal_keys_rotated": False,
|
||||
"root_token_disposition": "",
|
||||
"restore_drill_passed": False,
|
||||
"cleanup_complete": False,
|
||||
@@ -1017,6 +1031,7 @@ 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()
|
||||
root_disposition = str(data.get("root_token_disposition") or "")
|
||||
init_output = yes(data, "openbao_init_output_produced")
|
||||
return [
|
||||
{
|
||||
"name": "platform-root",
|
||||
@@ -1115,7 +1130,7 @@ def artifact_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
"responsibility": "openbao-ceremony-operator",
|
||||
"email": role_email(data, "role_openbao_operator_email"),
|
||||
"location": "created during attended init; not stored here",
|
||||
"state": state_value(yes(data, "openbao_initialized"), yes(data, "openbao_preflight_passed")),
|
||||
"state": state_value(yes(data, "openbao_unseal_keys_rotated"), init_output or yes(data, "openbao_initialized")),
|
||||
},
|
||||
{
|
||||
"name": "initial root token",
|
||||
@@ -1124,7 +1139,7 @@ def artifact_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
"responsibility": "openbao-ceremony-operator",
|
||||
"email": role_email(data, "role_openbao_operator_email"),
|
||||
"location": "created during attended init; never pasted here",
|
||||
"state": state_value(root_disposition in {"revoked", "offline-sealed"}, yes(data, "openbao_initialized")),
|
||||
"state": state_value(root_disposition in {"revoked", "offline-sealed"}, init_output or yes(data, "openbao_initialized")),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1132,7 +1147,10 @@ def artifact_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
preflight_done = yes(data, "openbao_preflight_passed")
|
||||
custody_approved = custody_mode_approved(data)
|
||||
init_output = yes(data, "openbao_init_output_produced")
|
||||
initialized = yes(data, "openbao_initialized")
|
||||
trial_exposed = yes(data, "openbao_trial_material_exposed")
|
||||
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
|
||||
root_disposed = data.get("root_token_disposition") in {"revoked", "offline-sealed"}
|
||||
restore_done = yes(data, "restore_drill_passed")
|
||||
|
||||
@@ -1141,7 +1159,7 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
if preflight_done:
|
||||
status_state = "done"
|
||||
status_reason = "Deployment and pre-init status were verified."
|
||||
if initialized and not root_disposed:
|
||||
if (init_output or initialized) and not root_disposed:
|
||||
status_state = "redo"
|
||||
status_reason = "OpenBao changed during init/unseal; rerun status before root-token disposition."
|
||||
|
||||
@@ -1153,21 +1171,33 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
preflight_state = "blocked"
|
||||
preflight_reason = "Approve the selected custody strategy first."
|
||||
|
||||
ceremony_state = "done" if initialized else "todo"
|
||||
ceremony_reason = "Init/unseal ceremony has been recorded."
|
||||
if not initialized:
|
||||
ceremony_reason = "Run once, attended, after OpenBao preflight."
|
||||
init_state = "done" if init_output or initialized else "todo"
|
||||
init_reason = "Init output was produced. Do not paste unseal shares or root token here."
|
||||
if not (init_output or initialized):
|
||||
init_reason = "Run once, attended, after OpenBao preflight."
|
||||
if not preflight_done:
|
||||
ceremony_state = "blocked"
|
||||
ceremony_reason = "OpenBao preflight must pass first."
|
||||
init_state = "blocked"
|
||||
init_reason = "OpenBao preflight must pass 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."
|
||||
unseal_state = "done" if initialized else "todo"
|
||||
unseal_reason = "OpenBao is recorded as initialized and unsealed."
|
||||
if not initialized:
|
||||
unseal_reason = "Provide threshold shares by prompt, not as command arguments."
|
||||
if not (init_output or initialized):
|
||||
unseal_state = "blocked"
|
||||
unseal_reason = "OpenBao init output must be produced first."
|
||||
|
||||
if trial_exposed and initialized and not keys_rotated:
|
||||
config_state = "blocked"
|
||||
config_reason = "OpenBao must be initialized and unsealed first."
|
||||
config_reason = "Trial key material is exposed; rotate unseal keys or reset before configuration."
|
||||
else:
|
||||
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."
|
||||
if not initialized:
|
||||
config_state = "blocked"
|
||||
config_reason = "OpenBao must be initialized and unsealed first."
|
||||
|
||||
verify_state = "done" if restore_done else "todo"
|
||||
verify_reason = "Restore proof has been recorded."
|
||||
@@ -1198,15 +1228,15 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
{
|
||||
"name": "OpenBao init ceremony",
|
||||
"description": "Creates real unseal shares and the initial root token. Run once, attended.",
|
||||
"status": ceremony_state,
|
||||
"status_reason": ceremony_reason,
|
||||
"status": init_state,
|
||||
"status_reason": init_reason,
|
||||
"command": "kubectl exec -n openbao openbao-0 -- bao operator init -key-shares=3 -key-threshold=2",
|
||||
},
|
||||
{
|
||||
"name": "OpenBao unseal prompt",
|
||||
"description": "Enter unseal shares by prompt. Do not place shares on the command line.",
|
||||
"status": ceremony_state,
|
||||
"status_reason": ceremony_reason,
|
||||
"status": unseal_state,
|
||||
"status_reason": unseal_reason,
|
||||
"command": "kubectl exec -n openbao openbao-0 -- bao operator unseal",
|
||||
},
|
||||
{
|
||||
@@ -1226,11 +1256,133 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
]
|
||||
|
||||
|
||||
def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
init_output = yes(data, "openbao_init_output_produced")
|
||||
initialized = yes(data, "openbao_initialized")
|
||||
trial_exposed = yes(data, "openbao_trial_material_exposed")
|
||||
response_complete = yes(data, "openbao_compromise_response_complete")
|
||||
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
|
||||
|
||||
key_compromise_status = "done" if response_complete else "todo"
|
||||
key_compromise_location = "Use for trial output exposure, screenshots, chat paste, shell history, or lost custody."
|
||||
if not trial_exposed:
|
||||
key_compromise_status = "blocked" if not init_output else "todo"
|
||||
key_compromise_location = "Mark trial key material exposed before running the response checklist."
|
||||
|
||||
rotate_status = "done" if keys_rotated else "todo"
|
||||
rotate_location = "Run only after OpenBao is unsealed and existing exposed shares are available for quorum."
|
||||
if not initialized:
|
||||
rotate_status = "blocked"
|
||||
rotate_location = "Unseal OpenBao first; rotate-keys needs a quorum of current unseal shares."
|
||||
if not trial_exposed and not keys_rotated:
|
||||
rotate_status = "blocked"
|
||||
rotate_location = "Record the key-compromise condition or schedule a normal rotation first."
|
||||
|
||||
return [
|
||||
{
|
||||
"name": "Key material compromised",
|
||||
"description": "Respond when init output, unseal shares, or root-token material escaped the custody boundary.",
|
||||
"subsystem": "Railiance OpenBao",
|
||||
"responsibility": "openbao-ceremony-operator",
|
||||
"email": role_email(data, "role_openbao_operator_email"),
|
||||
"location": key_compromise_location,
|
||||
"state": key_compromise_status,
|
||||
},
|
||||
{
|
||||
"name": "Generate new unseal keys",
|
||||
"description": "Rotate OpenBao Shamir unseal shares after a trial exposure or planned custody migration.",
|
||||
"subsystem": "Railiance OpenBao",
|
||||
"responsibility": "openbao-ceremony-operator",
|
||||
"email": role_email(data, "role_openbao_operator_email"),
|
||||
"location": rotate_location,
|
||||
"state": rotate_status,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
init_output = yes(data, "openbao_init_output_produced")
|
||||
initialized = yes(data, "openbao_initialized")
|
||||
trial_exposed = yes(data, "openbao_trial_material_exposed")
|
||||
response_complete = yes(data, "openbao_compromise_response_complete")
|
||||
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
|
||||
|
||||
exposure_status = "done" if trial_exposed else "todo"
|
||||
exposure_reason = "Trial key-material exposure is recorded in non-secret metadata." if trial_exposed else "Record that the trial init output escaped custody before using affected material."
|
||||
|
||||
response_status = "done" if response_complete else "todo"
|
||||
response_reason = "Compromise response was recorded." if response_complete else "Stop production use of exposed material, decide rotate-vs-reset, and record non-secret evidence."
|
||||
if not trial_exposed:
|
||||
response_status = "blocked"
|
||||
response_reason = "Record the key-material exposure first."
|
||||
|
||||
unseal_status = "done" if initialized else "todo"
|
||||
unseal_reason = "OpenBao is unsealed." if initialized else "Unseal by hidden prompt before rotating unseal keys."
|
||||
if not init_output:
|
||||
unseal_status = "blocked"
|
||||
unseal_reason = "OpenBao init output must exist first."
|
||||
|
||||
rotate_status = "done" if keys_rotated else "todo"
|
||||
rotate_reason = "New unseal keys are recorded as generated." if keys_rotated else "Start rotation, then submit current shares by prompt until quorum completes."
|
||||
if not initialized:
|
||||
rotate_status = "blocked"
|
||||
rotate_reason = "OpenBao must be unsealed before rotate-keys can run."
|
||||
if not trial_exposed and not keys_rotated:
|
||||
rotate_status = "blocked"
|
||||
rotate_reason = "Record exposure or schedule a normal rotation before generating new shares."
|
||||
|
||||
return [
|
||||
{
|
||||
"name": "Record key exposure",
|
||||
"description": "Non-secret metadata checkbox in this UI; do not paste exposed values.",
|
||||
"status": exposure_status,
|
||||
"status_reason": exposure_reason,
|
||||
"command": "Use the checkbox: Trial key material exposed",
|
||||
},
|
||||
{
|
||||
"name": "Unseal by prompt",
|
||||
"description": "Provide threshold shares interactively. Never put shares on the command line.",
|
||||
"status": unseal_status,
|
||||
"status_reason": unseal_reason,
|
||||
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator unseal",
|
||||
},
|
||||
{
|
||||
"name": "Start unseal-key rotation",
|
||||
"description": "Generate a new 3-share, threshold-2 Shamir split after compromise or planned migration.",
|
||||
"status": rotate_status,
|
||||
"status_reason": rotate_reason,
|
||||
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -init -key-shares=3 -key-threshold=2",
|
||||
},
|
||||
{
|
||||
"name": "Submit current shares for rotation",
|
||||
"description": "Repeat by prompt until the required threshold completes. Use the nonce from rotation init.",
|
||||
"status": rotate_status,
|
||||
"status_reason": rotate_reason,
|
||||
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -nonce=<nonce-from-rotation-init>",
|
||||
},
|
||||
{
|
||||
"name": "Cancel key rotation",
|
||||
"description": "Abort a started rotation if the nonce, share handling, or ceremony context is wrong.",
|
||||
"status": "todo" if initialized and not keys_rotated else "blocked",
|
||||
"status_reason": "Available while a rotation is in progress." if initialized and not keys_rotated else "No active rotation expected.",
|
||||
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -cancel",
|
||||
},
|
||||
{
|
||||
"name": "Record compromise response complete",
|
||||
"description": "Non-secret metadata checkbox after exposed material is rotated or the trial environment is reset.",
|
||||
"status": response_status,
|
||||
"status_reason": response_reason,
|
||||
"command": "Use the checkbox: Compromise response complete",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
role_rows = role_payloads(data)
|
||||
role_ok = all(row["state"] != "nil" for row in role_rows[:5])
|
||||
subsystem_rows = subsystem_payloads(data)
|
||||
integration_rows = integration_payloads(data)
|
||||
runbook_rows = runbook_payloads(data)
|
||||
artifact_rows = artifact_payloads(data)
|
||||
return [
|
||||
{
|
||||
@@ -1251,6 +1403,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": "runbooks",
|
||||
"name": "Usecases & Runbooks",
|
||||
"status": "ok" if all(row["state"] in {"done", "blocked"} for row in runbook_rows) else "set",
|
||||
"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",
|
||||
@@ -1281,7 +1439,7 @@ def status_payload(data: dict[str, Any], metadata_path: Path) -> dict[str, Any]:
|
||||
return {
|
||||
"metadata_path": str(metadata_path),
|
||||
"stage": derive_stage(merged),
|
||||
"next_action": next_action(gates, kit_validation(merged)),
|
||||
"next_action": next_action(gates, kit_validation(merged), merged),
|
||||
"gates": [gate_payload(gate) for gate in gates],
|
||||
"key_custody_gates": [gate_payload(gate) for gate in key_custody_validation(merged)],
|
||||
"kit_gates": [gate_payload(gate) for gate in kit_validation(merged)],
|
||||
@@ -1289,8 +1447,10 @@ 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),
|
||||
"runbooks": runbook_payloads(merged),
|
||||
"artifacts": artifact_payloads(merged),
|
||||
"commands": command_payloads(merged),
|
||||
"runbook_commands": runbook_command_payloads(merged),
|
||||
"bootstrap_secret_state": bootstrap_secret_state(),
|
||||
"metadata": metadata_view,
|
||||
"approval_phrase": APPROVAL_PHRASE,
|
||||
@@ -2039,6 +2199,7 @@ def ui_html() -> str:
|
||||
<div class="choice-list" style="margin-top: 14px;">
|
||||
<label class="choice"><input id="oidc_login_verified" type="checkbox"><span><strong>OIDC login verified</strong><span>The account can complete the NetKingdom login path through KeyCape after MFA enrollment.</span></span></label>
|
||||
<label class="choice"><input id="openbao_preflight_passed" type="checkbox"><span><strong>OpenBao preflight passed</strong><span>Status and verification checks completed after custody approval.</span></span></label>
|
||||
<label class="choice"><input id="openbao_init_output_produced" type="checkbox"><span><strong>Init output produced</strong><span>OpenBao generated unseal shares and the initial root token outside this UI. Do not paste those values here.</span></span></label>
|
||||
<label class="choice"><input id="openbao_initialized" type="checkbox"><span><strong>Initialized and unsealed</strong><span>The human ceremony completed outside this UI under the approved strategy.</span></span></label>
|
||||
<label class="choice"><input id="restore_drill_passed" type="checkbox"><span><strong>Restore drill passed</strong><span>Snapshot and isolated restore proof completed before live secrets are migrated.</span></span></label>
|
||||
</div>
|
||||
@@ -2053,8 +2214,21 @@ def ui_html() -> str:
|
||||
<div id="command-list" class="command-list"></div>
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="runbooks" open>
|
||||
<summary><span class="summary-title">4. Usecases & Runbooks</span><span class="state nil" data-section-state="runbooks">nil</span></summary>
|
||||
<div class="section-gate" data-section-gate="runbooks">Loading runbook gate.</div>
|
||||
<p class="notice">Use these routines when the ceremony path changes, trial secrets are exposed, or custody material must be regenerated. The UI records only non-secret outcomes.</p>
|
||||
<div id="runbooks-records" class="record-list"></div>
|
||||
<div class="choice-list">
|
||||
<label class="choice"><input id="openbao_trial_material_exposed" type="checkbox"><span><strong>Trial key material exposed</strong><span>Init output, unseal shares, or root-token material escaped the custody boundary during a trial.</span></span></label>
|
||||
<label class="choice"><input id="openbao_compromise_response_complete" type="checkbox"><span><strong>Compromise response complete</strong><span>Exposed material was rotated or the trial environment was reset. No secret values are recorded here.</span></span></label>
|
||||
<label class="choice"><input id="openbao_unseal_keys_rotated" type="checkbox"><span><strong>New unseal keys generated</strong><span>OpenBao generated replacement unseal shares under the current runbook.</span></span></label>
|
||||
</div>
|
||||
<div id="runbook-command-list" class="command-list"></div>
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="artifacts" open>
|
||||
<summary><span class="summary-title">4. Artefacts & Locations</span><span class="state nil" data-section-state="artifacts">nil</span></summary>
|
||||
<summary><span class="summary-title">5. Artefacts & Locations</span><span class="state nil" data-section-state="artifacts">nil</span></summary>
|
||||
<div class="section-gate" data-section-gate="artifacts">Loading artefact gate.</div>
|
||||
<p class="notice">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.</p>
|
||||
<div id="artifacts-records" class="record-list"></div>
|
||||
@@ -2135,7 +2309,11 @@ def ui_html() -> str:
|
||||
"recovery_confirmed",
|
||||
"custody_packet_prepared",
|
||||
"openbao_preflight_passed",
|
||||
"openbao_init_output_produced",
|
||||
"openbao_initialized",
|
||||
"openbao_trial_material_exposed",
|
||||
"openbao_compromise_response_complete",
|
||||
"openbao_unseal_keys_rotated",
|
||||
"root_token_disposition",
|
||||
"restore_drill_passed"
|
||||
];
|
||||
@@ -2244,8 +2422,8 @@ def ui_html() -> str:
|
||||
}
|
||||
}
|
||||
|
||||
function renderCommands(commands) {
|
||||
const root = document.getElementById("command-list");
|
||||
function renderCommands(target, commands) {
|
||||
const root = document.getElementById(target);
|
||||
root.replaceChildren();
|
||||
for (const item of commands || []) {
|
||||
const row = document.createElement("div");
|
||||
@@ -2352,8 +2530,10 @@ def ui_html() -> str:
|
||||
renderRecords("roles-records", data.roles);
|
||||
renderRecords("subsystems-records", data.subsystems);
|
||||
renderRecords("integrations-records", data.integrations);
|
||||
renderRecords("runbooks-records", data.runbooks);
|
||||
renderRecords("artifacts-records", data.artifacts);
|
||||
renderCommands(data.commands);
|
||||
renderCommands("command-list", data.commands);
|
||||
renderCommands("runbook-command-list", data.runbook_commands);
|
||||
currentMetadata = data.metadata || {};
|
||||
fillForm(currentMetadata);
|
||||
setResponsibilityDirty(false);
|
||||
@@ -2394,7 +2574,11 @@ def ui_html() -> str:
|
||||
custody_packet_prepared: document.getElementById("custody_packet_prepared").checked,
|
||||
custody_mode: mode ? mode.value : "",
|
||||
openbao_preflight_passed: document.getElementById("openbao_preflight_passed").checked,
|
||||
openbao_init_output_produced: document.getElementById("openbao_init_output_produced").checked,
|
||||
openbao_initialized: document.getElementById("openbao_initialized").checked,
|
||||
openbao_trial_material_exposed: document.getElementById("openbao_trial_material_exposed").checked,
|
||||
openbao_compromise_response_complete: document.getElementById("openbao_compromise_response_complete").checked,
|
||||
openbao_unseal_keys_rotated: document.getElementById("openbao_unseal_keys_rotated").checked,
|
||||
root_token_disposition: document.getElementById("root_token_disposition").value,
|
||||
restore_drill_passed: document.getElementById("restore_drill_passed").checked,
|
||||
approval_phrase: document.getElementById("approval_phrase").value,
|
||||
@@ -2438,7 +2622,7 @@ def ui_html() -> str:
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("command-list").addEventListener("click", async (event) => {
|
||||
document.addEventListener("click", async (event) => {
|
||||
const button = event.target.closest(".copy-button");
|
||||
if (!button) return;
|
||||
const command = button.dataset.command || "";
|
||||
|
||||
@@ -242,6 +242,12 @@ inside a dirty-state Save/Cancel foldout, future quorum contact uses the same
|
||||
effective-value prefill as the role display, and command cards now derive
|
||||
`blocked`, `todo`, `redo`, or `done` status from bootstrap metadata.
|
||||
|
||||
**2026-05-25:** Added a Usecases & Runbooks section for trial-output exposure
|
||||
and key-material compromise. The UI now records non-secret compromise response
|
||||
state, separates "init output produced" from "initialized and unsealed", and
|
||||
adds guided command cards for unseal and OpenBao `rotate-keys` replacement
|
||||
share generation.
|
||||
|
||||
**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
|
||||
|
||||
Reference in New Issue
Block a user