Loading
feat(auth): guard the GitLab API with a service token
What
/api/gitlab/v1 now authenticates by the ADR-020 per-edge service token in the Gitlab-Artifact-Registry-Token header (plan Step 2).
- Dispatch row 7 (the interim Bearer guard) is removed. The central middleware passes the subtree through on its escaped path (
ServeMuxroutes on the escaped form, so matching the decoded path would exempt%2Fvariants it routes elsewhere), and the guard mounts on the subtree itself (internal/auth/servicetoken). - Configured mode (
auth.gitlab_apipresent): SHA-512 + constant-time validation via iam'spkg/serviceauth; the current and the optional rotation token are both accepted; a set-but-unresolvable ref aborts boot. - Fallback mode (block absent): a bearer credential checked by the deployment's configured validator, identity dropped, plus a startup warning. Step 4 removes the fallback before go-live.
- Both modes answer rejections with the S01 envelope
401and noWWW-Authenticate. - Rejection logging (a Warn per rejected credential, mode-mismatch attribution) lands separately in !1713 (merged), stacked on this branch, so this MR reviews as the guard alone. Until it merges, rejections here have no server-side log line.
The last commit records the review-found plan corrections (prefix-constant direction, Step 4's grown scope, the fallback-validator wording) and amends the S33 spec's fallback sentences.
Review notes
- Transitional fallback: on a
token_exchangedeployment the fallback accepts any valid end-user AR token on this platform-internal surface. This is pre-existing dispatch-row-7 behavior, not widened here; the plan and spec now record it as a transitional ADR-020 deviation closed by Step 4. Staging runsbootstrap_token. - OpenAPI: documents the apiKey scheme with a transitional-fallback note. The credential swap is breaking inside
v1underdocs/dev/api-style.md's catalog; no caller exists yet, which is why it lands now. - Diff size: ~1313 added lines, over the 500-LOC guideline: tests +809,
internal/production +182,cmd/production +126, docs/OpenAPI/Bruno/run-recipe +190,go.mod/go.sum+6. The rejection-logging slice was split into !1713 (merged); the remainder is the plan's named atom — splitting further would merge either a middleware nobody mounts or a mount nobody guards, and 62% of what is left is tests. - E2E catalogs: no scenario added or affected.
docs/testing/e2e/covers the client-facing format surfaces;/api/gitlab/v1is the platform-internal edge and has no catalog file. driver.sh smoke: 24/24, including the two new GitLab API rows (guard401+ handler404).
Related to #356 (closed)
Edited by João Pereira