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>
This commit is contained in:
@@ -22,3 +22,9 @@ spec:
|
|||||||
remoteRef:
|
remoteRef:
|
||||||
key: keycloak/classic-models-realm
|
key: keycloak/classic-models-realm
|
||||||
property: KC_SECRET_CLASSIC_MODELS_AGENT_APPROVISIONNEMENT
|
property: KC_SECRET_CLASSIC_MODELS_AGENT_APPROVISIONNEMENT
|
||||||
|
# Redeems a mandate. Held by the workflow rather than by the adapter because
|
||||||
|
# it is the workflow that acts as the agent.
|
||||||
|
- secretKey: MANDATE_CLIENT_SECRET
|
||||||
|
remoteRef:
|
||||||
|
key: keycloak/classic-models-realm
|
||||||
|
property: KC_SECRET_MANDATE
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ spec:
|
|||||||
# The agents' Keycloak client secrets, so a workflow can exchange tokens
|
# The agents' Keycloak client secrets, so a workflow can exchange tokens
|
||||||
# as the agent it represents. Created by agent-secrets.yaml beside this.
|
# as the agent it represents. Created by agent-secrets.yaml beside this.
|
||||||
EXTRA_ENV_SECRET: n8n-agent-secrets
|
EXTRA_ENV_SECRET: n8n-agent-secrets
|
||||||
|
EXTRA_ENV_SECRET_2: n8n-mandate-secrets
|
||||||
EXTRA_CA_CONFIGMAP: sttlab-ca
|
EXTRA_CA_CONFIGMAP: sttlab-ca
|
||||||
# "false" so a workflow can read $env.<AGENT>_CLIENT_SECRET. It means any
|
# "false" so a workflow can read $env.<AGENT>_CLIENT_SECRET. It means any
|
||||||
# workflow on this instance can read any variable in the pod — acceptable
|
# workflow on this instance can read any variable in the pod — acceptable
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# The standing authority the autonomous runs act under.
|
||||||
|
#
|
||||||
|
# A mandate is an `offline_access` refresh token, issued by the
|
||||||
|
# classic-models-mandate client for the user who authorised the agent. It does
|
||||||
|
# not expire, which is exactly why it is here and not in a workflow: it is
|
||||||
|
# revoked by that user's session being revoked in Keycloak, and by nothing else.
|
||||||
|
#
|
||||||
|
# Two ExternalSecrets rather than one, because they answer to different owners:
|
||||||
|
# the mandate is the agent estate's, the client secret that redeems it is the
|
||||||
|
# realm's.
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: n8n-mandate-secrets
|
||||||
|
namespace: n8n
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
name: vault-n8n
|
||||||
|
target:
|
||||||
|
name: n8n-mandate-secrets
|
||||||
|
dataFrom:
|
||||||
|
- extract:
|
||||||
|
key: n8n/mandates
|
||||||
Reference in New Issue
Block a user