From efbdab465278014fc892e574ef4877b4bddd918a Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 18 Jun 2026 01:23:02 +0200 Subject: [PATCH] feat(keycape): add netkingdom OIDC mount and bao.coulomb.social callbacks Configure OpenBao auth for both netkingdom and keycape mounts with browser redirect URIs; update verify scripts and runtime architecture notes. --- docs/NetkingdomRuntimeArchitecture.md | 10 ++- sso-mfa/k8s/keycape/README.md | 21 +++++-- sso-mfa/k8s/keycape/configure-openbao-oidc.sh | 42 +++++++------ sso-mfa/k8s/keycape/create-secrets.sh | 4 +- sso-mfa/k8s/keycape/openbao-client-config.py | 4 +- sso-mfa/k8s/keycape/patch-openbao-client.sh | 3 +- sso-mfa/k8s/keycape/verify-openbao-client.sh | 61 +++++++++++-------- sso-mfa/k8s/verify-t07.sh | 4 +- 8 files changed, 95 insertions(+), 54 deletions(-) diff --git a/docs/NetkingdomRuntimeArchitecture.md b/docs/NetkingdomRuntimeArchitecture.md index dc30e80..d267caa 100644 --- a/docs/NetkingdomRuntimeArchitecture.md +++ b/docs/NetkingdomRuntimeArchitecture.md @@ -24,8 +24,8 @@ Recursive trust rule: Normal tenant admin (even Coulomb) must never suffice to a - MFA/Token: privacyIDEA (self-service enrollment for TOTP; pi-admin for setup/repair; used for assurance on privileged actions). - OIDC Provider: KeyCape (issuer https://kc.coulomb.social; conforms to NetKingdom IAM Profile v0.2). - KeyCape issues tokens with required claims: tenant, principal_type, groups, roles, scope/scp, assurance. - - Registered clients include: netkingdom-bootstrap-console (for console OIDC login), openbao-admin (for OpenBao OIDC auth). - - Redirects: http://localhost:8250/oidc/callback, http://127.0.0.1:8250/oidc/callback. +- Registered clients include: netkingdom-bootstrap-console (for console OIDC login), openbao-admin (for OpenBao OIDC auth). + - Redirects: http://localhost:8250/oidc/callback, http://127.0.0.1:8250/oidc/callback, https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback, https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback. - Groups/roles for bootstrap: net-kingdom-admins (for platform-admin OpenBao policy), net-kingdom-users (for scoped non-root). - platform-root / king credential: dedicated LLDAP user (separate from personal accounts like tegwick). Password in operator password safe; TOTP via privacyIDEA; roles include platform-root-custodian, openbao-admin, identity-admin. @@ -56,6 +56,10 @@ Authelia acts as the SSO proxy/authenticator in lightweight mode, fronting LLDAP - Delivery: direct clients, External Secrets Operator -> K8s Secrets, CSI mounts. - Auth: OIDC/JWT against KeyCape (maps claims/groups to policies, e.g. platform-admin for net-kingdom-admins group). - platform-root can obtain platform-admin policy via KeyCape/MFA (proven in 0015/0017). +- Browser operator access uses `https://bao.coulomb.social` for the OpenBao UI + and redirects to KeyCape at `kc.coulomb.social`; use auth path `netkingdom` + and role `platform-admin`, not root-token browser login. The `keycape` auth + path is retained only as a compatibility alias. - Root token: revoked/dispositioned after init; used only for bootstrap/break-glass. Unseal keys in custody (age/SOPS protected, offline packets, king credential). **Bootstrap to runtime transition:** @@ -202,4 +206,4 @@ See NET-WP-0019 and sso-mfa/k8s/lldap/dry-run-nonroot-user.sh: - NET-WP-0017, 0019 workplans + their evidence - DECISIONS.md, ADRs (e.g. 0007, 0010), canon/standards/iam-profile_v0.2.md -This document will be updated as T03 retrospective, T05 guide, T06/T08 work, and T09 risk assessment proceed. It is the single source for "what the running system actually is" for rebuild guidance. \ No newline at end of file +This document will be updated as T03 retrospective, T05 guide, T06/T08 work, and T09 risk assessment proceed. It is the single source for "what the running system actually is" for rebuild guidance. diff --git a/sso-mfa/k8s/keycape/README.md b/sso-mfa/k8s/keycape/README.md index 2b019cd..7f5a960 100644 --- a/sso-mfa/k8s/keycape/README.md +++ b/sso-mfa/k8s/keycape/README.md @@ -117,7 +117,7 @@ keeps KeyCape from using the admin token-list API as the MFA-required check. Downstream applications are registered in the `clients:` block in `keycape/create-secrets.sh`. The NetKingdom bootstrap console and Railiance -OpenBao admin CLI clients are code-defined there; operators should not create +OpenBao admin clients are code-defined there; operators should not create those clients manually in a separate UI. After changing the block: ```bash @@ -126,16 +126,20 @@ kubectl rollout restart deployment/keycape -n sso ``` The `openbao-admin` client is intentionally a public PKCE client for the -current local operator CLI flow. It registers the OpenBao CLI callback URIs: +current operator flow. It registers both the OpenBao CLI callback URIs and the +browser UI callbacks for `bao.coulomb.social`: ```text http://localhost:8250/oidc/callback http://127.0.0.1:8250/oidc/callback +https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback +https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback ``` -OpenBao browser UI callbacks are not registered yet because Railiance OpenBao -currently has public ingress disabled. Add exact UI callback URIs only after -the OpenBao UI exposure model is explicitly designed. +The browser UI callback is paired with the Railiance Platform OpenBao ingress +at `https://bao.coulomb.social`. The preferred browser auth mount is +`netkingdom`; `keycape` remains a compatibility alias. Keep the localhost +callbacks unless there is a separate decision to retire CLI login. To add or refresh only the OpenBao client in a live cluster, do not decrypt the bootstrap secret bundle and do not re-run the full secret generator. Patch the @@ -161,6 +165,13 @@ KeyCape: bash ./configure-openbao-oidc.sh ``` +That script registers the browser UI callbacks on the OpenBao +`auth/netkingdom/role/platform-admin` role and the compatibility +`auth/keycape/role/platform-admin` role. Browser operators should use the +OpenBao UI at `https://bao.coulomb.social`, leave namespace blank, choose +OIDC, set mount path `netkingdom`, and use role `platform-admin`; root-token +browser use is outside the approved operator path. + 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 diff --git a/sso-mfa/k8s/keycape/configure-openbao-oidc.sh b/sso-mfa/k8s/keycape/configure-openbao-oidc.sh index 07f8772..c3be11a 100644 --- a/sso-mfa/k8s/keycape/configure-openbao-oidc.sh +++ b/sso-mfa/k8s/keycape/configure-openbao-oidc.sh @@ -22,25 +22,12 @@ OPENBAO_POD="${OPENBAO_POD:-openbao-0}" 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" + OPENBAO_OIDC_MOUNTS="netkingdom keycape" # Keep array-valued groups in groups_claim/bound_claims only. OpenBao # claim_mappings copy scalar claim values into metadata and will fail if the @@ -53,7 +40,9 @@ OPENBAO_POD="${OPENBAO_POD:-openbao-0}" "oidc_scopes": ["openid", "profile", "email", "groups"], "allowed_redirect_uris": [ "http://localhost:8250/oidc/callback", - "http://127.0.0.1:8250/oidc/callback" + "http://127.0.0.1:8250/oidc/callback", + "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback", + "https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback" ], "bound_claims": { "groups": ["net-kingdom-admins"] @@ -67,7 +56,26 @@ OPENBAO_POD="${OPENBAO_POD:-openbao-0}" } 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 + for mount in $OPENBAO_OIDC_MOUNTS; do + bao auth enable -path="$mount" oidc >/tmp/openbao-${mount}-auth-enable.out 2>/tmp/openbao-${mount}-auth-enable.err || { + if grep -q "path is already in use" /tmp/openbao-${mount}-auth-enable.err; then + printf "auth/%s already exists\n" "$mount" >&2 + else + cat /tmp/openbao-${mount}-auth-enable.err >&2 + exit 1 + fi + } + + bao write "auth/${mount}/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" + + bao write "auth/${mount}/role/platform-admin" @/tmp/openbao-platform-admin-role.json + printf "configured auth/%s/role/platform-admin\n" "$mount" >&2 + done + + rm -f /tmp/openbao-platform-admin-role.json /tmp/openbao-*-auth-enable.out /tmp/openbao-*-auth-enable.err unset BAO_TOKEN ' diff --git a/sso-mfa/k8s/keycape/create-secrets.sh b/sso-mfa/k8s/keycape/create-secrets.sh index 5f330a8..8af86be 100644 --- a/sso-mfa/k8s/keycape/create-secrets.sh +++ b/sso-mfa/k8s/keycape/create-secrets.sh @@ -122,10 +122,12 @@ clients: grantTypes: ["authorization_code"] clientType: "public" - clientId: "openbao-admin" - displayName: "Railiance OpenBao Admin CLI" + displayName: "Railiance OpenBao Admin" redirectUris: - "http://localhost:8250/oidc/callback" - "http://127.0.0.1:8250/oidc/callback" + - "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback" + - "https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback" allowedScopes: ["openid", "profile", "email", "groups"] grantTypes: ["authorization_code"] clientType: "public" diff --git a/sso-mfa/k8s/keycape/openbao-client-config.py b/sso-mfa/k8s/keycape/openbao-client-config.py index 8fb69af..12e97eb 100644 --- a/sso-mfa/k8s/keycape/openbao-client-config.py +++ b/sso-mfa/k8s/keycape/openbao-client-config.py @@ -22,10 +22,12 @@ except ImportError as exc: # pragma: no cover - operator environment guard OPENBAO_CLIENT = { "clientId": "openbao-admin", - "displayName": "Railiance OpenBao Admin CLI", + "displayName": "Railiance OpenBao Admin", "redirectUris": [ "http://localhost:8250/oidc/callback", "http://127.0.0.1:8250/oidc/callback", + "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback", + "https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback", ], "allowedScopes": ["openid", "profile", "email", "groups"], "grantTypes": ["authorization_code"], diff --git a/sso-mfa/k8s/keycape/patch-openbao-client.sh b/sso-mfa/k8s/keycape/patch-openbao-client.sh index 50933cd..3ce2ac3 100644 --- a/sso-mfa/k8s/keycape/patch-openbao-client.sh +++ b/sso-mfa/k8s/keycape/patch-openbao-client.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Patch the live KeyCape config Secret with non-secret code-defined settings: -# the OpenBao CLI client and LLDAP OU lookup paths. +# the OpenBao admin client, browser auth mount callbacks, and LLDAP OU lookup +# paths. # This does not require decrypted bootstrap secrets and does not print existing # Secret values. diff --git a/sso-mfa/k8s/keycape/verify-openbao-client.sh b/sso-mfa/k8s/keycape/verify-openbao-client.sh index 55d58c7..88098bb 100755 --- a/sso-mfa/k8s/keycape/verify-openbao-client.sh +++ b/sso-mfa/k8s/keycape/verify-openbao-client.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Verify the live KeyCape config carries the OpenBao CLI client and KeyCape is -# serving OIDC discovery after rollout. +# Verify the live KeyCape config carries the OpenBao admin client and KeyCape +# is serving OIDC discovery after rollout. set -euo pipefail @@ -15,30 +15,41 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PUBLIC_URL="${KEYCAPE_PUBLIC_URL:-https://kc.coulomb.social}" PUBLIC_AUTHORIZE_URL="${PUBLIC_URL%/}/authorize" -PUBLIC_PROBE_OUTPUT=$( - curl -sS -i -G "$PUBLIC_AUTHORIZE_URL" \ - --data-urlencode "client_id=openbao-admin" \ - --data-urlencode "redirect_uri=http://localhost:8250/oidc/callback" \ - --data-urlencode "response_type=code" \ - --data-urlencode "scope=openid profile email groups" \ - --data-urlencode "state=netkingdom-openbao-client-probe" \ - --data-urlencode "code_challenge=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ" \ - --data-urlencode "code_challenge_method=S256" \ - 2>&1 || true -) -if grep -q '"unknown client_id"' <<<"$PUBLIC_PROBE_OUTPUT"; then - echo "[FAIL] $PUBLIC_AUTHORIZE_URL rejects openbao-admin with unknown client_id" >&2 - echo " Check DNS for kc.coulomb.social and ensure it reaches the KeyCape ingress that was patched." >&2 - exit 1 -fi -if ! grep -qE '^HTTP/[0-9.]+ 302 ' <<<"$PUBLIC_PROBE_OUTPUT"; then - echo "[FAIL] $PUBLIC_AUTHORIZE_URL did not return the expected OIDC redirect for openbao-admin" >&2 - echo " First response:" >&2 - sed -n '1,12p' <<<"$PUBLIC_PROBE_OUTPUT" >&2 - exit 1 -fi -echo "[PASS] public KeyCape authorize endpoint recognizes openbao-admin" +probe_redirect() { + local label="$1" + local redirect_uri="$2" + local output + output=$( + curl -sS -i -G "$PUBLIC_AUTHORIZE_URL" \ + --data-urlencode "client_id=openbao-admin" \ + --data-urlencode "redirect_uri=$redirect_uri" \ + --data-urlencode "response_type=code" \ + --data-urlencode "scope=openid profile email groups" \ + --data-urlencode "state=netkingdom-openbao-client-probe" \ + --data-urlencode "code_challenge=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ" \ + --data-urlencode "code_challenge_method=S256" \ + 2>&1 || true + ) + + if grep -q '"unknown client_id"' <<<"$output"; then + echo "[FAIL] $PUBLIC_AUTHORIZE_URL rejects openbao-admin with unknown client_id" >&2 + echo " Check DNS for kc.coulomb.social and ensure it reaches the KeyCape ingress that was patched." >&2 + exit 1 + fi + if ! grep -qE '^HTTP/[0-9.]+ 302 ' <<<"$output"; then + echo "[FAIL] $PUBLIC_AUTHORIZE_URL did not accept the $label redirect URI for openbao-admin" >&2 + echo " Redirect URI: $redirect_uri" >&2 + echo " First response:" >&2 + sed -n '1,12p' <<<"$output" >&2 + exit 1 + fi + echo "[PASS] public KeyCape authorize endpoint accepts $label redirect" +} + +probe_redirect "CLI" "http://localhost:8250/oidc/callback" +probe_redirect "browser UI netkingdom mount" "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback" +probe_redirect "browser UI keycape compatibility mount" "https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback" KC_POD=$("$KUBECTL" get pod -n "$NAMESPACE" \ -l app.kubernetes.io/name=keycape \ diff --git a/sso-mfa/k8s/verify-t07.sh b/sso-mfa/k8s/verify-t07.sh index 509247f..893530f 100755 --- a/sso-mfa/k8s/verify-t07.sh +++ b/sso-mfa/k8s/verify-t07.sh @@ -187,6 +187,8 @@ if not target: required_redirects = { "http://localhost:8250/oidc/callback", "http://127.0.0.1:8250/oidc/callback", + "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback", + "https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback", } required_scopes = {"openid", "profile", "email", "groups"} missing_redirects = sorted(required_redirects - set(target.get("redirectUris") or [])) @@ -200,7 +202,7 @@ if missing_redirects: if missing_scopes: print("openbao-admin missing scope(s): " + ", ".join(missing_scopes)) raise SystemExit(5) -print("openbao-admin client has local CLI redirects and required scopes") +print("openbao-admin client has CLI/browser redirects and required scopes") ' 2>/dev/null || echo "missing or invalid openbao-admin client") if [[ "$OPENBAO_CLIENT_CHECK" == openbao-admin* ]]; then pass "$OPENBAO_CLIENT_CHECK"