f9900a5fb9
configmap-realm.yaml carried fourteen client secrets and five user
passwords in clear, in a tracked file. Five were there from its first
commit; I added the fourteenth yesterday by following the existing pattern
without questioning it. Making this file authoritative on every start did
not increase the exposure, but it did make the file the place those
secrets live for good — and it is inconsistent with the rest of the
estate, where infra-observability and kong-v2 both take theirs from Vault.
Keycloak resolves ${PLACEHOLDER} in a realm import file from the
environment, so the structure stays in git and the values move to Vault at
secrets/keycloak/classic-models-realm, reaching the import container
through External Secrets.
A store of its own, vault-keycloak, rather than reusing vault-kong-v2 or
vault-observability: each carries a policy scoped to its own path, so a
reader for one estate cannot see another's. Widening an existing policy
would have handed Kong's reader every client secret in the realm.
dataFrom.extract pulls the whole path, so adding a client means adding its
secret in Vault with no edit to the ExternalSecret.
Verified after the switch: the import logs "Realm 'classic-models' imported"
under OVERWRITE_EXISTING, and all three credential paths still work — the
broker's client_credentials, librechat with pm-user's password, and the
Catalogue agent's exchange reaching catalog-mcp with 200. Re-checked that
none of the nineteen original values appears anywhere in the file.
Note the values themselves were committed and are in this repo's history.
They are lab credentials behind a private CA with no real data, but they
should be rotated if that ever stops being true.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Infra Keycloak (Kubernetes / CaC)
Déploiement de Keycloak 26.6.0 et PostgreSQL 16 en Configuration-as-Code (CaC) sous Kubernetes dans le namespace keycloak.
Structure du Répertoire
k8s/
├── kustomization.yaml # Manifeste Kustomize principal
├── namespace.yaml # Namespace keycloak
├── secret.yaml # Secrets (Admin, DB, OIDC secrets)
├── secret.example.yaml # Gabarit des secrets
├── postgresql/
│ ├── pvc.yaml # Stockage persistant PostgreSQL (2Gi)
│ ├── statefulset.yaml # Base de données PostgreSQL 16
│ └── service.yaml # Service PostgreSQL (port 5432)
├── keycloak/
│ ├── configmap-realm.yaml # Realm classic-models (Configuration-as-Code)
│ ├── deployment.yaml # Déploiement Keycloak 26.6.0 (--import-realm)
│ └── service.yaml # Service Keycloak (ports 8080, 9000)
└── ingress/
├── tls.yaml # Secret TLS wildcard (*.sttlab.local)
└── ingress.yaml # Ingress NGINX (keycloak.sttlab.local)
Fonctionnalités Clés
- Version Keycloak :
quay.io/keycloak/keycloak:26.6.0(Runtime Quarkus). - Import Automatique du Realm (CaC) : Le realm
classic-modelsest chargé au démarrage via l'option--import-realmdepuis leConfigMapmonté dans/opt/keycloak/data/import/. - Token Exchange RFC 8693 : Activé via
KC_FEATURES: "token-exchange,admin-fine-grained-authz". - Clients Configurés :
librechat: Client OIDC pour le login et le flux OAuth MCP.mcp-catalog: Audience du serveur MCP.kong-token-exchange-broker: Échangeur de jetons RFC 8693 de Kong Gateway.catalog-api&classic-models-api: Audiences des services backend.
- Utilisateurs & Rôles :
admin-user(admin)pm-user(product_manager)ro-user(read_only)customer-user(customer)support-user(support)
Déploiement
k8s/secret.yaml n'est pas versionné : il porte les mots de passe réels (base
PostgreSQL, admin Keycloak, secrets clients OIDC). Le créer une fois depuis le
gabarit, puis renseigner chaque <CHANGE_ME> :
cp k8s/secret.example.yaml k8s/secret.yaml
$EDITOR k8s/secret.yaml
k8s/kustomization.yaml le référence, donc le déploiement échoue tant qu'il
est absent.
kubectl apply -k k8s/