docs(plans): plan the ADR-020 service-token credential for the AR client

Summary

Plans the Rails counterpart to AR's service-token guard: the monolith stops sending the interim bootstrap bearer on /api/gitlab/v1 and presents the ADR-020 per-edge service token in the Gitlab-Artifact-Registry-Token header instead.

Adds docs/plans/monolith/2026-08-20-s02-service-token-credential.md and amends the two specs the change falsifies.

The AR side is already done. ops/artifact-registry#356 closed on 2026-08-21: the guard shipped and the rollout reached staging (1.343.1), production (1.350.0) and the caproni rig (gitlab-caproni!84), so every deployed environment runs in configured mode. That work item's closing note names gitlab#617724, which this plan implements, as one of the two things left. So this is the last step on the edge rather than a blocked one.

Plan shape

One step, one MR, in the monolith:

  1. artifact_registry.service_token.secret_file settings key plus its gitlab.yml.example stanza, mirroring iam_data_access_service.
  2. ArtifactRegistry::ServiceCredential#token reads that secret instead of returning nil.
  3. ArtifactRegistry::Client threads the credential's transport form from the two entry points, user_request and service_request, through the shared authed_request into perform_request. authed_request carries whichever header it is handed and composes neither itself, so it cannot re-attach a bearer on the service path.
  4. ArtifactRegistry::ProvisionNamespaceService, the second Client.new site, which injects its own credential and so does not inherit the default provider.

Not split, because splitting at the credential/transport seam would ship an intermediate that returns the real token while still sending it as a bearer, and AR rejects that shape in both of its modes.

Spec amendments

  • monolith/S02: Credential acquisition, Rails configuration stanza, Acceptance criteria, Security considerations, Resolutions, and the dependency table. The spec said the client attaches every credential as a bearer token.
  • monolith/S10: its dependency table, the Ruby client Credential and Injected inputs paragraphs, and the credential acceptance criterion. S10 claimed a slice wires a bootstrap service provider; no slice does.

Diff size

696 insertions across four files, past the 500 reviewable-LOC line in docs/dev/development-model.md, and not worth splitting:

Lines File group
+558 the new plan, which is one document and cannot be usefully halved
+115 monolith/S02
+22 monolith/S10
+1 the docs/plans/README.md index row

The two spec amendments are the plan's own gate, so landing them separately would leave a plan whose Spec Reference points at text that contradicts it.

Two findings worth a reviewer's attention

  • The header constant is settled, not an open question as the issue implies. AR declares HeaderName = "Gitlab-Artifact-Registry-Token" in internal/auth/servicetoken/servicetoken.go, asserted in its test and published as components.securitySchemes.serviceToken in api/openapi/gitlab-v1.yaml. ADR-020 leaves the name to each service, so the client's constant matches AR's rather than declaring the contract.
  • AR's two modes are exclusive, not additive. With a token configured it rejects a bearer-only request; with none configured its fallback rejects a header-only request. The ordering that follows is already satisfied wherever AR is deployed, so mounting the Rails secret closes the edge rather than opening a window. It still binds a new environment, which is why the plan and the S02 amendment both record it.

Governing ADRs

  • ADR-020 Authentication Flow is the decision source. Conforms: a static symmetric per-edge secret, no JWT, no audience and no principal, because the call acts for no end user; the header name is the service's to choose; the validator accepts a current and a next value so rotation needs no cutover.

    Read against the handbook copy, not the local mirror. scripts/adr-freshness.sh reports the mirror 10 commits stale with 020_authentication_flow.md among the drifted files, and the unsynced commit is the one that added the service-to-service section, which the mirror therefore does not contain.

    One thing the spec now records rather than glosses: ADR-020 calls this token interim for this edge, with mutual TLS and workload identity as the direction and neither decided. So the seam stays injectable, and "settled" means settled for this iteration.

  • ADR-014 (docs/adr/014_frontend_to_artifact_registry.md) governs the client pattern and the server-held credential. Conforms: the credential is acquired and attached server-side and never reaches the browser. Not drifted in the mirror.

  • ADR-009 (docs/adr/009_api_design.md) governs the GitLab API surface. Conforms: the service seam takes no slug, because that surface is slug-exempt.

Testing

Documentation only, so verification is a consistency read rather than a suite. What was checked, and against what:

  • Every monolith path and symbol against origin/master of $GITLAB_MONOLITH_PATH, by symbol rather than by line number.
  • Every AR path against origin/main of this repository. The servicetoken implementation is not in this branch's working tree, which forks from before it, so a reviewer grepping the checkout finds nothing and should use git show origin/main:....
  • The header name, the exclusive-modes behaviour and both rejection log lines against internal/auth/servicetoken/servicetoken.go and its test.
  • The mirror this plan copies (Authn::IamDataAccessService) against its own source and spec, including where this plan departs from it: the error behaviour deliberately, and the memoization scope, the credential's name and nesting, and the header constant's home as consequences of the surrounding code.
  • markdownlint-cli2, vale and lychee clean on the three changed files.

No e2e scenario applies: docs/testing/e2e/ covers the artifact formats, and none drives the GitLab API namespace methods, whose two callers are fail-closed until an operator mounts the secret.

Related to https://gitlab.com/gitlab-org/gitlab/-/work_items/617724

Edited by Narendran

Merge request reports

Loading
Loading