Vetrix Docs

Registry Authorization and Telemetry Reference

Scope: OCI (container), npm, PyPI, Go module, generic, and admin registry routes. Covers required permissions, 401/404 behavior for private repositories, and analytics events emitted per request.


Authorization Model

All registry routes share a single authorization helper (authorizeRegistry in internal/registry/authz.go). Resolution order:

  1. Public-repo reads — allowed for everyone, including anonymous callers.
  2. Anonymous callers on writes/private reads — 401 Unauthorized.
  3. Instance admins (claims.IsAdmin = true) — always allowed.
  4. Repository owner — always allowed.
  5. Collaborator — allowed when their role (or, for RoleCustom, their explicit permission list) grants the required permission.

401 / 404 behavior (private repos)

Caller Op Response
Anonymous (no Authorization header) Any 401 with WWW-Authenticate: Bearer realm="Vetrix Registry"
Authenticated non-member Read 404 (existence-leak prevention — equivalent to OCI NAME_UNKNOWN)
Authenticated non-member Write / Delete 403 Forbidden
Authenticated member lacking write Write 403 Forbidden
Authenticated member lacking delete Delete 403 Forbidden

The WWW-Authenticate challenge header is required by RFC 7235 §3.1 and the OCI Distribution Spec. All 401 paths in the registry set this header.


Route Table

