feat(maven): wire authz enforcement part 2/2 (S09 Enforcement plan: 9/20)

Part 1 (!2064 (merged)) merged as 619a4886; this MR was retargeted to main when that branch was deleted, and is the only part left to review. The step was split in two to bring each part closer to the 200-400 LOC target in development-model.md; the Size section below records where this part actually lands against it.

Part 2 of 2 of S09 authorization enforcement — Step 9: Maven wiring.

What this part adds

Authorization enforcement on the Maven surface, end to end:

  • cmd/artifact-registry/wire_maven.go: mavenAuthzResolverAdapter, the authz.RepositoryResolver over the shared *maven.Resolver — it maps the maven not-found sentinels to the namespace ones authz.Assembly recognizes, seeds the maven holder with the full resolution, and returns the namespace.Resolution projection (including Kind and NamespaceSuspended, which the shared holder's contract obliges every construction path to populate).
  • cmd/artifact-registry/wire_root_dispatcher.go: withMavenAuthz wraps the Maven dispatcher with authz.NewMavenMiddleware behind the slugAnchoredAuthzConfigured gate, installing the resolution holder outside the wrap; the chain registers at the maven.RoutePattern mount, so both context clones happen after the inner mux stamps r.Pattern.
  • cmd/artifact-registry/wire.go: logMavenAuthzWiring mirrors the OCI boot log, delegating to the same gate the wrap consults.
  • Composition tests: anonymous masked 404 byte-identical to the genuine not-found with zero IAM calls, unwrapped-when-one-client, role-based denial matrix (read-only principal: 403 on PUT, dispatch on GET; no role: masked 404 on both), unmapped-method 403 and resolver-failure 503 through the real maven.DenialRenderer, single resolve per authorized request over counting finders, replay surviving the suspension and kind gates, per-request holder isolation, r.Pattern exit-path survival, and boot-level client threading through wireServices.
  • Docs: the configuration reference now states Maven is evaluated when IAM and GLAZ are configured. The Maven e2e access-control rows stay blocked, and their section intro is aligned with docs/testing/e2e/oci.md: enforcement landing does not unblock them, because role assignment is a GitLab UI/API surface this repository does not own. docs/testing/e2e/docker.md's intro gets the same alignment — it still named the role-less authorization stub — and its rows stay blocked for that same reason, so all three catalogs now state one blocker in one wording.

Spec: S09 (authorization).

Size

~1060 reviewable LoC, above the 500 guideline. Split by file group, from git diff --stat origin/main...HEAD at 9e4e0a0e:

Group Files Changed lines
Production wire.go, wire_maven.go, wire_root_dispatcher.go, internal/authz/middleware.go 143
Tests wire_maven_authz_test.go, wire_services_authz_boot_test.go, wiring_test.go 886
Docs configuration-reference.md, docs/testing/e2e/maven.md, docs/testing/e2e/docker.md 31

The suite is indivisible from the wiring it exercises — splitting it would separate tests from the code they cover — and the foundation it builds on already shipped as part 1.

Accepted, documented gap

An encoded slash makes the mux (escaped path) and the authorization parser (decoded path) disagree; mavenRouteParser.Mine's doc comment in internal/authz/maven.go documents both shapes. Both are contained today — the slug by the check_namespaces_slug_format CHECK constraint, the repository name by the management API's creation regex — and predate this MR (shipped with the Step 7 parser).

Conformance

The Maven conformance rig boots without an iam: block, so it exercises this path unenforced until the fail-closed boot lands (Step 20); the composition tests above carry the enforcement behavior.

Related to #652 (closed)

Edited by David Fernandez

Merge request reports

Loading
Loading