chore(iam): add the LookupResources RPC behind a ResourceLookup seam (S09 LookupResources plan: 1/3)
internal/iam gains a one-page LookupResources call behind a new
ResourceLookup interface beside Resolver, with its domain request and page
types, the proto mapping including LookupFilter, and the bounded rpc metric
label. Nothing calls it yet — step 3 wires it into the repository-listing
filter — so there is no release-visible surface, hence chore.
Why the seam looks like this
One call is one page. Draining stays with the caller, which is what lets step 3
own the page ceiling and the fail-closed arm instead of burying a loop here. The
client echoes next_page_token unchanged and never reads an empty page as the
terminator.
NOT_FOUND stays unmapped. IAM answers a subject it cannot resolve with an
empty page, so an absence is an empty LookupResourcesPage; mapping NOT_FOUND
onto a sentinel would invite a caller to read a reachability failure as an
absence. A NOT_FOUND from a pre-fix IAM therefore fails the caller closed,
which is the intended posture.
Response rows carry the subject on the proto's identity oneof rather than
principal, so relationships.Relationship.Principal is empty on every lookup
row. LookupResourcesPage says so, relationships.Relationship's own doc now
says which of its two producers that applies to, and a mapping subtest pins it.
Verified against IAM
Re-confirmed against gitlab-org/auth/iam at 88630ea994:
| Fact | Where |
|---|---|
default page size 100, cap 999; page_size <= 0 takes the default, above the cap is clamped down |
dataaccess/lookup/paginate.go |
| token fingerprinted over the subject's origin, origin id and local id plus the sorted kinds and roles | lookupResourcesCursorFingerprint, same file |
direct edges only (edge_type = 1) |
LookupRelationshipsBySubject in dataaccess/internal/relationships/store/queries.sql |
an unresolvable principal answers an empty page, never NOT_FOUND |
lookupResources in dataaccess/lookup/lookup_resources.go |
The vendored proto's Unresolvable subject → NOT_FOUND comment is stale against
that handler; raising it upstream is a follow-up. No vendir bump needed.
AuthorizeLookupResources is not only a self-lookup: an org owner may look up
any subject in their own organization, and both arms require an
organization-origin subject in the caller's org, so a gitlab_federated
subject is refused whoever asks. The doc comments state both arms. Step 3's
caller must not skip its own subject check on the strength of the narrower
rule.
Companions
internal/metrics/cardinality.go's rpc budget and closed set, and
docs/dev/observability.md's label set. AuditCardinality checks gathered
values against the pinned set, so a metered call fails the audit until the table
lists the value.
Testing
Unit tests against an in-process fake LookupService: page mapping, the request
reaching the wire verbatim, the four status sentinels plus NOT_FOUND staying
unmapped, per-status attempt counts pinning the retry policy the service config
grants the whole LookupService, the empty page, an empty page that still
carries a token, the zero-value request, the rpc label, and the forwarded-JWT
pair mirroring the ReadRelationships tests. An integration test issues one
page against the IAM rig.
Local Docker is unavailable, so the integration-tagged file is compile-checked
with go vet -tags=integration ./... and linted with
golangci-lint run --build-tags=integration.
Size
659 added lines, past the 500 reviewable-LOC guideline. Splitting does not help:
123 are source (resolver.go 42, mapping.go 52, client.go 21,
cardinality.go 4, types.go 3, metrics.go 1) and 535 are tests across five
internal/iam suites — close to the ratio the plan forecast for this step. A
test-only part would move lines between MRs without shrinking the reviewable
unit, because the tests are what state what the seam promises.
Known follow-up
internal/iam/context.go's tokenHolder doc names ReadRelationships as the
only method whose authorization header requires a JWT; LookupResources needs
one too. That block sits far above the 1-line cap for an unexported type, so any
edit fails check-comment-caps.sh and would cost the SchemeBearer reachability
reasoning it carries. Step 3 changes those reachability facts, so the rewrite
belongs there.
S25's spec-side correction is !2405 (merged), which moves LookupResources out of its
"this client does not consume" list and into the consumed set; it merged ahead
of this MR.
Related to https://gitlab.com/gitlab-org/ops/artifact-registry/-/work_items/969