- 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>
4.6 KiB
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
accessclaim 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 viaRegistryTokenService:Authority:RequiredScopes).
Plan registry (authorization rules)
- The caller's plan is read from
stellaops:planclaim (fallback: configuredDefaultPlan). - Licence revocation uses
stellaops:licenseclaim and configuredRevokedLicenses. - 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
IPlanRuleStoreis backed by PostgreSQL whenRegistryTokenService:Postgres:ConnectionStringis configured. - Startup migrations run automatically on host startup for the registry-token schema.
- The in-memory store is restricted to
Testinghosts only; live runtime composition requires the durable backend. - The persistence schema stores plan rules plus audit history so plan CRUD, audit endpoints, and
/tokenauthorization survive process restarts.
Token issuer
- Tokens are signed with an RSA private key loaded from
RegistryTokenService:Signing:KeyPath(PEM or PFX). auddefaults to the requested registryservicevalue unlessSigning:Audienceis 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
- Docker/OCI client receives a
401from the registry with aWWW-Authenticate: Bearer realm=...,service=...,scope=repository:...challenge. - Client obtains an Authority token with the
registry.token.issuescope (and any required sender constraints for the deployment). - Client calls
GET /token?service=<service>&scope=repository:<repo>:<actions>on Registry Token Service. - Service validates:
serviceis present (and is allow-listed ifAllowedServicesis configured)- requested scopes parse correctly
- caller plan/licence claims authorize all requested repository actions
- Service returns a JSON response containing the signed registry token.
Denial paths:
400for malformed requests (servicemissing, invalidscopequery).403for authorization failures (plan/licence/policy denies).
Token shape (Docker registry compatible)
The issued JWT includes:
sub: subject derived fromnameidentifier/client_id/subclaimsservice: the requested registry serviceaccess: array of{ type, name, actions[] }entries (type isrepository)- Optional:
stellaops:licensepassthrough 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-listedservicevalues)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