diff --git a/sso-mfa/k8s/keycape/create-secrets.sh b/sso-mfa/k8s/keycape/create-secrets.sh index 8af86be..d097f74 100644 --- a/sso-mfa/k8s/keycape/create-secrets.sh +++ b/sso-mfa/k8s/keycape/create-secrets.sh @@ -131,6 +131,15 @@ clients: allowedScopes: ["openid", "profile", "email", "groups"] grantTypes: ["authorization_code"] clientType: "public" + - clientId: "rapp-qonto-client" + displayName: "rapp-qonto workload" + allowedScopes: ["qonto:read"] + grantTypes: ["client_credentials"] + clientType: "confidential" + secretRef: "env:KEYCAPE_RAPP_QONTO_CLIENT_SECRET" + serviceSubject: "rapp-qonto" + tenant: "tenant:friendly:binky" + roles: ["qonto-reader"] EOF ) diff --git a/sso-mfa/k8s/keycape/deployment.yaml b/sso-mfa/k8s/keycape/deployment.yaml index 7b31523..8ef7614 100644 --- a/sso-mfa/k8s/keycape/deployment.yaml +++ b/sso-mfa/k8s/keycape/deployment.yaml @@ -54,7 +54,7 @@ spec: # 2026-05-24: direct-imported into railiance01 k3s for the # bootstrap-console OIDC/MFA rollout. Use IfNotPresent while the # HTTP registry push/pull path is being cleaned up. - image: 92.205.130.254:32166/coulomb/key-cape:main-nonce-0601 + image: 92.205.130.254:32166/coulomb/key-cape:main-e877d27-2 imagePullPolicy: IfNotPresent ports: @@ -65,6 +65,11 @@ spec: env: - name: KEYCAPE_CONFIG value: /etc/keycape/config.yaml + - name: KEYCAPE_RAPP_QONTO_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: keycape-rapp-qonto-client + key: client-secret volumeMounts: # keycape-config Secret provides config.yaml and key.pem diff --git a/sso-mfa/k8s/keycape/openbao-client-config.py b/sso-mfa/k8s/keycape/openbao-client-config.py index 12e97eb..7df73dc 100644 --- a/sso-mfa/k8s/keycape/openbao-client-config.py +++ b/sso-mfa/k8s/keycape/openbao-client-config.py @@ -34,6 +34,18 @@ OPENBAO_CLIENT = { "clientType": "public", } +RAPP_QONTO_CLIENT = { + "clientId": "rapp-qonto-client", + "displayName": "rapp-qonto workload", + "allowedScopes": ["qonto:read"], + "grantTypes": ["client_credentials"], + "clientType": "confidential", + "secretRef": "env:KEYCAPE_RAPP_QONTO_CLIENT_SECRET", + "serviceSubject": "rapp-qonto", + "tenant": "tenant:friendly:binky", + "roles": ["qonto-reader"], +} + LLDAP_REQUIRED = { "userOU": "ou=people", "groupOU": "ou=groups", @@ -55,38 +67,38 @@ def load_config() -> dict[str, Any]: return config -def client_errors(config: dict[str, Any]) -> list[str]: +def client_errors(config: dict[str, Any], required: dict[str, Any]) -> list[str]: clients = config.get("clients") if not isinstance(clients, list): return ["clients must be a list"] target = next( - (client for client in clients if isinstance(client, dict) and client.get("clientId") == OPENBAO_CLIENT["clientId"]), + (client for client in clients if isinstance(client, dict) and client.get("clientId") == required["clientId"]), None, ) if target is None: - return ["missing openbao-admin client"] + return [f"missing {required['clientId']} client"] errors: list[str] = [] - for key in ("displayName", "clientType"): - if target.get(key) != OPENBAO_CLIENT[key]: - errors.append(f"{key} should be {OPENBAO_CLIENT[key]!r}") - for key in ("redirectUris", "allowedScopes", "grantTypes"): - missing = sorted(set(OPENBAO_CLIENT[key]) - set(target.get(key) or [])) - if missing: - errors.append(f"{key} missing: {', '.join(missing)}") + for key, expected in required.items(): + if key in ("redirectUris", "allowedScopes", "grantTypes", "roles"): + missing = sorted(set(expected) - set(target.get(key) or [])) + if missing: + errors.append(f"{required['clientId']}.{key} missing: {', '.join(missing)}") + elif target.get(key) != expected: + errors.append(f"{required['clientId']}.{key} should be {expected!r}") return errors -def upsert_client(config: dict[str, Any]) -> dict[str, Any]: +def upsert_client(config: dict[str, Any], required: dict[str, Any]) -> dict[str, Any]: clients = config.get("clients") if not isinstance(clients, list): clients = [] config["clients"] = clients for index, client in enumerate(clients): - if isinstance(client, dict) and client.get("clientId") == OPENBAO_CLIENT["clientId"]: - clients[index] = dict(OPENBAO_CLIENT) + if isinstance(client, dict) and client.get("clientId") == required["clientId"]: + clients[index] = dict(required) return config - clients.append(dict(OPENBAO_CLIENT)) + clients.append(dict(required)) return config @@ -111,7 +123,7 @@ def enforce_lldap_defaults(config: dict[str, Any]) -> dict[str, Any]: def render_patch(config: dict[str, Any]) -> None: - updated = enforce_lldap_defaults(upsert_client(config)) + updated = enforce_lldap_defaults(upsert_client(upsert_client(config, OPENBAO_CLIENT), RAPP_QONTO_CLIENT)) config_text = yaml.safe_dump(updated, sort_keys=False) encoded = base64.b64encode(config_text.encode("utf-8")).decode("ascii") json.dump({"data": {"config.yaml": encoded}}, sys.stdout, separators=(",", ":")) @@ -119,12 +131,12 @@ def render_patch(config: dict[str, Any]) -> None: def verify(config: dict[str, Any]) -> None: - errors = client_errors(config) + lldap_errors(config) + errors = client_errors(config, OPENBAO_CLIENT) + client_errors(config, RAPP_QONTO_CLIENT) + lldap_errors(config) if errors: for error in errors: print(f"[FAIL] {error}") raise SystemExit(1) - print("[PASS] openbao-admin client and LLDAP OU lookup settings are present") + print("[PASS] openbao-admin and rapp-qonto clients and LLDAP OU lookup settings are present") def main() -> None: