# Registry Token Service architecture ## Overview Registry Token Service issues short-lived Docker registry bearer tokens for private or mirrored registries. It is designed for offline/self-hosted operation and enforces plan/licence constraints before minting any registry token. The service is intentionally small: - One HTTP endpoint: `GET /token` - Stateless authorization decisions based on (a) Authority-issued identity token claims and (b) local configuration ## Primary responsibilities - Validate caller identity using Authority-issued tokens (deployment profile may use bearer-only, DPoP, and/or mTLS). - Authorize requested registry scopes against a configured plan catalogue. - Deny issuance for revoked licences. - Mint a Docker-registry-compatible JWT with an `access` claim covering the permitted repository actions. - Emit deterministic observability signals (metrics + structured logs) for audits and ops. ## Runtime components **Minimal API host** - Project: `src/Registry/StellaOps.Registry.TokenService` - Endpoints: - `GET /token` (authorized) - `GET /healthz` (unauthenticated liveness) **Auth integration** - Resource server validation is configured from `RegistryTokenService:Authority`. - Authorization policy name: `registry.token.issue` - Required scopes default to `registry.token.issue` (configurable via `RegistryTokenService:Authority:RequiredScopes`). **Plan registry (authorization rules)** - The caller's plan is read from `stellaops:plan` claim (fallback: configured `DefaultPlan`). - Licence revocation uses `stellaops:license` claim and configured `RevokedLicenses`. - Plan rules match repositories by wildcard pattern (`*`) and validate requested actions (`pull`, `push`, etc.) as a subset of allowed actions. **Token issuer** - Tokens are signed with an RSA private key loaded from `RegistryTokenService:Signing:KeyPath` (PEM or PFX). - `aud` defaults to the requested registry `service` value unless `Signing:Audience` is configured. - Token lifetime is configured and bounded (1s..1h, default 5m). **Observability** - OpenTelemetry metrics: - `registry_token_issued_total{plan=...}` - `registry_token_rejected_total{reason=...}` - Structured logs via Serilog request logging. ## Request flow 1. Docker/OCI client receives a `401` from the registry with a `WWW-Authenticate: Bearer realm=...,service=...,scope=repository:...` challenge. 2. Client obtains an Authority token with the `registry.token.issue` scope (and any required sender constraints for the deployment). 3. Client calls `GET /token?service=&scope=repository::` on Registry Token Service. 4. Service validates: - `service` is present (and is allow-listed if `AllowedServices` is configured) - requested scopes parse correctly - caller plan/licence claims authorize all requested repository actions 5. Service returns a JSON response containing the signed registry token. Denial paths: - `400` for malformed requests (`service` missing, invalid `scope` query). - `403` for authorization failures (plan/licence/policy denies). ## Token shape (Docker registry compatible) The issued JWT includes: - `sub`: subject derived from `nameidentifier`/`client_id`/`sub` claims - `service`: the requested registry service - `access`: array of `{ type, name, actions[] }` entries (type is `repository`) - Optional: `stellaops:license` passthrough claim (for downstream correlation) ## Configuration Configuration is loaded from: - `etc/registry-token.yaml` (optional) - environment variables prefixed with `REGISTRY_TOKEN_` Key sections are defined by `RegistryTokenServiceOptions`: - `Authority` (issuer/metadata, audiences, required scopes) - `Signing` (issuer, key, lifetime, optional audience/kid) - `Registry` (realm, allow-listed `service` values) - `Plans`, `DefaultPlan`, `RevokedLicenses` ## References - Operations/runbook: `docs/modules/registry/operations/token-service.md`