OCI Distribution Spec (/v2/*)

All OCI routes are served by OCIHandler (internal/registry/oci.go). Authentication via Authorization: Bearer <JWT> or Authorization: Basic <base64(username:jwt)>.

Method Path Required permission 401/404 private behavior Analytics event
GET /v2/ None (version probe) 401 w/ WWW-Authenticate if auth header is malformed
GET /v2/<name>/tags/list PermRegistryRead anon → 401; authed non-member → 404 NAME_UNKNOWN
GET, HEAD /v2/<name>/manifests/<ref> PermRegistryRead anon → 401; authed non-member → 404 NAME_UNKNOWN registry.pull (GET only, on success)
PUT /v2/<name>/manifests/<ref> PermRegistryWrite anon → 401; authed denied → 403 DENIED registry.push (on success)
DELETE /v2/<name>/manifests/<ref> PermRegistryDelete anon → 401; authed denied → 403 DENIED registry.delete (on success)
GET, HEAD /v2/<name>/blobs/<digest> PermRegistryRead anon → 401; authed non-member → 404 NAME_UNKNOWN
DELETE /v2/<name>/blobs/<digest> PermRegistryDelete anon → 401; authed denied → 403 DENIED registry.delete (on success)
POST /v2/<name>/blobs/uploads/ PermRegistryWrite anon → 401; authed denied → 403 DENIED
GET /v2/<name>/blobs/uploads/<uuid> PermRegistryWrite anon → 401; authed denied → 403 DENIED
PATCH /v2/<name>/blobs/uploads/<uuid> PermRegistryWrite anon → 401; authed denied → 403 DENIED
PUT /v2/<name>/blobs/uploads/<uuid>?digest= PermRegistryWrite anon → 401; authed denied → 403 DENIED

Job tokens: write requests may carry a typ=job-token bearer. authorizeJobToken is evaluated first; a denied job token yields 403 DENIED regardless of the standard ACL result.


npm Registry (/npm/{owner}/{repo}/)

Served by NPMHandler (internal/registry/npm.go).

Method Path Required permission 401/404 private behavior Analytics event
GET /npm/{owner}/{repo}/{package} PermRegistryRead anon → 401 w/ WWW-Authenticate; authed non-member → 404
GET /npm/{owner}/{repo}/{package}/-/{tarball} PermRegistryRead anon → 401 w/ WWW-Authenticate; authed non-member → 404
PUT /npm/{owner}/{repo}/{package} PermRegistryWrite missing auth → 401 w/ WWW-Authenticate; authed denied → 403

PyPI Simple Index (/pypi/{owner}/{repo}/)

Served by PyPIHandler (internal/registry/pypi.go).

Method Path Required permission 401/404 private behavior Analytics event
GET /pypi/{owner}/{repo}/simple/ PermRegistryRead anon → 401 w/ WWW-Authenticate; authed non-member → 404
GET /pypi/{owner}/{repo}/simple/{package}/ PermRegistryRead anon → 401 w/ WWW-Authenticate; authed non-member → 404
GET /pypi/{owner}/{repo}/packages/{digest}/{filename} PermRegistryRead anon → 401 w/ WWW-Authenticate; authed non-member → 404
POST /pypi/{owner}/{repo}/ PermRegistryWrite missing auth → 401 w/ WWW-Authenticate; authed denied → 403

Go Module Proxy (/go/{owner}/{repo}/)

Served by GoModuleHandler (internal/registry/gomodule.go).

Method Path Required permission 401/404 private behavior Analytics event
GET /go/{owner}/{repo}/* PermRegistryRead anon → 401 w/ WWW-Authenticate; authed non-member → 404
PUT /go/{owner}/{repo}/* PermRegistryWrite missing auth → 401 w/ WWW-Authenticate; authed denied → 403

The * wildcard captures the GOPROXY path tail (e.g. github.com/foo/bar/@v/v1.0.0.zip).


Generic Artifact Registry (/api/v1/repos/{owner}/{repo}/packages/generic/)

Served by GenericHandler (internal/registry/generic.go).

Method Path Required permission 401/404 private behavior Analytics event
GET .../packages/generic/{name} PermRegistryRead anon → 401 w/ WWW-Authenticate; authed non-member → 404
GET .../packages/generic/{name}/{version} PermRegistryRead anon → 401 w/ WWW-Authenticate; authed non-member → 404
GET .../packages/generic/{name}/{version}/{filename} PermRegistryRead anon → 401 w/ WWW-Authenticate; authed non-member → 404
PUT .../packages/generic/{name}/{version}/{filename} PermRegistryWrite missing auth → 401 w/ WWW-Authenticate; authed denied → 403
DELETE .../packages/generic/{name}/{version}/{filename} PermRegistryDelete missing auth → 401 w/ WWW-Authenticate; authed denied → 403

Cross-Repo User Image Listing

Method Path Required permission 401/404 private behavior Analytics event
GET /api/v1/users/{username}/registry/images Authenticated; results scoped to caller's PermRegistryRead standard API auth middleware (401 on missing token)

Results are visibility-filtered in SQL (ListImagesForUser) to repositories where the caller is the owner, the repository is public, or the caller holds a collaborator role that grants PermRegistryRead (built-in roles: repo_read, repo_write, repo_admin; custom roles: requires explicit registry:read in the permissions array).


Admin Registry Routes (/api/v1/admin/registry/)

All admin routes require admin:registry permission (instance-admin role). Non-admins receive 403 from the admin middleware before reaching the handler.

Method Path Required permission 401/404 behavior Analytics event
GET /api/v1/admin/registry/images admin:registry (AdminBypass=true, sees all repos) admin middleware 401/403
DELETE /api/v1/admin/registry/images/{owner}/{repo}/{name}/tags/{tag} admin:registry admin middleware 401/403
DELETE /api/v1/admin/registry/images/{owner}/{repo}/{name} admin:registry admin middleware 401/403
GET /api/v1/admin/registry/gc/status admin:registry admin middleware 401/403
POST /api/v1/admin/registry/gc/run admin:registry admin middleware 401/403 registry.gc.completed (on completion)
GET /api/v1/admin/registry/retention/{owner}/{repo} admin:registry admin middleware 401/403
PUT /api/v1/admin/registry/retention/{owner}/{repo} admin:registry admin middleware 401/403 registry.retention.completed (on success)
GET /api/v1/admin/registry/storage admin:registry admin middleware 401/403

Analytics Events

All registry analytics events are sourced as SourceAPI. Non-OCI handlers do not currently emit analytics events; OCI push/pull/delete telemetry is implemented in OCIHandler.

Event name Trigger Properties
registry.push Successful OCI manifest PUT RegistryPushProperties (owner, repo, image, tag, digest, media_type, size_bytes)
registry.pull Successful OCI manifest GET (not HEAD) RegistryPullProperties (owner, repo, image, tag, digest, media_type)
registry.delete Successful OCI manifest or blob DELETE RegistryDeleteProperties (owner, repo, image, ref_or_digest, kind)
registry.gc.completed GC run finishes RegistryGCRunProperties (blobs_deleted, bytes_freed, duration_ms)
registry.retention.completed Retention policy enforcement finishes RegistryRetentionCompletedProperties (owner, repo, tags_deleted, kept)

The event recorder is attached to OCIHandler via SetRecorder. A nil recorder silently skips all emit calls — requests never fail due to analytics errors.


Implementation Notes

  • nonOCIAuthzDeny (internal/registry/authz.go) is the single function that emits 401/403/404 for all non-OCI handlers. All 401 paths set WWW-Authenticate: Bearer realm="Vetrix Registry" per RFC 7235 §3.1.
  • OCIHandler.ociUnauthorized sets WWW-Authenticate: Basic realm="Vetrix Registry" (OCI clients prefer Basic challenge for token service discovery).
  • The 404 on authenticated read denial is an existence-leak prevention measure: it prevents an authenticated non-member from enumerating private repository names by probing registry endpoints.
  • ListImagesForUser runs the visibility predicate in SQL so pagination counts are accurate without streaming all rows through Go.