Files
infra-keycloak/k8s
Stéphane Tailland c1eaead5d2 Declare the Catalogue agent as a Keycloak client
First of the six agents to need an identity. It exchanges the end user's
token for one narrowly-scoped token per MCP server, so `azp` names the
agent while `sub` stays the human — which is what makes the audit trail
able to answer "which agent asked, on whose behalf".

Deliberately narrower than the librechat client it is modelled on:

  * no browser flow at all. standardFlowEnabled, implicitFlow and
    directAccessGrants are off, because the agent never logs a user in —
    it receives an already-issued token over A2A. Any of those three
    would be a way to obtain a token it has no business obtaining.
  * no service account. It has no standing access of its own; everything
    it can reach, it reaches on behalf of a user who asked.
  * two optional scopes, mcp-catalog-audience and mcp-sales-audience, and
    nothing else. librechat carries all of them because it fronts every
    domain; an agent serving the product manager must not be able to mint
    a payments token.

Adds classic-models-agent-catalogue-audience, the scope whoever issues
the user's token requests so the agent appears in that token's audience.
Keycloak's Standard Token Exchange refuses otherwise, with "Client is not
within the token audience" — which reads as a permissions problem on the
target rather than a missing audience on the source.

Verified against the running realm: catalog and sales exchange
successfully and return aud=[mcp-catalog] / aud=[mcp-sales] with
azp=classic-models-agent-catalogue, while payments is refused with
invalid_scope. The confinement is enforced by Keycloak, not by the
agent's own restraint.

Note this file is a bootstrap seed, not the live realm: Keycloak starts
with --import-realm under IGNORE_EXISTING and logs "Realm
'classic-models' already exists. Import skipped", so editing it changes
nothing on a restart. The client and scope were created through the admin
API and recorded here so a fresh install matches. That divergence is
worth closing separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 08:57:50 +02:00
..
2026-08-25 07:30:20 +02:00

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-models est chargé au démarrage via l'option --import-realm depuis le ConfigMap monté 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/