docs(plans): plan the per-user token-exchange credential

What

Plans gitlab#623370: ArtifactRegistry::TokenExchange#token_for mints a real gitlab-artifact-registry-audience JWT through Authn::TokenExchange::TokenIssuer, so the client's per-user management-API methods stop failing closed before a request is issued.

Adds docs/plans/monolith/2026-08-26-s02-per-user-token-exchange.md and amends the specs the change falsifies.

Plan shape

One step, in the monolith. The seam is a single method, its collaborator already exists, and ArtifactRegistry::Client already defaults to this class — making the default functional is the whole wiring, so neither Client nor CachesClient changes.

Four decisions the seam left open

  • Mint in process, not over HTTP. A GraphQL resolver holds no credential to authenticate a self-directed POST /api/v4/token_exchange with, which decides it ahead of any efficiency argument; Authz::ArtifactRegistry::BaseService is the in-tree precedent. The plan then answers each of the four controls the endpoint wraps around the mint rather than leaving them unmentioned: two are redundant here, and the rate limit and gate_token_exchange_endpoint flag are deliberately not reproduced over a surface artifact_registry_ui already gates per organization.
  • slug comes out of the signature. The shipped issuer carries no resource claim, so there is nothing to pass it to, and Client#user_request — its only caller — drops the argument with it.
  • Memoize per user, which follows from the above: with no resource in the token, a mint is keyed by the user alone. No cross-request store, because the token carries organization_role.
  • The home-organization limitation is pinned by a test, not worked around.

Spec amendments

monolith/S02 across five sections — its per-user seam is documented as returning no credential, and its ADR-014 OQ#2 row as open. One Out-of-scope line each in monolith/S03 and monolith/S04, which both defer the real exchange to "the S02 credential seam and ADR-014 open question #2".

Two findings a reviewer should weigh

  • The token names the acting user's home organization. TokenIssuer#organization_uuid reads @user.organization.uuid. A user acting on an organization they are a member of but not homed in therefore presents a token naming a different organization than the namespace addressed. This is the same defect the closed-beta roadmap records against #473, one layer down. Out of scope here (it is an issuer change), filed separately.
  • What the change exposes. AR authenticates /api/v1 and then applies no role check, so Rails' own read_artifact_registry authorization becomes the only access control over the per-user surface. Acceptable for a closed beta gated per organization, and stated so it is weighed rather than discovered.

Not in scope

The service credential (gitlab#617724, independent — different method, either merge order), resource-scoped tokens, the cross-organization identity gap, the stub slug (gitlab#619494), and AR-side enforcement (#652 (closed)).

Related to gitlab-org/gitlab#623370 (closed)

Edited by Narendran

Merge request reports

Loading
Loading