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, theauthz.RepositoryResolverover the shared*maven.Resolver— it maps the maven not-found sentinels to the namespace onesauthz.Assemblyrecognizes, seeds the maven holder with the full resolution, and returns thenamespace.Resolutionprojection (includingKindandNamespaceSuspended, which the shared holder's contract obliges every construction path to populate).cmd/artifact-registry/wire_root_dispatcher.go:withMavenAuthzwraps the Maven dispatcher withauthz.NewMavenMiddlewarebehind theslugAnchoredAuthzConfiguredgate, installing the resolution holder outside the wrap; the chain registers at themaven.RoutePatternmount, so both context clones happen after the inner mux stampsr.Pattern.cmd/artifact-registry/wire.go:logMavenAuthzWiringmirrors 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.Patternexit-path survival, and boot-level client threading throughwireServices. - 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 withdocs/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 stayblockedfor 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)