diff --git a/sso-mfa/k8s/keycape/README.md b/sso-mfa/k8s/keycape/README.md index fd05f03..b3b711b 100644 --- a/sso-mfa/k8s/keycape/README.md +++ b/sso-mfa/k8s/keycape/README.md @@ -128,22 +128,21 @@ kubectl rollout restart deployment/keycape -n sso ``` The `openbao-admin` client is intentionally a public PKCE client for the -current operator flow. It registers both the OpenBao CLI callback URIs and the -browser UI callbacks for `bao.coulomb.social`: +current operator flow. It registers the OpenBao CLI callback URIs and the +operator-tunneled browser UI callback: ```text http://localhost:8250/oidc/callback http://127.0.0.1:8250/oidc/callback http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback -https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback -https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback ``` -The preferred browser UI callback is paired with the named Railiance Platform -operator tunnel at `http://127.0.0.1:18200`. The public callback remains during -the bounded listener rollback window. The preferred browser auth mount is -`netkingdom`; `keycape` remains a compatibility alias. Keep the CLI callbacks -unless there is a separate decision to retire CLI login. +The browser UI callback is paired with the named Railiance Platform operator +tunnel at `http://127.0.0.1:18200`. The public `bao.coulomb.social` callbacks +were retired with that name on 2026-09-15; `openbao-client-config.py` and +`verify-t07.sh` fail if either is registered again. The preferred browser auth +mount is `netkingdom`; `keycape` remains a compatibility alias. Keep the CLI +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 diff --git a/sso-mfa/k8s/keycape/create-secrets.sh b/sso-mfa/k8s/keycape/create-secrets.sh index 3f26c3d..54ae5e4 100644 --- a/sso-mfa/k8s/keycape/create-secrets.sh +++ b/sso-mfa/k8s/keycape/create-secrets.sh @@ -140,8 +140,6 @@ clients: - "http://localhost:8250/oidc/callback" - "http://127.0.0.1:8250/oidc/callback" - "http://127.0.0.1:18200/ui/vault/auth/netkingdom/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 80899bb..9ed3562 100644 --- a/sso-mfa/k8s/keycape/openbao-client-config.py +++ b/sso-mfa/k8s/keycape/openbao-client-config.py @@ -31,14 +31,18 @@ OPENBAO_CLIENT = { "http://localhost:8250/oidc/callback", "http://127.0.0.1:8250/oidc/callback", "http://127.0.0.1:18200/ui/vault/auth/netkingdom/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", } +# bao.coulomb.social was retired on 2026-09-15; its callbacks must not return. +RETIRED_OPENBAO_REDIRECT_URIS = ( + "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback", + "https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback", +) + RAPP_QONTO_CLIENT = { "clientId": "rapp-qonto-client", "displayName": "rapp-qonto workload", @@ -223,6 +227,16 @@ def client_errors(config: dict[str, Any], required: dict[str, Any]) -> list[str] return errors +def retired_redirect_errors(config: dict[str, Any]) -> list[str]: + clients = config.get("clients") if isinstance(config.get("clients"), list) else [] + target = next( + (client for client in clients if isinstance(client, dict) and client.get("clientId") == OPENBAO_CLIENT["clientId"]), + {}, + ) + retired = sorted(set(RETIRED_OPENBAO_REDIRECT_URIS) & set(target.get("redirectUris") or [])) + return [f"{OPENBAO_CLIENT['clientId']}.redirectUris retired: {', '.join(retired)}"] if retired else [] + + def upsert_client(config: dict[str, Any], required: dict[str, Any]) -> dict[str, Any]: clients = config.get("clients") if not isinstance(clients, list): @@ -265,7 +279,7 @@ def render_patch(config: dict[str, Any]) -> None: def verify(config: dict[str, Any]) -> None: - errors = client_errors(config, OPENBAO_CLIENT) + client_errors(config, RAPP_QONTO_CLIENT) + lldap_errors(config) + errors = client_errors(config, OPENBAO_CLIENT) + client_errors(config, RAPP_QONTO_CLIENT) + retired_redirect_errors(config) + lldap_errors(config) if errors: for error in errors: print(f"[FAIL] {error}") diff --git a/sso-mfa/k8s/keycape/test_openbao_client_config.py b/sso-mfa/k8s/keycape/test_openbao_client_config.py index f3f644f..2d75c77 100644 --- a/sso-mfa/k8s/keycape/test_openbao_client_config.py +++ b/sso-mfa/k8s/keycape/test_openbao_client_config.py @@ -32,6 +32,25 @@ class OpenBaoClientConfigTest(unittest.TestCase): [f"openbao-admin.redirectUris missing: {LOOPBACK_CALLBACK}"], ) + def test_retired_public_callbacks_are_not_declared(self) -> None: + for uri in MODULE.RETIRED_OPENBAO_REDIRECT_URIS: + self.assertNotIn(uri, MODULE.OPENBAO_CLIENT["redirectUris"]) + + def test_retired_public_callback_fails_verification(self) -> None: + client = dict(MODULE.OPENBAO_CLIENT) + retired = MODULE.RETIRED_OPENBAO_REDIRECT_URIS[0] + client["redirectUris"] = [*MODULE.OPENBAO_CLIENT["redirectUris"], retired] + self.assertEqual( + MODULE.retired_redirect_errors({"clients": [client]}), + [f"openbao-admin.redirectUris retired: {retired}"], + ) + + def test_upsert_removes_retired_callbacks(self) -> None: + stale = dict(MODULE.OPENBAO_CLIENT) + stale["redirectUris"] = [*MODULE.OPENBAO_CLIENT["redirectUris"], *MODULE.RETIRED_OPENBAO_REDIRECT_URIS] + updated = MODULE.upsert_client({"clients": [stale]}, MODULE.OPENBAO_CLIENT) + self.assertEqual(MODULE.retired_redirect_errors(updated), []) + def test_upsert_preserves_unrelated_clients(self) -> None: config = {"clients": [{"clientId": "unrelated", "clientType": "public"}]} updated = MODULE.upsert_client(config, MODULE.OPENBAO_CLIENT) diff --git a/sso-mfa/k8s/keycape/verify-openbao-client.sh b/sso-mfa/k8s/keycape/verify-openbao-client.sh index 924d19e..c4ee19f 100755 --- a/sso-mfa/k8s/keycape/verify-openbao-client.sh +++ b/sso-mfa/k8s/keycape/verify-openbao-client.sh @@ -49,8 +49,6 @@ probe_redirect() { probe_redirect "CLI" "http://localhost:8250/oidc/callback" probe_redirect "operator-tunneled browser UI netkingdom mount" "http://127.0.0.1:18200/ui/vault/auth/netkingdom/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 911581b..894a09c 100755 --- a/sso-mfa/k8s/verify-t07.sh +++ b/sso-mfa/k8s/verify-t07.sh @@ -188,8 +188,6 @@ required_redirects = { "http://localhost:8250/oidc/callback", "http://127.0.0.1:8250/oidc/callback", "http://127.0.0.1:18200/ui/vault/auth/netkingdom/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,6 +198,13 @@ if target.get("clientType") != "public": if missing_redirects: print("openbao-admin missing redirect URI(s): " + ", ".join(missing_redirects)) raise SystemExit(4) +retired_redirects = sorted({ + "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback", + "https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback", +} & set(target.get("redirectUris") or [])) +if retired_redirects: + print("openbao-admin still registers retired redirect URI(s): " + ", ".join(retired_redirects)) + raise SystemExit(6) if missing_scopes: print("openbao-admin missing scope(s): " + ", ".join(missing_scopes)) raise SystemExit(5)