Commit Graph

8 Commits

Author SHA1 Message Date
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
Stéphane Tailland 80a2a27ca9 Stop versioning the real secret.yaml
k8s/secret.yaml held the actual PostgreSQL password, Keycloak admin
password and OIDC client secrets, and .gitignore only covered .DS_Store
and *.tmp. Untrack it and ignore it, matching what infra-librechat already
does, and extend the ignore list to TLS key material.

kustomization.yaml still references the file, so the README now documents
creating it from secret.example.yaml — whose key set is identical — before
the first apply.

This only stops future leaks. The values remain in commit 6d3431c and on
the remote, so they must still be treated as compromised and rotated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 07:30:20 +02:00
Stéphane Tailland 01d2082f3b Send traces to Tempo and expose metrics to Prometheus
Point the OTel Java agent at Tempo instead of Jaeger, so Keycloak's traces
land in the same backend as the rest of the stack and correlate with the
Loki logs of the same requests.

Annotate the pod for scraping. KC_METRICS_ENABLED already served /metrics
on the management port, so this only makes Prometheus discover it — on
port 9000, never on the application port.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 07:27:14 +02:00
Stéphane Tailland aac85b2f7a Update of classic models realm config 2026-08-23 17:22:57 +02:00
Stéphane Tailland cf456f10a1 fix(realm): Persist client secret and realm CaC 2026-08-23 15:54:16 +02:00
Stéphane Tailland d5242f68ca fix(secrets): Update kong-token-exchange-broker secret to match Vault/ExternalSecret 2026-08-23 08:05:23 +02:00
Stéphane Tailland b9287761bf feat(telemetry): Enable OpenTelemetry distributed tracing and JSON logging with MDC 2026-08-23 07:45:23 +02:00
Stéphane Tailland 08200a7df0 feat(k8s): Add Keycloak 26.6.0 with PostgreSQL 16 and classic-models realm CaC 2026-08-23 07:40:40 +02:00