ProofedMesh/docs/adr/0002-session-scope-token-format.md

103 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-0002: Session-Scope-Token-Format
- **Status:** accepted
- **Datum:** 2026-10-25
- **Entscheider:** CTO
- **Betroffene Dokumente:** `docs/architecture/v1.1.md` Abschnitte 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 mit `act`-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 /introspect` am 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 keinen `urn: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 (siehe `docs/architecture/v1.1.md` 3a).
- **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:
```json
{ "alg": "EdDSA", "typ": "JWT", "kid": "sst-2026-q4" }
```
Payload (Claims):
```json
{
"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.sub` ist der Benutzer, `act.session_id` die Approval-Service-Session.
- `aud` = genau *eine* Zieldatenquelle. Mehrere Datenquellen → mehrere Tokens.
- `scope` = engster erlaubter Scope. Datenquellen-Adapter MUSS `scope` strikt gegen Request matchen (keine erweiterten Filter, keine zusätzlichen Resources).
- `jti` = ULID oder UUID. Datenquellen-Adapter führen `scope_token_log` mit `UNIQUE (data_source_id, jti)` als optionalen Replay-Schutz zusätzlich zur `exp`-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-tokens` ist bewusst billig.
- **`scope.filters` ist 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_jti` zeigt 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.md` Abschnitte 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`