feat(npm): wire authz enforcement for the npm dispatcher (part 4/4) (S09 Enforcement plan: 13/20)

Split into 4 stacked MRs to keep each part within the review size target (~800 reviewable LoC ideal). Each part targets the previous one (part 1 targets main); the stack is reviewed and merged bottom-up.

Part 4 of 4 of the S09 (authorization) enforcement plan — Step 13: npm wiring and route-table parity. Spec: S09 (authorization). Depends on parts 1–3 (merge order enforced by the merge-request dependencies).

📦 What this part does

Wraps the npm dispatcher with the authorization middleware at its catch-all registration, behind the same configured-clients gate as OCI and Maven:

  • withNpmAuthz — installs the resolution holder and composes authz.NewNpmMiddleware around the dispatcher; panics on a nil resolver at wiring time.
  • npmAuthzResolverAdapter — resolves the repository for the decision layer, translates the resolver's sentinel errors, and seeds the holder only on success, so the format middleware resolves exactly once per authorized request.
  • logNpmAuthzWiring — boot-time log making the gate greppable, mirroring the OCI and Maven variants.

The composition suite proves the chain end to end: wrap/unwrap gate, anonymous-write masked 404 (byte-identical to npm's genuine not-found), role-based denial matrix, error-envelope rendering, replay across suspension and kind gates, per-request holder isolation under concurrency, scoped/escaped package forms, and the authz-driven parity direction (every registered route drives exactly one GLAZ check with the spec's action).

⚖️ Known trade-off

On deployments where both the iam: and GLAZ clients are configured, npm's granular route patterns are stamped below the wrap, so exit-path readers (RoutePatternMiddleware, the HTTP metrics route label) see / for npm requests. TestNpmAuthzComposition_RoutePatternExitReadIsRootMount pins the collapse so it is explicit rather than accidental; recorded on #152 (comment 3762478657).

📏 Size

1,179 reviewable LoC, past the 500 guideline: ~1,040 of the 1,146 added lines are test files (959 of them in cmd/artifact-registry/wire_npm_authz_test.go); the production Go is ~92 lines across the four wiring files. The composition tests exercise withNpmAuthz and the adapter directly, so splitting them out would separate the tests from the code they cover.

🔬 e2e scenarios

docs/testing/e2e/npm.md is updated in this MR: the Access rows record that enforcement is real while end-to-end automation stays blocked on role assignment, an unowned GitLab surface (same status as Maven's catalog).

📚 Stacked MRs (review/merge bottom-up)

Related to #855 (closed)

Edited by David Fernandez

Merge request reports

Loading
Loading