- API_CLI_REFERENCE.md, INSTALL_GUIDE.md, quickstart.md, architecture/integrations.md, dev/DEV_ENVIRONMENT_SETUP.md, integrations/LOCAL_SERVICES.md: reflect real-service wiring. - docs/modules/**: module dossier updates across the modules touched by SPRINT_20260415_001..007 + SPRINT_20260416_003..017 + SPRINT_20260417_018..024 + SPRINT_20260418_025 + SPRINT_20260419_026. - docs/features/checked/web/**: update feature notes where UI changed. - docs/qa/feature-checks/runs/web/evidence-presentation-ux/: QA evidence artifacts. - docs/setup/**, docs/technical/**: align with setup wizard contracts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
96 lines
4.6 KiB
Markdown
96 lines
4.6 KiB
Markdown
# 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`
|
|
- Authorization decisions based on (a) Authority-issued identity token claims and (b) either persisted or static plan rules
|
|
|
|
## 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.
|
|
|
|
**Plan administration storage**
|
|
- The admin `IPlanRuleStore` is backed by PostgreSQL when `RegistryTokenService:Postgres:ConnectionString` is configured.
|
|
- Startup migrations run automatically on host startup for the registry-token schema.
|
|
- The in-memory store is restricted to `Testing` hosts only; live runtime composition requires the durable backend.
|
|
- The persistence schema stores plan rules plus audit history so plan CRUD, audit endpoints, and `/token` authorization survive process restarts.
|
|
|
|
**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=<service>&scope=repository:<repo>:<actions>` 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`
|
|
|
|
Durable plan-rule persistence is configured separately under `RegistryTokenService:Postgres`.
|
|
When Postgres persistence is configured, the host may start without any statically configured `Plans`; persisted plan rules become the canonical source for admin CRUD and token issuance.
|
|
|
|
## References
|
|
|
|
- Operations/runbook: `docs/modules/registry/operations/token-service.md`
|