diff --git a/sso-mfa/k8s/keycape/README.md b/sso-mfa/k8s/keycape/README.md index 235ad4e..c29d5ad 100644 --- a/sso-mfa/k8s/keycape/README.md +++ b/sso-mfa/k8s/keycape/README.md @@ -139,6 +139,21 @@ decoded `config.yaml` or signing key. The verifier checks the live Secret and then opens a short local `kubectl port-forward` to KeyCape; it does not require `curl` or `wget` inside the KeyCape container image. +After the live KeyCape client is present, configure Railiance OpenBao to trust +KeyCape: + +```bash +bash ./configure-openbao-oidc.sh +``` + +The script prompts for a root/sudo-capable OpenBao token inside the pod TTY. +OpenBao currently requires `oidc_client_secret` for OIDC auth config, while +KeyCape's `openbao-admin` client is public PKCE and does not validate a +downstream client secret. The script therefore writes the explicit +non-secret compatibility value `keycape-public-pkce-compatibility-value`. +Replace that with a real managed client secret when KeyCape supports +confidential downstream clients. + Example entry (public client, PKCE, for a SPA): ```yaml clients: diff --git a/sso-mfa/k8s/keycape/configure-openbao-oidc.sh b/sso-mfa/k8s/keycape/configure-openbao-oidc.sh new file mode 100644 index 0000000..3c39d0e --- /dev/null +++ b/sso-mfa/k8s/keycape/configure-openbao-oidc.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Configure Railiance OpenBao to trust KeyCape for platform-admin OIDC login. +# The OpenBao token is prompted inside the pod TTY and is never placed on the +# local command line or stored by this script. + +set -euo pipefail + +KUBECTL="${KUBECTL:-kubectl}" +OPENBAO_NAMESPACE="${OPENBAO_NAMESPACE:-openbao}" +OPENBAO_POD="${OPENBAO_POD:-openbao-0}" + +"$KUBECTL" exec -it -n "$OPENBAO_NAMESPACE" "$OPENBAO_POD" -- sh -lc ' + set -eu + + restore_tty() { stty echo 2>/dev/null || true; } + trap restore_tty EXIT INT TERM + + printf "OpenBao root/sudo token: " >&2 + stty -echo + read -r BAO_TOKEN + stty echo + printf "\n" >&2 + export BAO_TOKEN + + 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 + } + + # OpenBao requires oidc_client_secret for OIDC auth config. The current + # KeyCape openbao-admin profile is public PKCE and does not validate this + # downstream client-secret field, so this compatibility value is not a + # protected secret. Replace this with a real managed client secret when + # KeyCape supports confidential downstream clients. + bao write auth/keycape/config \ + oidc_discovery_url="https://kc.coulomb.social" \ + oidc_client_id="openbao-admin" \ + oidc_client_secret="keycape-public-pkce-compatibility-value" \ + 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 + unset BAO_TOKEN +' diff --git a/tools/security-bootstrap-console/security_bootstrap_console.py b/tools/security-bootstrap-console/security_bootstrap_console.py index 7ca3d67..6d25bed 100755 --- a/tools/security-bootstrap-console/security_bootstrap_console.py +++ b/tools/security-bootstrap-console/security_bootstrap_console.py @@ -1349,7 +1349,7 @@ def admin_identity_command_payloads(data: dict[str, Any]) -> list[dict[str, str] 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." + auth_reason = "Operator action: run the helper script and enter a root/sudo-capable OpenBao token at the pod TTY 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." @@ -1371,57 +1371,7 @@ def admin_identity_command_payloads(data: dict[str, Any]) -> list[dict[str, str] "bash ./verify-openbao-client.sh\n" "NETKINGDOM_KEYCAPE_APPLY\n" ) - 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 </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" - "'" - ) + configure_command = f"bash {shlex.quote(str(KEYCAPE_OPENBAO_CLIENT_CONFIG.parent / 'configure-openbao-oidc.sh'))}" 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" @@ -1442,7 +1392,7 @@ rm -f /tmp/openbao-platform-admin-role.json /tmp/keycape-auth-enable.out /tmp/ke ), 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.", + "Create or update the auth/keycape mount and platform-admin role so KeyCape group claims map to OpenBao platform-admin policy. The helper uses a non-secret compatibility client-secret value because OpenBao requires the field while the current KeyCape client is public PKCE.", auth_state, auth_reason, configure_command, 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 05ec382..2422cf5 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 @@ -339,6 +339,12 @@ uses a short local `kubectl port-forward` plus Python HTTP request for OIDC discovery, avoiding assumptions about tools installed inside the KeyCape container. +**2026-05-26:** Fixed the OpenBao OIDC auth setup after OpenBao rejected an +empty `oidc_client_secret` even though the current KeyCape `openbao-admin` +client is public PKCE. The UI now points to a short helper script instead of a +long nested shell/JSON command, and the helper writes an explicit non-secret +compatibility value until KeyCape supports confidential downstream clients. + **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