feat(authz): add the format-agnostic authorization Assembly

1 of 3 in the !960 (closed) split (see !960 (closed) for the original combined draft). Depends on nothing; the next two — the OCI RouteParser and the dispatcher wiring — stack on top of this branch.

What

The format-agnostic authorization decision layer, generalized before any format plugs into it:

  • RouteParser is the seam each format owns: it recovers the ADR-021 action and target repository from a request in its own path grammar (OCI's /v2/, npm's package layout, Maven's coordinate layout, whichever lands first).
  • Assembly is the shared decision logic every format's middleware reuses: resolve the repository, fetch the principal's role-assignment tuples, ask GLAZ Check, map the verdict onto next/403/404 per ADR-021's existence-leak split.

Closed beta: private repositories only

ADR-021 was rescoped to private-visibility-only for the closed beta by gitlab-com/content-sites/handbook!20467 (merged). This MR implements that rescoped version: every action needs a role from GLAZ, there is no visibility check, and an anonymous caller is denied outright (404) before any lookup runs.

This layer has no governing spec in docs/specs/ — S09 still lists "Never returns 403" as an acceptance criterion, which predates the ADR-021 rescope and no longer applies. ADR-021 (as rescoped) is the document this MR implements.

Tests

middleware_test.go exercises Assembly directly via a stub RouteParser, independent of any one format's grammar:

  • TestAssembly_ScopeAndRouteOutcomes — the RouteOutcome → next/403/404 mapping.
  • TestNewAssembly_PanicsOnNilDep — every constructor dependency.
  • TestAssembly_EveryActionReachesGLAZ — every action reaches GLAZ; there's no shortcut for any of them.
  • TestAssembly_AnonymousCallerDenied, TestAssembly_AllowedActionCallsNext, TestAssembly_DenialStatusMapping, TestAssembly_ResolutionAndInfrastructureFailures — the anonymous gate, the allow path, the 403/404 denial split, and the resolution/infrastructure fail-closed paths.

Known gaps, deferred to a follow-up MR

  • The IAM relationships lookup doesn't include the organization as a third ancestor object (ADR-021). namespaces.entity_id isn't a UUID, so this needs its own namespace-data-model change; tracked with a TODO in tuples.
  • JWT forwarding to IAM (iam.ContextWithToken) has no caller yet.

Not in this MR

No format's RouteParser — that's OCI's in the next MR in the stack, wired into the dispatcher in the one after that.

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

Edited by Shilpa Kundapur

Merge request reports

Loading
Loading