diff --git a/docs/runbook/workload-identity-0pt.md b/docs/runbook/workload-identity-0pt.md new file mode 100644 index 0000000..51026eb --- /dev/null +++ b/docs/runbook/workload-identity-0pt.md @@ -0,0 +1,78 @@ +# 0pt workload identity — målbild, nuläge och övergång + +Status: **design klar, provisionering blockerad** (2026-11-07). +Ägare: CL0UD (identitet) / 0pt (konsument). + +## 1. Nuläge + +0pt autentiserar maskin-till-maskin med en **delad hemlighet**: + +- `X-Internal-Token` mot `INTERNAL_TOKEN` (samma värde i engine och edge). +- Tenant-auktoritet kommer från serverkonfigurationen `INTERNAL_SERVICE_TENANTS` + (se `src/auth/internal-service-tenant.ts`), aldrig från en klientheader. +- Tom allowlist ⇒ ingen tenant-auktoritet ⇒ tenant-scopade skrivningar 403. + +Det här är säkert men grovkornigt: en enda hemlighet representerar hela +engine-processen, och varje ny konsument kräver en manuell allowlist-post. + +## 2. Målbild (CL0UD workload identity) + +CL0UD äger redan maskineriet: + +- `POST /account/service-accounts` skapar en service account (identitet). +- `POST /account/service-accounts/{id}/workload-token` mints en **kortlivad** + (10 min default, 1 h max) workload-token vars `capabilities` är en delmängd av + service accountens `platform.cl0ud_service_account_scopes.allowedCapabilities`. +- `@siax/platform-core` `auth/workload.ts` är verifieringsadaptern; publika + nycklar via JWKS. +- `packages/capability-registry/src/workload-identities.ts` deklarerar + arbetsbelastningsnamn och `aud:*`-audiences. Modulen är uttryckligen **inte** + provisionerad och **inte** inkopplad i utfärdande eller verifiering än. + +För 0pt innebär det: + +| Egenskap | Idag | Mål | +|---|---|---| +| Credential | delad `INTERNAL_TOKEN` | CL0UD-workload-token, `aud:0pt`, 10 min | +| Tenant | serverkonfigurerad allowlist | `tenant_id`-claim i verifierad token | +| Capabilities | route-lista i middleware | `capabilities`-claim (t.ex. `0pt.canonical.write`) | +| Rotation | manuell | automatisk (kortlivad token) | +| Spårbarhet | delad hemlighet | per-arbetsbelastning (`0pt-engine`, `site0-pipeline`) | + +## 3. Integrationsdesign (när provisionering är möjlig) + +1. **Verifiering före route-logik.** Middleware accepterar + `Authorization: Bearer ` för `INTERNAL_SERVICE_ROUTES` om + signaturen verifieras mot CL0UD JWKS, `aud` innehåller `0pt` och `exp` är + giltig. Ingen klientheader får bidra till tenant. +2. **Tenant från claim, inte från allowlist.** `tenant_id` i den verifierade + tokenen sätts som `x-0pt-verified-tenant`; `INTERNAL_SERVICE_TENANTS` + används endast för legacy `X-Internal-Token`. +3. **Capabilities per route.** Route-metadata (`0pt.canonical.write`, + `0pt.quality.run`) jämförs med tokenens `capabilities`. Saknad capability ⇒ + 403, inte 401 (identiteten är giltig, behörigheten räcker inte). +4. **Dual-accept under övergång.** Båda credentialtyperna accepteras tills + engine och site0-pipeline mintar workload-tokens; därefter tas + `X-Internal-Token` bort i en egen release med negativt test. +5. **Negativa tester i samma svit som idag** + (`tests/auth/internal-token-tenant-scope.test.ts`): fel `aud` ⇒ 401, utgången + token ⇒ 401, tenant utanför claim ⇒ 403, saknad capability ⇒ 403. + +## 4. Blockerare + +- `/account/service-accounts` svarar **307 → /login** utan operator-session; + ingen giltig `SIAX_TOKEN` finns i Infisical-miljön. +- Zitadel admin-PAT (`/opt/siax-access/zitadel-admin-pat`) är invalid + (AUTH-7fs1e), så nya machine users kan inte mintas via det flödet. +- `workload-identities.ts` är deklaration-only: ingen identitet är + `provisioned: true`. + +Åtgärd kräver en operatörsinloggning i CL0UD-konsolen (eller en ny giltig +admin-PAT) och är därför **EXTERNALLY_BLOCKED** från agentsidan. + +## 5. Vad som inte ska göras + +- Ingen egen JWT-signering i 0pt. +- Ingen parallell identitetsplattform. +- Ingen klientheader som bär tenant. +- Ingen långlivad delad hemlighet som "tillfällig" lösning utan slutdatum.