generated from coulomb/repo-seed
Split OpenBao admin identity tasks
This commit is contained in:
@@ -30,6 +30,7 @@ from typing import Any
|
||||
DEFAULT_STAGE = "S1 - Low-trust assembly"
|
||||
STAGE_ORDER = ("S1", "S2", "S3", "S4", "S5", "S6")
|
||||
DEFAULT_METADATA_PATH = Path("/tmp/net-kingdom-security-bootstrap.json")
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
APPROVAL_PHRASE = "approve custody mode"
|
||||
VALID_STORAGE_CLASSES = {"password-safe", "offline-packet", "hardware-token"}
|
||||
VALID_MFA_CLASSES = {"totp", "webauthn", "hardware-token"}
|
||||
@@ -45,6 +46,12 @@ KEYCAPE_ISSUER = "https://kc.coulomb.social"
|
||||
OIDC_CLIENT_ID = "netkingdom-bootstrap-console"
|
||||
OIDC_SCOPE = "openid profile email groups"
|
||||
OIDC_CODE_VERIFIER = "netkingdom-bootstrap-local-oidc-verifier-2026-v1"
|
||||
KEYCAPE_OPENBAO_CLIENT_ID = "openbao-admin"
|
||||
KEYCAPE_OPENBAO_CLIENT_CONFIG = REPO_ROOT / "sso-mfa/k8s/keycape/create-secrets.sh"
|
||||
KEYCAPE_OPENBAO_CLIENT_REDIRECTS = (
|
||||
"http://localhost:8250/oidc/callback",
|
||||
"http://127.0.0.1:8250/oidc/callback",
|
||||
)
|
||||
AGE_PUBLIC_PREFIX = "age1"
|
||||
AGE_PRIVATE_MARKER = "AGE-SECRET-KEY-1"
|
||||
|
||||
@@ -94,6 +101,19 @@ def yes(data: dict[str, Any], key: str) -> bool:
|
||||
return data.get(key) is True
|
||||
|
||||
|
||||
def keycape_openbao_client_source_ready() -> bool:
|
||||
try:
|
||||
text = KEYCAPE_OPENBAO_CLIENT_CONFIG.read_text()
|
||||
except OSError:
|
||||
return False
|
||||
required = [
|
||||
f'clientId: "{KEYCAPE_OPENBAO_CLIENT_ID}"',
|
||||
'allowedScopes: ["openid", "profile", "email", "groups"]',
|
||||
]
|
||||
required.extend(f'"{uri}"' for uri in KEYCAPE_OPENBAO_CLIENT_REDIRECTS)
|
||||
return all(item in text for item in required)
|
||||
|
||||
|
||||
def second_factor_ready(data: dict[str, Any]) -> bool:
|
||||
return (
|
||||
data.get("mfa_class") in VALID_MFA_CLASSES
|
||||
@@ -391,8 +411,8 @@ def stage_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
(
|
||||
"S4",
|
||||
"Admin identity integration",
|
||||
"Register OpenBao with KeyCape and map verified NetKingdom admin claims to the platform-admin policy.",
|
||||
"Verify OIDC-backed OpenBao admin login.",
|
||||
"Deploy the code-defined KeyCape client, map verified NetKingdom admin claims to OpenBao policy, and verify login.",
|
||||
"Apply live KeyCape config, configure OpenBao OIDC auth, and verify admin login.",
|
||||
),
|
||||
(
|
||||
"S5",
|
||||
@@ -460,18 +480,41 @@ 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.",
|
||||
"KeyCape OpenBao client definition",
|
||||
"done" if keycape_openbao_client_source_ready() else "blocked",
|
||||
"The non-secret openbao-admin client is defined in KeyCape source/config generation.",
|
||||
),
|
||||
Gate(
|
||||
"KeyCape OpenBao client deployed",
|
||||
(
|
||||
"done"
|
||||
if yes(data, "openbao_oidc_client_registered")
|
||||
else "human"
|
||||
if keycape_openbao_client_source_ready() and yes(data, "openbao_initial_config_applied")
|
||||
else "blocked"
|
||||
),
|
||||
"Apply the code-defined client to the live KeyCape keycape-config Secret and restart KeyCape.",
|
||||
),
|
||||
Gate(
|
||||
"OpenBao OIDC auth",
|
||||
"done" if yes(data, "openbao_oidc_auth_configured") else "blocked",
|
||||
(
|
||||
"done"
|
||||
if yes(data, "openbao_oidc_auth_configured")
|
||||
else "human"
|
||||
if yes(data, "openbao_oidc_client_registered")
|
||||
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",
|
||||
(
|
||||
"done"
|
||||
if yes(data, "openbao_oidc_admin_login_verified")
|
||||
else "human"
|
||||
if yes(data, "openbao_oidc_auth_configured")
|
||||
else "blocked"
|
||||
),
|
||||
"platform-root can obtain OpenBao platform-admin access through KeyCape/MFA.",
|
||||
),
|
||||
Gate(
|
||||
@@ -528,6 +571,12 @@ def next_action(
|
||||
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"
|
||||
if gate.name == "KeyCape OpenBao client deployed":
|
||||
return "Apply KeyCape OpenBao client config"
|
||||
if gate.name == "OpenBao OIDC auth":
|
||||
return "Run OpenBao OIDC auth setup"
|
||||
if gate.name == "OIDC admin login":
|
||||
return "Verify OpenBao OIDC admin login"
|
||||
return gate.name
|
||||
if gate.status == "blocked":
|
||||
if gate.name == "King credential kit":
|
||||
@@ -538,10 +587,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 == "KeyCape OpenBao client definition":
|
||||
return "Ship KeyCape OpenBao client definition"
|
||||
if gate.name == "KeyCape OpenBao client deployed":
|
||||
return "Apply KeyCape OpenBao client config"
|
||||
if gate.name == "OpenBao OIDC auth":
|
||||
return "Configure OpenBao OIDC auth"
|
||||
return "Run OpenBao OIDC auth setup"
|
||||
if gate.name == "OIDC admin login":
|
||||
return "Verify OpenBao OIDC admin login"
|
||||
if gate.name == "Root-token disposition":
|
||||
@@ -1211,16 +1262,26 @@ 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 {}
|
||||
source_ready = keycape_openbao_client_source_ready()
|
||||
return [
|
||||
{
|
||||
"name": "openbao-admin client definition",
|
||||
"description": "Code-defined KeyCape public PKCE client for Railiance OpenBao CLI login. This is development/configuration, not a manual registration step.",
|
||||
"subsystem": "KeyCape source",
|
||||
"responsibility": "identity-admin",
|
||||
"email": role_email(data, "role_identity_admin_email"),
|
||||
"location": "sso-mfa/k8s/keycape/create-secrets.sh",
|
||||
"state": state_value(source_ready),
|
||||
},
|
||||
add_taint(
|
||||
{
|
||||
"name": "OpenBao OIDC client",
|
||||
"description": "Dedicated KeyCape client for OpenBao admin login with exact localhost callback URIs.",
|
||||
"name": "openbao-admin client deployed",
|
||||
"description": "The code-defined KeyCape client is applied to the live keycape-config Secret and KeyCape was restarted.",
|
||||
"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")),
|
||||
"location": "clientId=openbao-admin; CLI redirects localhost/127.0.0.1:8250",
|
||||
"state": state_value(yes(data, "openbao_oidc_client_registered"), source_ready and yes(data, "openbao_initial_config_applied")),
|
||||
},
|
||||
downstream_taint,
|
||||
),
|
||||
@@ -1251,6 +1312,150 @@ def admin_identity_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
]
|
||||
|
||||
|
||||
def admin_identity_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
source_ready = keycape_openbao_client_source_ready()
|
||||
client_deployed = yes(data, "openbao_oidc_client_registered")
|
||||
auth_configured = yes(data, "openbao_oidc_auth_configured")
|
||||
login_verified = yes(data, "openbao_oidc_admin_login_verified")
|
||||
initial_config_applied = yes(data, "openbao_initial_config_applied")
|
||||
downstream_taint = openbao_trial_taint(data, "downstream") if yes(data, "openbao_initialized") else {}
|
||||
|
||||
def action(
|
||||
name: str,
|
||||
description: str,
|
||||
status: str,
|
||||
status_reason: str,
|
||||
command: str,
|
||||
taint: dict[str, str] | None = None,
|
||||
) -> dict[str, str]:
|
||||
return add_taint(
|
||||
{
|
||||
"name": name,
|
||||
"description": description,
|
||||
"status": status,
|
||||
"status_reason": status_reason,
|
||||
"command": command,
|
||||
},
|
||||
taint or {},
|
||||
)
|
||||
|
||||
deploy_state = "done" if client_deployed else "todo" if source_ready and initial_config_applied else "blocked"
|
||||
deploy_reason = "Live KeyCape is recorded as carrying the code-defined openbao-admin client."
|
||||
if deploy_state == "todo":
|
||||
deploy_reason = "Operator action: apply the already-shipped KeyCape config and restart KeyCape. No new client secret is created."
|
||||
if deploy_state == "blocked":
|
||||
deploy_reason = "Blocked until OpenBao initial configuration exists and the KeyCape client definition is present in source."
|
||||
|
||||
auth_state = "done" if auth_configured else "todo" if client_deployed else "blocked"
|
||||
auth_reason = "OpenBao OIDC/JWT auth is recorded as configured."
|
||||
if auth_state == "todo":
|
||||
auth_reason = "Operator action: requires a root/sudo-capable OpenBao token at the hidden prompt; the token value is not recorded."
|
||||
if auth_state == "blocked":
|
||||
auth_reason = "Apply and confirm the live KeyCape openbao-admin client before configuring OpenBao auth."
|
||||
|
||||
login_state = "done" if login_verified else "todo" if auth_configured else "blocked"
|
||||
login_reason = "OIDC-backed OpenBao platform-admin login is recorded as verified."
|
||||
if login_state == "todo":
|
||||
login_reason = "Human verification: complete the browser MFA flow and confirm the resulting token has platform-admin policy."
|
||||
if login_state == "blocked":
|
||||
login_reason = "Configure OpenBao OIDC auth before testing the login path."
|
||||
|
||||
deploy_command = (
|
||||
"cd sso-mfa/k8s/keycape\n"
|
||||
"./create-secrets.sh\n"
|
||||
"kubectl rollout restart deployment/keycape -n sso\n"
|
||||
"kubectl rollout status deployment/keycape -n sso --timeout=60s\n"
|
||||
"cd ..\n"
|
||||
"./verify-t07.sh"
|
||||
)
|
||||
oidc_config_inner = """bao auth enable -path=keycape oidc >/tmp/keycape-auth-enable.out 2>/tmp/keycape-auth-enable.err || {
|
||||
if grep -q "path is already in use" /tmp/keycape-auth-enable.err; then
|
||||
printf "auth/keycape already exists\\n" >&2
|
||||
else
|
||||
cat /tmp/keycape-auth-enable.err >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
bao write auth/keycape/config \\
|
||||
oidc_discovery_url="https://kc.coulomb.social" \\
|
||||
oidc_client_id="openbao-admin" \\
|
||||
oidc_client_secret="" \\
|
||||
default_role="platform-admin"
|
||||
cat >/tmp/openbao-platform-admin-role.json <<ROLE_JSON
|
||||
{
|
||||
"role_type": "oidc",
|
||||
"user_claim": "sub",
|
||||
"groups_claim": "groups",
|
||||
"oidc_scopes": ["openid", "profile", "email", "groups"],
|
||||
"allowed_redirect_uris": [
|
||||
"http://localhost:8250/oidc/callback",
|
||||
"http://127.0.0.1:8250/oidc/callback"
|
||||
],
|
||||
"bound_claims": {
|
||||
"groups": ["net-kingdom-admins"]
|
||||
},
|
||||
"claim_mappings": {
|
||||
"email": "email",
|
||||
"preferred_username": "username",
|
||||
"groups": "groups"
|
||||
},
|
||||
"policies": ["platform-admin"],
|
||||
"ttl": "1h"
|
||||
}
|
||||
ROLE_JSON
|
||||
bao write auth/keycape/role/platform-admin @/tmp/openbao-platform-admin-role.json
|
||||
rm -f /tmp/openbao-platform-admin-role.json /tmp/keycape-auth-enable.out /tmp/keycape-auth-enable.err"""
|
||||
configure_command = (
|
||||
"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"
|
||||
" printf \"OpenBao root/sudo token: \" >&2\n"
|
||||
" stty -echo\n"
|
||||
" read -r BAO_TOKEN\n"
|
||||
" stty echo\n"
|
||||
" printf \"\\n\" >&2\n"
|
||||
" export BAO_TOKEN\n"
|
||||
f"{oidc_config_inner}\n"
|
||||
" unset BAO_TOKEN\n"
|
||||
"'"
|
||||
)
|
||||
login_command = (
|
||||
"# Terminal 1: keep a local OpenBao API port open while testing.\n"
|
||||
"kubectl -n openbao port-forward svc/openbao-active 8200:8200\n\n"
|
||||
"# Terminal 2: run the OIDC login and verify the policy on the returned token.\n"
|
||||
"export BAO_ADDR=http://127.0.0.1:8200\n"
|
||||
"bao login -method=oidc -path=keycape role=platform-admin\n"
|
||||
"bao token lookup"
|
||||
)
|
||||
|
||||
return [
|
||||
action(
|
||||
"Apply code-defined KeyCape OpenBao client",
|
||||
"Deployment action for the non-secret openbao-admin client already present in source. Run this only if live KeyCape has not yet loaded the updated config.",
|
||||
deploy_state,
|
||||
deploy_reason,
|
||||
deploy_command,
|
||||
downstream_taint if yes(data, "openbao_initialized") else {},
|
||||
),
|
||||
action(
|
||||
"Configure OpenBao OIDC auth",
|
||||
"Create or update the auth/keycape mount and platform-admin role so KeyCape group claims map to OpenBao platform-admin policy.",
|
||||
auth_state,
|
||||
auth_reason,
|
||||
configure_command,
|
||||
downstream_taint if yes(data, "openbao_initialized") else {},
|
||||
),
|
||||
action(
|
||||
"Verify OIDC-backed OpenBao admin login",
|
||||
"Start a local port-forward, complete the KeyCape MFA browser flow, and verify the returned OpenBao token before checking the confirmation box.",
|
||||
login_state,
|
||||
login_reason,
|
||||
login_command,
|
||||
downstream_taint if yes(data, "openbao_initialized") else {},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
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()
|
||||
@@ -1808,7 +2013,7 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
|
||||
"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.",
|
||||
"reason": "OpenBao admin access is bound to NetKingdom OIDC claims." if all(row["state"] == "ok" for row in admin_rows) else "Run only the remaining operator cards: live KeyCape deploy, protected OpenBao auth setup, or login verification.",
|
||||
},
|
||||
{
|
||||
"key": "artifacts",
|
||||
@@ -1874,6 +2079,7 @@ def status_payload(data: dict[str, Any], metadata_path: Path) -> dict[str, Any]:
|
||||
"subsystems": subsystem_payloads(merged),
|
||||
"integrations": integration_payloads(merged),
|
||||
"admin_identity_integrations": admin_identity_payloads(merged),
|
||||
"admin_identity_commands": admin_identity_command_payloads(merged),
|
||||
"runbooks": runbook_payloads(merged),
|
||||
"artifacts": artifact_payloads(merged),
|
||||
"commands": command_payloads(merged),
|
||||
@@ -2731,16 +2937,17 @@ def ui_html() -> str:
|
||||
<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>
|
||||
<p class="notice">This stage replaces manually minted OpenBao admin tokens as the normal path. Development-owned client definitions are shipped in source; operator-owned cards below apply live config, use protected OpenBao prompts, or verify login.</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>
|
||||
<li>Development/config: <code>openbao-admin</code> is defined in <code>sso-mfa/k8s/keycape/create-secrets.sh</code>; no manual KeyCape registration is expected.</li>
|
||||
<li>Operator deployment: apply the updated KeyCape config to live <code>keycape-config</code> and restart KeyCape if the live client is missing.</li>
|
||||
<li>Protected OpenBao step: configure <code>auth/keycape</code> with a hidden root/sudo token prompt, then verify <code>platform-root</code> can complete MFA-backed login.</li>
|
||||
</ul>
|
||||
<div id="admin-identity-command-list" class="command-list"></div>
|
||||
<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_client_registered" type="checkbox"><span><strong>KeyCape OpenBao client deployed</strong><span>The code-defined <code>openbao-admin</code> client is present in live KeyCape after config apply/restart.</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. The setup command uses a hidden token prompt.</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>
|
||||
@@ -3150,6 +3357,7 @@ def ui_html() -> str:
|
||||
renderRecords("subsystems-records", data.subsystems);
|
||||
renderRecords("integrations-records", data.integrations);
|
||||
renderRecords("admin-identity-records", data.admin_identity_integrations);
|
||||
renderCommands("admin-identity-command-list", data.admin_identity_commands);
|
||||
renderRecords("runbooks-records", data.runbooks);
|
||||
renderRecords("artifacts-records", data.artifacts);
|
||||
renderCommands("command-list", data.commands);
|
||||
|
||||
Reference in New Issue
Block a user