generated from coulomb/repo-seed
Add OpenBao admin identity stage
This commit is contained in:
@@ -28,7 +28,7 @@ from typing import Any
|
||||
|
||||
|
||||
DEFAULT_STAGE = "S1 - Low-trust assembly"
|
||||
STAGE_ORDER = ("S1", "S2", "S3", "S4", "S5")
|
||||
STAGE_ORDER = ("S1", "S2", "S3", "S4", "S5", "S6")
|
||||
DEFAULT_METADATA_PATH = Path("/tmp/net-kingdom-security-bootstrap.json")
|
||||
APPROVAL_PHRASE = "approve custody mode"
|
||||
VALID_STORAGE_CLASSES = {"password-safe", "offline-packet", "hardware-token"}
|
||||
@@ -351,13 +351,11 @@ def custody_mode_reason(data: dict[str, Any]) -> str:
|
||||
|
||||
def derive_stage(data: dict[str, Any]) -> str:
|
||||
if yes(data, "platform_reopened"):
|
||||
return "S5 - Reopen under custody"
|
||||
if (
|
||||
yes(data, "openbao_initial_config_applied")
|
||||
and data.get("root_token_disposition") in {"revoked", "offline-sealed"}
|
||||
and yes(data, "restore_drill_passed")
|
||||
):
|
||||
return "S4 - Cleanup and hardening"
|
||||
return "S6 - Reopen under custody"
|
||||
if yes(data, "cleanup_complete") or yes(data, "openbao_oidc_admin_login_verified"):
|
||||
return "S5 - Cleanup and hardening"
|
||||
if yes(data, "openbao_initial_config_applied"):
|
||||
return "S4 - Admin identity integration"
|
||||
if yes(data, "openbao_initialized"):
|
||||
return "S3 - OpenBao bootstrap"
|
||||
if yes(data, "king_credential_ready") or king_kit_ready(data):
|
||||
@@ -387,17 +385,23 @@ def stage_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
(
|
||||
"S3",
|
||||
"OpenBao bootstrap",
|
||||
"Initialize, unseal, configure OpenBao, then decide root-token disposition and prove restore.",
|
||||
"Record root-token disposition and pass the restore drill.",
|
||||
"Initialize, unseal, rotate trial material, configure OpenBao, and create a temporary admin bridge.",
|
||||
"Bind normal OpenBao admin access to NetKingdom identity.",
|
||||
),
|
||||
(
|
||||
"S4",
|
||||
"Cleanup and hardening",
|
||||
"Rotate or retire bootstrap-era access, resolve taint, review audit, and document residual risk.",
|
||||
"Complete cleanup and mark the platform ready to reopen.",
|
||||
"Admin identity integration",
|
||||
"Register OpenBao with KeyCape and map verified NetKingdom admin claims to the platform-admin policy.",
|
||||
"Verify OIDC-backed OpenBao admin login.",
|
||||
),
|
||||
(
|
||||
"S5",
|
||||
"Cleanup and hardening",
|
||||
"Retire bootstrap-era access, resolve taint, record root-token disposition, prove restore, and document residual risk.",
|
||||
"Complete cleanup and mark the platform ready to reopen.",
|
||||
),
|
||||
(
|
||||
"S6",
|
||||
"Reopen under custody",
|
||||
"Operate under the approved custody model with break-glass and recovery paths known.",
|
||||
"Review related workplans.",
|
||||
@@ -455,6 +459,21 @@ def build_gates(data: dict[str, Any]) -> list[Gate]:
|
||||
),
|
||||
"Apply first auth, mount, and policy configuration; audit may be a declarative follow-up.",
|
||||
),
|
||||
Gate(
|
||||
"KeyCape OpenBao client",
|
||||
"done" if yes(data, "openbao_oidc_client_registered") else "blocked",
|
||||
"Dedicated OpenBao OIDC client is registered in KeyCape with exact redirect URIs.",
|
||||
),
|
||||
Gate(
|
||||
"OpenBao OIDC auth",
|
||||
"done" if yes(data, "openbao_oidc_auth_configured") else "blocked",
|
||||
"OpenBao OIDC/JWT auth is configured against KeyCape and maps claims to policy.",
|
||||
),
|
||||
Gate(
|
||||
"OIDC admin login",
|
||||
"done" if yes(data, "openbao_oidc_admin_login_verified") else "blocked",
|
||||
"platform-root can obtain OpenBao platform-admin access through KeyCape/MFA.",
|
||||
),
|
||||
Gate(
|
||||
"Root-token disposition",
|
||||
"done" if data.get("root_token_disposition") in {"revoked", "offline-sealed"} else "blocked",
|
||||
@@ -519,6 +538,12 @@ def next_action(
|
||||
return "Approve custody strategy"
|
||||
if gate.name == "OpenBao preflight":
|
||||
return "Run OpenBao preflight"
|
||||
if gate.name == "KeyCape OpenBao client":
|
||||
return "Register OpenBao OIDC client"
|
||||
if gate.name == "OpenBao OIDC auth":
|
||||
return "Configure OpenBao OIDC auth"
|
||||
if gate.name == "OIDC admin login":
|
||||
return "Verify OpenBao OIDC admin login"
|
||||
if gate.name == "Root-token disposition":
|
||||
return "Record root-token disposition"
|
||||
if gate.name == "Restore drill":
|
||||
@@ -665,6 +690,9 @@ def merged_approval_metadata(
|
||||
"openbao_compromise_response_complete",
|
||||
"openbao_unseal_keys_rotated",
|
||||
"openbao_emergency_lockdown_drilled",
|
||||
"openbao_oidc_client_registered",
|
||||
"openbao_oidc_auth_configured",
|
||||
"openbao_oidc_admin_login_verified",
|
||||
"restore_drill_passed",
|
||||
"cleanup_complete",
|
||||
"platform_reopened",
|
||||
@@ -885,6 +913,9 @@ def metadata_template() -> dict[str, Any]:
|
||||
"openbao_compromise_response_complete": False,
|
||||
"openbao_unseal_keys_rotated": False,
|
||||
"openbao_emergency_lockdown_drilled": False,
|
||||
"openbao_oidc_client_registered": False,
|
||||
"openbao_oidc_auth_configured": False,
|
||||
"openbao_oidc_admin_login_verified": False,
|
||||
"root_token_disposition": "",
|
||||
"restore_drill_passed": False,
|
||||
"cleanup_complete": False,
|
||||
@@ -1154,6 +1185,48 @@ def integration_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
]
|
||||
|
||||
|
||||
def admin_identity_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
downstream_taint = openbao_trial_taint(data, "downstream") if yes(data, "openbao_initialized") else {}
|
||||
return [
|
||||
add_taint(
|
||||
{
|
||||
"name": "OpenBao OIDC client",
|
||||
"description": "Dedicated KeyCape client for OpenBao admin login with exact localhost callback URIs.",
|
||||
"subsystem": "KeyCape",
|
||||
"responsibility": "identity-admin",
|
||||
"email": role_email(data, "role_identity_admin_email"),
|
||||
"location": "clientId=openbao-admin; scopes=openid profile email groups",
|
||||
"state": state_value(yes(data, "openbao_oidc_client_registered"), yes(data, "openbao_initial_config_applied")),
|
||||
},
|
||||
downstream_taint,
|
||||
),
|
||||
add_taint(
|
||||
{
|
||||
"name": "OpenBao OIDC auth method",
|
||||
"description": "OpenBao trusts KeyCape discovery and maps the net-kingdom-admins group to the platform-admin policy.",
|
||||
"subsystem": "Railiance OpenBao",
|
||||
"responsibility": "openbao-ceremony-operator",
|
||||
"email": role_email(data, "role_openbao_operator_email"),
|
||||
"location": "auth/keycape or equivalent OpenBao OIDC/JWT mount",
|
||||
"state": state_value(yes(data, "openbao_oidc_auth_configured"), yes(data, "openbao_oidc_client_registered")),
|
||||
},
|
||||
downstream_taint,
|
||||
),
|
||||
add_taint(
|
||||
{
|
||||
"name": "OIDC admin login",
|
||||
"description": "platform-root can obtain OpenBao platform-admin access through KeyCape MFA instead of a manually minted token.",
|
||||
"subsystem": "KeyCape -> OpenBao",
|
||||
"responsibility": "platform-root-custodian",
|
||||
"email": role_email(data, "role_platform_custodian_email"),
|
||||
"location": "bao login -method=oidc -path=keycape role=platform-admin",
|
||||
"state": state_value(yes(data, "openbao_oidc_admin_login_verified"), yes(data, "openbao_oidc_auth_configured")),
|
||||
},
|
||||
downstream_taint,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
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()
|
||||
@@ -1675,6 +1748,7 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
role_ok = all(row["state"] != "nil" for row in role_rows[:5])
|
||||
subsystem_rows = subsystem_payloads(data)
|
||||
integration_rows = integration_payloads(data)
|
||||
admin_rows = admin_identity_payloads(data)
|
||||
artifact_rows = artifact_payloads(data)
|
||||
cleanup_done = yes(data, "cleanup_complete")
|
||||
reopened = yes(data, "platform_reopened")
|
||||
@@ -1703,6 +1777,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": "admin-identity",
|
||||
"name": "Admin Identity Integration",
|
||||
"status": "ok" if all(row["state"] == "ok" for row in admin_rows) else "set",
|
||||
"reason": "OpenBao admin access is bound to NetKingdom OIDC claims." if all(row["state"] == "ok" for row in admin_rows) else "Configure and verify the KeyCape-backed OpenBao admin path.",
|
||||
},
|
||||
{
|
||||
"key": "artifacts",
|
||||
"name": "Artefacts & Locations",
|
||||
@@ -1766,6 +1846,7 @@ 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),
|
||||
"admin_identity_integrations": admin_identity_payloads(merged),
|
||||
"runbooks": runbook_payloads(merged),
|
||||
"artifacts": artifact_payloads(merged),
|
||||
"commands": command_payloads(merged),
|
||||
@@ -2018,7 +2099,7 @@ def ui_html() -> str:
|
||||
}
|
||||
.stage-rail {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
@@ -2073,11 +2154,12 @@ def ui_html() -> str:
|
||||
.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="handover"] { order: 7; }
|
||||
.workflow-section[data-section="terminology"] { order: 8; }
|
||||
.workflow-actions { order: 9; }
|
||||
.workflow-section[data-section="admin-identity"] { order: 5; }
|
||||
.workflow-section[data-section="artifacts"] { order: 6; }
|
||||
.workflow-section[data-section="runbooks"] { order: 7; }
|
||||
.workflow-section[data-section="handover"] { order: 8; }
|
||||
.workflow-section[data-section="terminology"] { order: 9; }
|
||||
.workflow-actions { order: 10; }
|
||||
.panel + .panel { margin-top: 18px; }
|
||||
h2 {
|
||||
margin: 0 0 14px;
|
||||
@@ -2619,8 +2701,25 @@ def ui_html() -> str:
|
||||
<div id="command-list" class="command-list"></div>
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="admin-identity" open>
|
||||
<summary><span class="summary-title">5. Admin Identity Integration</span><span class="state nil" data-section-state="admin-identity">nil</span></summary>
|
||||
<div class="section-gate" data-section-gate="admin-identity">Loading admin identity gate.</div>
|
||||
<p class="notice">This stage replaces manually minted OpenBao admin tokens as the normal path. KeyCape remains the identity issuer; OpenBao maps verified NetKingdom group claims to the existing platform-admin policy.</p>
|
||||
<div id="admin-identity-records" class="record-list"></div>
|
||||
<ul class="spec-list" style="margin-top: 14px;">
|
||||
<li>Register a dedicated KeyCape client such as <code>openbao-admin</code> with exact localhost OpenBao callback URIs and the <code>groups</code> scope.</li>
|
||||
<li>Configure an OpenBao OIDC/JWT auth mount against <code>https://kc.coulomb.social</code> and bind <code>net-kingdom-admins</code> to <code>platform-admin</code>.</li>
|
||||
<li>Verify <code>platform-root</code> can complete MFA-backed OpenBao login before root-token disposition and cleanup.</li>
|
||||
</ul>
|
||||
<div class="choice-list">
|
||||
<label class="choice"><input id="openbao_oidc_client_registered" type="checkbox"><span><strong>OpenBao client registered in KeyCape</strong><span>The dedicated client exists with exact redirect URIs and allowed OIDC scopes.</span></span></label>
|
||||
<label class="choice"><input id="openbao_oidc_auth_configured" type="checkbox"><span><strong>OpenBao OIDC auth configured</strong><span>OpenBao trusts KeyCape and maps NetKingdom admin claims to the platform-admin policy.</span></span></label>
|
||||
<label class="choice"><input id="openbao_oidc_admin_login_verified" type="checkbox"><span><strong>OIDC admin login verified</strong><span>platform-root can obtain OpenBao admin access through KeyCape MFA without using a manually minted token.</span></span></label>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="artifacts" open>
|
||||
<summary><span class="summary-title">5. Artefacts & Locations</span><span class="state nil" data-section-state="artifacts">nil</span></summary>
|
||||
<summary><span class="summary-title">6. 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>
|
||||
@@ -2648,7 +2747,7 @@ def ui_html() -> str:
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="runbooks" open>
|
||||
<summary><span class="summary-title">6. Usecases & Runbooks</span><span class="state nil" data-section-state="runbooks">nil</span></summary>
|
||||
<summary><span class="summary-title">7. 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">This section contains reusable actions and runbook templates. Action cards are copyable command references without task status; runbook task state belongs to Integration & Tests or to the explicit confirmation gates.</p>
|
||||
<div id="runbooks-records" class="record-list"></div>
|
||||
@@ -2662,7 +2761,7 @@ def ui_html() -> str:
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="handover" open>
|
||||
<summary><span class="summary-title">7. Final Handover</span><span class="state nil" data-section-state="handover">nil</span></summary>
|
||||
<summary><span class="summary-title">8. Final Handover</span><span class="state nil" data-section-state="handover">nil</span></summary>
|
||||
<div class="section-gate" data-section-gate="handover">Loading final handover gate.</div>
|
||||
<p class="notice">This is the line between trial/bootstrap and operating under custody. Mark these only after root-token disposition, restore proof, taint response, and cleanup have been handled outside this UI.</p>
|
||||
<ul class="spec-list">
|
||||
@@ -2676,7 +2775,7 @@ def ui_html() -> str:
|
||||
</details>
|
||||
|
||||
<details class="panel workflow-section" data-section="terminology" open>
|
||||
<summary><span class="summary-title">8. Terminology & Patterns</span><span class="state nil" data-section-state="terminology">nil</span></summary>
|
||||
<summary><span class="summary-title">9. 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">
|
||||
@@ -2758,6 +2857,9 @@ def ui_html() -> str:
|
||||
"openbao_compromise_response_complete",
|
||||
"openbao_unseal_keys_rotated",
|
||||
"openbao_emergency_lockdown_drilled",
|
||||
"openbao_oidc_client_registered",
|
||||
"openbao_oidc_auth_configured",
|
||||
"openbao_oidc_admin_login_verified",
|
||||
"root_token_disposition",
|
||||
"restore_drill_passed",
|
||||
"cleanup_complete",
|
||||
@@ -3020,6 +3122,7 @@ def ui_html() -> str:
|
||||
renderRecords("roles-records", data.roles);
|
||||
renderRecords("subsystems-records", data.subsystems);
|
||||
renderRecords("integrations-records", data.integrations);
|
||||
renderRecords("admin-identity-records", data.admin_identity_integrations);
|
||||
renderRecords("runbooks-records", data.runbooks);
|
||||
renderRecords("artifacts-records", data.artifacts);
|
||||
renderCommands("command-list", data.commands);
|
||||
@@ -3072,6 +3175,9 @@ def ui_html() -> str:
|
||||
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,
|
||||
openbao_oidc_client_registered: document.getElementById("openbao_oidc_client_registered").checked,
|
||||
openbao_oidc_auth_configured: document.getElementById("openbao_oidc_auth_configured").checked,
|
||||
openbao_oidc_admin_login_verified: document.getElementById("openbao_oidc_admin_login_verified").checked,
|
||||
root_token_disposition: document.getElementById("root_token_disposition").value,
|
||||
restore_drill_passed: document.getElementById("restore_drill_passed").checked,
|
||||
cleanup_complete: document.getElementById("cleanup_complete").checked,
|
||||
|
||||
@@ -303,6 +303,12 @@ prompts for the bootstrap/root token without placing it on the command line
|
||||
and reminds the operator to store the emitted token through the approved secret
|
||||
path.
|
||||
|
||||
**2026-05-26:** Promoted the KeyCape-to-OpenBao admin path into its own stage
|
||||
before cleanup and hardening. The control surface now has S4 Admin Identity
|
||||
Integration with gates for the dedicated KeyCape OpenBao client, OpenBao
|
||||
OIDC/JWT auth configuration, and MFA-backed OpenBao admin login verification;
|
||||
cleanup and reopening move to S5/S6.
|
||||
|
||||
**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
|
||||
@@ -345,7 +351,7 @@ roles later, but must be revocable without losing root custody.
|
||||
|
||||
```task
|
||||
id: NET-WP-0015-T06
|
||||
status: todo
|
||||
status: in_progress
|
||||
priority: medium
|
||||
state_hub_task_id: "ef97f3cb-9792-4b9d-bd2b-8871d368a50f"
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user