generated from coulomb/repo-seed
Add OpenBao emergency lockdown runbook
This commit is contained in:
@@ -594,6 +594,7 @@ def merged_approval_metadata(
|
||||
"openbao_trial_material_exposed",
|
||||
"openbao_compromise_response_complete",
|
||||
"openbao_unseal_keys_rotated",
|
||||
"openbao_emergency_lockdown_drilled",
|
||||
"restore_drill_passed",
|
||||
):
|
||||
if field in payload:
|
||||
@@ -811,6 +812,7 @@ def metadata_template() -> dict[str, Any]:
|
||||
"openbao_trial_material_exposed": False,
|
||||
"openbao_compromise_response_complete": False,
|
||||
"openbao_unseal_keys_rotated": False,
|
||||
"openbao_emergency_lockdown_drilled": False,
|
||||
"root_token_disposition": "",
|
||||
"restore_drill_passed": False,
|
||||
"cleanup_complete": False,
|
||||
@@ -1339,6 +1341,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
trial_exposed = yes(data, "openbao_trial_material_exposed")
|
||||
response_complete = yes(data, "openbao_compromise_response_complete")
|
||||
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
|
||||
lockdown_drilled = yes(data, "openbao_emergency_lockdown_drilled")
|
||||
openbao_direct_taint = openbao_trial_taint(data, "direct")
|
||||
openbao_downstream_taint = openbao_trial_taint(data, "downstream")
|
||||
|
||||
@@ -1357,6 +1360,12 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
rotate_status = "blocked"
|
||||
rotate_location = "Record the key-compromise condition or schedule a normal rotation first."
|
||||
|
||||
lockdown_status = "done" if lockdown_drilled else "todo"
|
||||
lockdown_location = "Requires an unsealed OpenBao instance and a token with root or sudo on sys/seal."
|
||||
if not initialized:
|
||||
lockdown_status = "blocked"
|
||||
lockdown_location = "OpenBao is not recorded as unsealed; sealing is only meaningful while it is serving requests."
|
||||
|
||||
return [
|
||||
add_taint(
|
||||
{
|
||||
@@ -1382,6 +1391,18 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
},
|
||||
openbao_downstream_taint if trial_exposed and not response_complete else {},
|
||||
),
|
||||
add_taint(
|
||||
{
|
||||
"name": "Emergency lock-down",
|
||||
"description": "Seal Railiance OpenBao to stop access to stored OpenBao assets until a later unseal quorum reopens it.",
|
||||
"subsystem": "Railiance OpenBao",
|
||||
"responsibility": "openbao-ceremony-operator",
|
||||
"email": role_email(data, "role_openbao_operator_email"),
|
||||
"location": lockdown_location,
|
||||
"state": lockdown_status,
|
||||
},
|
||||
openbao_downstream_taint if initialized else {},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -1391,6 +1412,7 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
trial_exposed = yes(data, "openbao_trial_material_exposed")
|
||||
response_complete = yes(data, "openbao_compromise_response_complete")
|
||||
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
|
||||
lockdown_drilled = yes(data, "openbao_emergency_lockdown_drilled")
|
||||
openbao_direct_taint = openbao_trial_taint(data, "direct")
|
||||
openbao_downstream_taint = openbao_trial_taint(data, "downstream")
|
||||
|
||||
@@ -1418,6 +1440,20 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
rotate_status = "blocked"
|
||||
rotate_reason = "Record exposure or schedule a normal rotation before generating new shares."
|
||||
|
||||
lockdown_status = "done" if lockdown_drilled else "todo"
|
||||
lockdown_reason = "Emergency lock-down drill is recorded." if lockdown_drilled else "Seals OpenBao immediately. Use only during an emergency or scheduled drill."
|
||||
if not initialized:
|
||||
lockdown_status = "blocked"
|
||||
lockdown_reason = "OpenBao must be unsealed before emergency seal changes availability."
|
||||
seal_command = (
|
||||
"printf 'OpenBao token: ' >&2\n"
|
||||
"read -rs OPENBAO_TOKEN\n"
|
||||
"printf '\\n' >&2\n"
|
||||
"printf '%s\\n' \"$OPENBAO_TOKEN\" | kubectl exec -i -n openbao openbao-0 -- "
|
||||
"sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; bao operator seal'\n"
|
||||
"unset OPENBAO_TOKEN"
|
||||
)
|
||||
|
||||
return [
|
||||
add_taint(
|
||||
{
|
||||
@@ -1479,6 +1515,36 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
},
|
||||
openbao_downstream_taint if trial_exposed and not response_complete else {},
|
||||
),
|
||||
add_taint(
|
||||
{
|
||||
"name": "Emergency seal OpenBao",
|
||||
"description": "Prompt locally for an OpenBao token and seal Railiance OpenBao without placing the token on the command line.",
|
||||
"status": lockdown_status,
|
||||
"status_reason": lockdown_reason,
|
||||
"command": seal_command,
|
||||
},
|
||||
openbao_downstream_taint if initialized else {},
|
||||
),
|
||||
add_taint(
|
||||
{
|
||||
"name": "Confirm sealed status",
|
||||
"description": "Check that Railiance OpenBao reports Sealed true after an emergency seal.",
|
||||
"status": "todo" if initialized else "blocked",
|
||||
"status_reason": "Run after emergency seal; expect Sealed true." if initialized else "OpenBao must be initialized before status confirms lock-down.",
|
||||
"command": "kubectl exec -n openbao openbao-0 -- bao status",
|
||||
},
|
||||
openbao_downstream_taint if initialized else {},
|
||||
),
|
||||
add_taint(
|
||||
{
|
||||
"name": "Record emergency lock-down drill",
|
||||
"description": "Non-secret metadata checkbox after an emergency seal drill or real lock-down is confirmed.",
|
||||
"status": lockdown_status,
|
||||
"status_reason": lockdown_reason,
|
||||
"command": "Use the checkbox: Emergency lock-down drill recorded",
|
||||
},
|
||||
openbao_downstream_taint if initialized else {},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -1490,24 +1556,36 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
runbook_rows = runbook_payloads(data)
|
||||
artifact_rows = artifact_payloads(data)
|
||||
return [
|
||||
{
|
||||
"key": "intro",
|
||||
"name": "Introduction & Actors",
|
||||
"status": "ok",
|
||||
"reason": "NetKingdom purpose, global actors, and supported custody shapes are visible.",
|
||||
},
|
||||
{
|
||||
"key": "subsystems",
|
||||
"name": "Subsystems & Scopes",
|
||||
"status": "ok" if all(row["state"] in {"ok", "set"} for row in subsystem_rows) else "err",
|
||||
"reason": "Subsystems have install/access evidence." if all(row["state"] != "nil" for row in subsystem_rows) else "Complete subsystem setup fields and confirmations.",
|
||||
},
|
||||
{
|
||||
"key": "roles",
|
||||
"name": "Roles & Responsibilities",
|
||||
"status": "ok" if role_ok else "set",
|
||||
"reason": "All active bootstrap roles have a designated email." if role_ok else "Assign an email to each active bootstrap role.",
|
||||
},
|
||||
{
|
||||
"key": "subsystems",
|
||||
"name": "Subsystems & Scope",
|
||||
"status": "ok" if all(row["state"] in {"ok", "set"} for row in subsystem_rows) else "err",
|
||||
"reason": "Subsystems have install/access evidence." if all(row["state"] != "nil" for row in subsystem_rows) else "Complete subsystem setup fields and confirmations.",
|
||||
},
|
||||
{
|
||||
"key": "integrations",
|
||||
"name": "Integration & Tests",
|
||||
"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": "artifacts",
|
||||
"name": "Artefacts & Locations",
|
||||
"status": "ok" if all(row["state"] != "nil" for row in artifact_rows[:10]) else "set",
|
||||
"reason": "Core custody artefacts have locations or confirmations." if all(row["state"] != "nil" for row in artifact_rows[:10]) else "Record missing artefact locations and confirmations.",
|
||||
},
|
||||
{
|
||||
"key": "runbooks",
|
||||
"name": "Usecases & Runbooks",
|
||||
@@ -1515,10 +1593,10 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
"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",
|
||||
"status": "ok" if all(row["state"] != "nil" for row in artifact_rows[:10]) else "set",
|
||||
"reason": "Core custody artefacts have locations or confirmations." if all(row["state"] != "nil" for row in artifact_rows[:10]) else "Record missing artefact locations and confirmations.",
|
||||
"key": "terminology",
|
||||
"name": "Terminology & Patterns",
|
||||
"status": "ok",
|
||||
"reason": "Shared NetKingdom security terms and patterns are documented for operators.",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1815,6 +1893,18 @@ def ui_html() -> str:
|
||||
background: var(--paper);
|
||||
padding: 18px;
|
||||
}
|
||||
#approval-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.workflow-section[data-section="intro"] { order: 1; }
|
||||
.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="terminology"] { order: 7; }
|
||||
.workflow-actions { order: 8; }
|
||||
.panel + .panel { margin-top: 18px; }
|
||||
h2 {
|
||||
margin: 0 0 14px;
|
||||
@@ -2179,8 +2269,26 @@ def ui_html() -> str:
|
||||
|
||||
<div class="layout">
|
||||
<form id="approval-form">
|
||||
<details class="panel workflow-section" data-section="intro" open>
|
||||
<summary><span class="summary-title">1. Introduction & Actors</span><span class="state nil" data-section-state="intro">nil</span></summary>
|
||||
<div class="section-gate" data-section-gate="intro">Loading introduction gate.</div>
|
||||
<p class="notice">NetKingdom is the operating frame for accountable digital sovereignty: identity, custody, secrets, approvals, recovery, and emergency control are made explicit before subsystems are trusted with live assets.</p>
|
||||
<div class="record-list">
|
||||
<div class="record-row"><span class="state set">role</span><div><div class="record-name">King / CEO</div><div class="record-description">Ultimate accountable authority for the kingdom, final custody intent, and existential risk decisions.</div></div><div class="record-meta">Global actor</div><div class="record-meta">Always present</div></div>
|
||||
<div class="record-row"><span class="state set">role</span><div><div class="record-name">Guardian / CSO</div><div class="record-description">Owns security posture, emergency lock-down readiness, incident response, and custody-risk review.</div></div><div class="record-meta">Global actor</div><div class="record-meta">Required for quorum setups</div></div>
|
||||
<div class="record-row"><span class="state set">role</span><div><div class="record-name">Armorer / CTO</div><div class="record-description">Owns technical implementation, platform architecture, automation, and operational correctness.</div></div><div class="record-meta">Global actor</div><div class="record-meta">Required for quorum setups</div></div>
|
||||
<div class="record-row"><span class="state set">role</span><div><div class="record-name">Steward / COO</div><div class="record-description">Owns process reliability, onboarding, offboarding, runbook hygiene, and day-to-day operating cadence.</div></div><div class="record-meta">Global actor</div><div class="record-meta">Five-person setup</div></div>
|
||||
<div class="record-row"><span class="state set">role</span><div><div class="record-name">Treasurer / CFO</div><div class="record-description">Owns financial authorization, asset inventory, economic risk, and continuity of valuable holdings.</div></div><div class="record-meta">Global actor</div><div class="record-meta">Five-person setup</div></div>
|
||||
</div>
|
||||
<div class="choice-list">
|
||||
<div class="choice"><span class="step-number">A</span><span><strong>One Person Kingdom</strong><span>King / CEO only. Best for early bootstrap and solo custody; fast but has no human quorum.</span></span></div>
|
||||
<div class="choice"><span class="step-number">B</span><span><strong>Three Person Kingdom</strong><span>King / CEO plus two others. Decisions and access control target a two-of-three quorum.</span></span></div>
|
||||
<div class="choice"><span class="step-number">C</span><span><strong>Five Person Kingdom</strong><span>All global actors active. Decisions and access control target a three-of-five quorum.</span></span></div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="roles" open>
|
||||
<summary><span class="summary-title">1. Roles & Responsibilities</span><span class="state nil" data-section-state="roles">nil</span></summary>
|
||||
<summary><span class="summary-title">3. Roles & Responsibilities</span><span class="state nil" data-section-state="roles">nil</span></summary>
|
||||
<div class="section-gate" data-section-gate="roles">Loading role gate.</div>
|
||||
<p class="notice">Define who is accountable for each bootstrap role before touching subsystem-specific controls. Role chips in every record show the role name; hover them to see the designated email.</p>
|
||||
<div id="roles-records" class="record-list"></div>
|
||||
@@ -2228,7 +2336,7 @@ def ui_html() -> str:
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="subsystems" open>
|
||||
<summary><span class="summary-title">2. Subsystems & Scope</span><span class="state nil" data-section-state="subsystems">nil</span></summary>
|
||||
<summary><span class="summary-title">2. Subsystems & Scopes</span><span class="state nil" data-section-state="subsystems">nil</span></summary>
|
||||
<div class="section-gate" data-section-gate="subsystems">Loading subsystem gate.</div>
|
||||
<p class="notice">This section is about installing each subsystem and establishing initial user access. Integration checks come later.</p>
|
||||
<div id="subsystems-records" class="record-list"></div>
|
||||
@@ -2307,7 +2415,7 @@ def ui_html() -> str:
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="integrations" open>
|
||||
<summary><span class="summary-title">3. Integration & Tests</span><span class="state nil" data-section-state="integrations">nil</span></summary>
|
||||
<summary><span class="summary-title">4. Integration & Tests</span><span class="state nil" data-section-state="integrations">nil</span></summary>
|
||||
<div class="section-gate" data-section-gate="integrations">Loading integration gate.</div>
|
||||
<p class="notice">This section connects the subsystems and shows every console command as a copyable block. Commands still run outside this browser so secret output never enters the control surface.</p>
|
||||
<div id="integrations-records" class="record-list"></div>
|
||||
@@ -2337,7 +2445,7 @@ def ui_html() -> str:
|
||||
</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>
|
||||
<summary><span class="summary-title">6. 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>
|
||||
@@ -2345,6 +2453,7 @@ def ui_html() -> str:
|
||||
<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>
|
||||
<label class="choice"><input id="openbao_emergency_lockdown_drilled" type="checkbox"><span><strong>Emergency lock-down drill recorded</strong><span>Railiance OpenBao was sealed and status-confirmed during a drill or real lock-down. No token or share is recorded here.</span></span></label>
|
||||
</div>
|
||||
<div id="runbook-command-list" class="command-list"></div>
|
||||
</details>
|
||||
@@ -2375,13 +2484,34 @@ def ui_html() -> str:
|
||||
<span class="label">Approval phrase for selected strategy</span>
|
||||
<input id="approval_phrase" type="text" autocomplete="off" placeholder="approve custody mode" title="Type the approval phrase only after the selected strategy, recovery material, and custody packet are ready.">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="terminology" open>
|
||||
<summary><span class="summary-title">7. Terminology & Patterns</span><span class="state nil" data-section-state="terminology">nil</span></summary>
|
||||
<div class="section-gate" data-section-gate="terminology">Loading terminology gate.</div>
|
||||
<p class="notice">These terms apply across NetKingdom. Subsystems may have their own names, but the control surface keeps the cross-subsystem security pattern visible.</p>
|
||||
<div class="record-list">
|
||||
<div class="record-row"><span class="state set">term</span><div><div class="record-name">Subsystem</div><div class="record-description">A bounded tool or service with its own admin surface, state, and security model.</div></div><div class="record-meta">Pattern</div><div class="record-meta">LLDAP, privacyIDEA, KeyCape, OpenBao</div></div>
|
||||
<div class="record-row"><span class="state set">term</span><div><div class="record-name">Artefact</div><div class="record-description">A named credential, key, token, group, policy, packet, bundle, or evidence item with an owner and location.</div></div><div class="record-meta">Pattern</div><div class="record-meta">Track name, subsystem, role, location, state</div></div>
|
||||
<div class="record-row"><span class="state set">term</span><div><div class="record-name">Custody</div><div class="record-description">The human and technical arrangement that controls who can access or recover critical material.</div></div><div class="record-meta">Pattern</div><div class="record-meta">Single king, two-of-three, three-of-five</div></div>
|
||||
<div class="record-row"><span class="state set">term</span><div><div class="record-name">Quorum</div><div class="record-description">A threshold of actors or shares required before sensitive decisions or recovery actions can proceed.</div></div><div class="record-meta">Pattern</div><div class="record-meta">2 of 3, 3 of 5</div></div>
|
||||
<div class="record-row"><span class="state set">term</span><div><div class="record-name">Taint</div><div class="record-description">A warning that work is downstream of a compromised, exposed, or trial-only artefact. Taint informs; it does not hard-block the operator.</div></div><div class="record-meta">Pattern</div><div class="record-meta">Keeps source reference visible</div></div>
|
||||
<div class="record-row"><span class="state set">term</span><div><div class="record-name">Break-glass</div><div class="record-description">Emergency material that can restore control but is not part of normal daily operation.</div></div><div class="record-meta">Pattern</div><div class="record-meta">Retrieve, use, review, rotate if needed</div></div>
|
||||
<div class="record-row"><span class="state set">term</span><div><div class="record-name">Seal and unseal</div><div class="record-description">For Railiance OpenBao, sealing stops access to stored OpenBao assets; unsealing requires the configured threshold of unseal shares.</div></div><div class="record-meta">Pattern</div><div class="record-meta">Emergency lock-down and reopening</div></div>
|
||||
<div class="record-row"><span class="state set">term</span><div><div class="record-name">Root token vs unseal shares</div><div class="record-description">The Railiance OpenBao initial root token is a superuser API credential after unseal. Unseal shares control whether OpenBao can decrypt and serve requests at all.</div></div><div class="record-meta">Pattern</div><div class="record-meta">Different layers, different custody</div></div>
|
||||
<div class="record-row"><span class="state set">term</span><div><div class="record-name">Least privilege</div><div class="record-description">Daily access should use scoped credentials; root, quorum, and break-glass paths remain exceptional and reviewable.</div></div><div class="record-meta">Pattern</div><div class="record-meta">Delegate, expire, audit</div></div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<section class="panel workflow-actions">
|
||||
<h2>Actions</h2>
|
||||
<div class="actions">
|
||||
<button class="secondary" id="save-button" type="button" title="Save the visible non-secret progress fields to local metadata.">Save progress</button>
|
||||
<button id="approve-button" type="submit" title="Approve the selected custody strategy only after all kit gates are satisfied.">Approve selected strategy</button>
|
||||
<button class="secondary" id="refresh-button" type="button" title="Reload the local metadata and gate status from disk.">Refresh</button>
|
||||
</div>
|
||||
<div id="message" class="message" role="status">Waiting for local approval.</div>
|
||||
</details>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<aside>
|
||||
@@ -2438,6 +2568,7 @@ def ui_html() -> str:
|
||||
"openbao_trial_material_exposed",
|
||||
"openbao_compromise_response_complete",
|
||||
"openbao_unseal_keys_rotated",
|
||||
"openbao_emergency_lockdown_drilled",
|
||||
"root_token_disposition",
|
||||
"restore_drill_passed"
|
||||
];
|
||||
@@ -2720,6 +2851,7 @@ def ui_html() -> str:
|
||||
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,
|
||||
openbao_emergency_lockdown_drilled: document.getElementById("openbao_emergency_lockdown_drilled").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,
|
||||
|
||||
Reference in New Issue
Block a user