Files
git.stella-ops.org/docs/modules/registry/architecture.md
master fdf95e0f46 docs: module dossier + install/quickstart sync for truthful cutover sprints
- 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>
2026-04-19 14:45:09 +03:00

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`