Registry Token Endpoint
Ahdapa can act as the authentication backend for a private OCI container
registry (Harbor, Docker Distribution, GitLab’s built-in registry, or any
registry supporting the OCI Distribution v2 Bearer token
protocol).
A CI pipeline authenticates once — with a static client credential, or
passwordlessly by presenting an externally issued OIDC JWT (e.g. a GitHub
Actions workload token) — and receives a short-lived, repository-scoped
token to hand directly to docker login / docker push / docker pull.
No long-lived registry password is stored anywhere.
This gives a private deployment the same “keyless CI” experience that Docker Hub’s OIDC connections and quay.io’s GitHub Actions federation provide for public registries.
How it works
docker login/podman login(or the registry itself, via its ownWWW-Authenticate: Bearer realm="..."challenge) callsGET /registry/token?service=<registry-host>&scope=repository:<name>:<actions>.- Ahdapa authenticates the caller:
- Static credential — HTTP Basic Auth with a registered client’s
client_id/client_secret. - Passwordless JWT — HTTP Basic Auth with
client_idand an externally issued OIDC JWT as the password, resolved via the same federation-policy mechanism documented in Federation and Token Exchange. - Anonymous — no
Authorizationheader at all, permitted only for repositories matching[registry] anonymous_pull_repositories, and only ever forpull.
- Static credential — HTTP Basic Auth with a registered client’s
- For each requested repository/action pair, ahdapa checks two
independent, both-must-pass gates:
- The effective client’s
allowed_registry_repositoriesgrants (glob pattern → allowed actions). - If any live HBAC rule exists, the HBAC registry-repository/action dimension (see Identity HBAC Policy).
- The effective client’s
- Ahdapa mints a short-lived JWT (5 minutes) with an OCI-spec
accessclaim listing only the repository/action pairs actually authorized — a partially- or fully-unauthorized request still gets200 OKwith a reduced (possibly empty)accesslist, per the OCI spec’s soft-denial model, not a hard failure. - The registry (configured to trust ahdapa’s signing key / JWKS) accepts the token for the matched actions.
Manual configuration
[registry]
enabled = true
allowed_services = ["registry.example.com"]
anonymous_pull_repositories = ["myorg/public-*"]
allowed_services— the incomingserviceparameter must match one of these, or the request is refused with401. This prevents ahdapa from being used as a token-minting oracle for an attacker-controlled registry hostname.anonymous_pull_repositories— glob patterns eligible for unauthenticated, pull-only access. Empty by default (fully opt-in); push/delete can never be granted anonymously regardless of configuration.
Per-client repository grants are set via the admin API (or a static TOML
client), not this config section — see Admin API — Client
fields’s
allowed_registry_repositories entry:
[[client]]
client_id = "ci-runner"
client_name = "CI Runner"
token_endpoint_auth_method = "client_secret_basic"
client_secret = "..."
grant_types = ["urn:ietf:params:oauth:grant-type:token-exchange"]
allowed_registry_repositories = [
{ pattern = "myorg/*", actions = ["pull", "push"] },
]
See also
- Federation — upstream OIDC trust configuration.
- Token Exchange — the RFC 8693 mechanism underlying the passwordless-JWT auth path.
- Identity HBAC Policy — the registry-repository/action conditional-access dimension.
- Registry token endpoint demo — a self-contained walkthrough.