6.2 KiB
ADR-0002: Session-Scope-Token-Format
- Status: accepted
- Datum: 2026-10-25
- Entscheider: CTO
- Betroffene Dokumente:
docs/architecture/v1.1.mdAbschnitte 3c, 4.5a, 5.1, 6, 7a.0, 9.1
Kontext und Problemstellung
Ein Agent, der eine Datenquelle liest, muss ihr beweisen, dass er für Benutzer Y in einer aktiven Session S liest — nicht nur, dass er irgendein Agent ist. Ohne diesen Nachweis gibt es keine saubere Grundlage für OPA-Regeln wie „Agent-Travel darf nur project_assignments seines eigenen Benutzers lesen, nicht fremder". Pocket ID allein liefert nur Agent-Identität (via client_credentials), keine Benutzer-Delegation.
Entscheidungsoptionen
- Option A — Vollständiger OAuth 2.0 Token Exchange (RFC 8693) in Pocket ID. Agent macht Token-Exchange gegen Pocket ID; dieser prüft Session und mintet ein Delegations-Token mit
act-Claim. - Option B — Eigener Session Scope Token Service (SSTS) in
trust-api. Agent präsentiert Pocket-ID-Access-Token + K8s-SA-Token; SSTS prüft doppelt, konsultiert OPA, stellt kurzlebigen JWT mitact-Claim aus, signiert mit eigenem Ed25519-Schlüssel. Datenquellen-Adapter validieren per JWKS offline. - Option C — Opaque Bearer Tokens mit zentralem Introspection-Endpunkt. Datenquellen rufen bei jedem Request
POST /introspectam trust-api.
Entscheidung
Option B. Der Session Scope Token Service läuft als Modul in trust-api und stellt JWTs im Format gemäß Anhang A dieser ADR aus, signiert mit einem eigenen Ed25519-Schlüssel.
Begründung
- Pocket ID macht keinen RFC-8693-Flow. Der Check im Pocket-ID-OIDC-Discovery zeigt
client_credentials, aber keinenurn:ietf:params:oauth:grant-type:token-exchange. Option A würde Pocket ID erweitern müssen oder einen zweiten IdP aufbauen — beides nicht gewünscht (siehedocs/architecture/v1.1.md3a). - Offline-Validierung ist wichtig. Datenquellen-Adapter sollen Tokens ohne Round-Trip zum trust-api prüfen können — JWKS + signierter JWT reichen. Option C erzwingt eine Netzwerkrunde pro Lese-Request und bindet Datenquellen-Verfügbarkeit an trust-api-Verfügbarkeit.
- OPA-Entscheidung bleibt zentral. Der SSTS konsultiert OPA genau einmal beim Ausstellen, nicht pro Query. Rego-Policies müssen nur im trust-api deployed sein.
Entscheidung — Anhang A: Token-Format
Header:
{ "alg": "EdDSA", "typ": "JWT", "kid": "sst-2026-q4" }
Payload (Claims):
{
"iss": "https://trust.basicstack.de",
"sub": "agent-travel",
"act": {
"iss": "https://auth.basicstack.de",
"sub": "user-12345",
"session_id": "SES-2026-10-25-abc"
},
"aud": "ds:expense-db-app",
"scope": {
"data_source": "ds:expense-db-app",
"resource": "project_assignments",
"filters": { "employee_id": "EMP-12345" },
"operation": "read"
},
"jti": "01HE2D3V6X7Y8Z9ABCDEFG",
"iat": 1735120000,
"exp": 1735120300,
"nbf": 1735120000,
"policy_version": "sha256:<rego-bundle-digest>"
}
Semantik:
iss= feste trust-api-Issuer-URL.sub=agent_id(OIDC-Client-Name in Pocket ID + Eintrag in Agent Registry).act= Delegations-Kontext nach RFC 8693 §4.1 (Semantik, nicht der volle Flow).act.subist der Benutzer,act.session_iddie Approval-Service-Session.aud= genau eine Zieldatenquelle. Mehrere Datenquellen → mehrere Tokens.scope= engster erlaubter Scope. Datenquellen-Adapter MUSSscopestrikt gegen Request matchen (keine erweiterten Filter, keine zusätzlichen Resources).jti= ULID oder UUID. Datenquellen-Adapter führenscope_token_logmitUNIQUE (data_source_id, jti)als optionalen Replay-Schutz zusätzlich zurexp-Prüfung.exp−iat= ≤ 300 Sekunden (5 Minuten). Begründung: Agenten holen Tokens pro Query, nicht pro Session.nbf=iat.policy_version= SHA-256 des zur Entscheidungszeit aktiven OPA-Rego-Bundles. Wandert später in die Query Attestation für Nachvollziehbarkeit.
Signatur: Ed25519. Schlüsselmaterial liegt in OpenBao Transit (transit/keys/session-scope-token-signer), rotiert alle 90 Tage. JWKS unter https://trust.basicstack.de/.well-known/jwks.json.
Begründung Detailentscheidungen
- EdDSA statt RS256. Konsistent zum restlichen Signatur-Stack (DSSE / in-toto nutzen Ed25519).
- 5-Minuten-TTL. Lang genug für Chat-Interaktionen mit einer Datenquelle; kurz genug, dass ein geleakter Token begrenzten Schaden anrichtet. Agenten holen bei Bedarf einen neuen;
/session-scope-tokensist bewusst billig. scope.filtersist Teil des Tokens. Alternative wäre „offene" Reads mit späterer Filterprüfung — würde aber erlauben, dass ein Agent erst eine breite Query stellt und dann nur bestimmte Zeilen weitergibt. Mit Filter im Token geht das nicht; die Query Attestation beweist zudem die gefilterten Parameter.aud = ds:.... Explizite Audience bindet den Token an eine Datenquelle. Verhindert, dass ein Token bei anderen Datenquellen „probiert" wird.
Konsequenzen
Positive
- OPA-Policies werden einmal pro Scope-Request ausgewertet, nicht pro Query-Zeile.
- Datenquellen können offline (ohne trust-api) authorisieren — bessere Entkopplung, besseres Fehlerverhalten.
- Delegationskette ist in Attestierungen vollständig nachvollziehbar:
session_scope_token_jtizeigt zurück auf Token-Ausstellung, die wiederum OPA-Policy-Version trägt.
Negative / Trade-offs
- Zusätzliche Signatur-Round-Trip (Agent →
/session-scope-tokens) pro Lese-Operation. Bei chattigen Workflows mit vielen kleinen Queries ggf. relevant — eine spätere ADR könnte Scope-Token mit mehreren erlaubten Resources diskutieren. - Datenquellen-Adapter brauchen JWT-Validierungs-Bibliotheken (für Go: trivial,
github.com/go-jose/go-jose).
Folgearbeiten / Open Points
- Client-Library (Go) für Agenten mit Caching eines Scope-Tokens bis
exp - 30s. - Rego-Testsuite für die drei Policy-Klassen (Transformation, Senke, Datenquelle).
- Monitoring: Rate von Scope-Token-Ausstellungen pro Agent/Benutzer; Anomalie-Erkennung.
Links
docs/architecture/v1.1.mdAbschnitte 3c, 4.5a, 5.1, 6, 7a.0, 9.1- RFC 8693 (OAuth 2.0 Token Exchange)
- Pocket ID OIDC Discovery:
https://auth.basicstack.de/.well-known/openid-configuration