5 Commits

Author SHA1 Message Date
Stéphane Tailland 33d811fa68 Quote the two ConfigMap values inside the value
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 08:10:03 +02:00
Stéphane Tailland 0847e11713 Gate the n8n trigger endpoints on Keycloak
Every webhook on this instance was answering unauthenticated requests, the
stock watchlist included — it returned references and stock levels and let
anyone clear entries. The A2A facade in front of these workflows enforces a
bearer, but the workflows stayed reachable directly, facade bypassed.

Keycloak's userinfo is the authenticator: 200 for a valid bearer in the realm,
401 otherwise, which is the auth-request contract exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 07:56:13 +02:00
Stéphane Tailland 2bb6faa121 refactor: an autonomous run is a machine identity, not a mandate
Withdrawn: the offline_access mandate built an hour ago. An agent running on its
own account acts for itself. Nobody asked for the 06:00 run, so naming a person
as its principal was a fiction — and choosing which person was arbitrary, as the
first implementation showed by picking whichever test user was to hand.

It also cost more than it was worth. An offline token has no exp, dies only
after 30 days unused, is not rotated on use, and is revocable only by killing
that user's offline session: a permanent bearer credential standing in for a
consent nobody gave.

Now client_credentials, with the service account carrying read_only — the least
privilege the job needs. That is not a convention the workflow observes: the MCP
server maps the role to a consumer group at discovery, so tools/list under this
identity returns get-product and list-products and nothing else. create-product
is invisible rather than merely unused.

Nothing downstream changes. azp still names the agent, audiences are still
per-server, and policy still reads roles — a service account has roles like
anyone else.

The mandate client stays in the realm, unused, for the case where a person's own
authority genuinely is in play: Recouvrement's dunning, which is human-validated
already.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 17:51:18 +02:00
Stéphane Tailland 8f1f0245da feat: the offline_access mandate
The standing authority the autonomous runs act under: an offline refresh token
issued by a new classic-models-mandate client for the user who authorised the
agent, stored in Vault, redeemed at trigger time.

The mandate client holds no domain audience. It cannot reach data; it can only
say who authorised whom. Direct access grants on it is a lab shortcut, noted as
such — mandating an agent is a decision a person takes, and a password grant
records no consent where the authorization code flow would.

Verified end to end: refresh gives sub=pm-user azp=classic-models-mandate with
the agent in the audience, the agent exchanges it to azp=itself and
aud=mcp-catalog, and the MCP call answers 200. sub stays the person who
mandated, azp says which agent acted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 17:42:33 +02:00
Stéphane Tailland f9511fd15d feat: this estate's n8n deployment
The values that infra-n8n deliberately does not carry: our hostname, issuer,
Vault path and images, plus the two things that are ours alone — the lab CA the
workflows need to verify internal TLS, and the agents' Keycloak client secrets,
read from the realm's own Vault entry so there is one source per secret.

Self-managing after three applies, like the rest of the estate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 13:10:10 +02:00