feat(oauth): stamp gitlab-rails' resolved cell onto the access token
Stacked on !588 (merged) (the token-prefix MR) — targets that branch, not main.
Summary
- Adds a
cell_idfield toLoginServiceAcceptRequest, sogitlab-railscan pass through the cell it already resolved for the user during its own Topology Service lookup earlier in the login flow. - Threads that value through both session-creation paths (trusted-client fast path, and the normal consent path) into a
cclaim on the access token's JWT payload. - Also adds a
u(User ID) claim, per the Cells: Routable Tokens design doc: at least one ofc/ois required (IAM has nooanywhere in this flow, soccovers that), and the doc recommendsuin addition for a user-scoped token like this one. - Both
canduare base36-encoded strings ("c":"16", not"c":42), matching every other GitLab routable token and the Cells HTTP Router's strict decoder. - Implements part of gitlab-org/gitlab#604548 (closed) section 8.2, without any Topology Service integration in IAM itself.
Why this shape, not an IAM-side Topology Service client
The ticket's draft assumed IAM Auth would call the Topology Service's Classify RPC directly to resolve a user's cell. Investigating that turned up two blockers: Classify requires an mTLS certificate under one of three fixed roles (Cell/Router/Admin, per gitlab-org/cells/topology-service's docs/certificates.md) that IAM doesn't have, and the RPC only returns a proxy address, not the stable numeric cell ID the token needs — a caller would have to reverse-map it via a separately cached GetCells() call, with no existing pattern anywhere to copy.
Checking gitlab-org/gitlab's actual production code for glpat-, glagent-, and CI runner tokens (lib/authn/token_field/generator/routable_token.rb and its callers) showed none of them call the Topology Service either — the default routing payload is just c: -> { Gitlab.config.cell.id }, the local cell config, because Rails only ever mints a token from inside the cell that owns it. By the time gitlab-rails calls Login.Accept, it has already resolved the user's cell via its own earlier Topology Service lookup (the existing 2-step login flow). This MR has it pass that value through, rather than teaching IAM to ask the Topology Service the same question a second time.
u costs nothing extra to add: it's the same value already carried as the JWT's standard sub claim (subject on LoginService.Accept is @user.id.to_s on the gitlab-rails side). It's duplicated under the u key, rather than expecting the router to read sub, so the router's routable-token vocabulary doesn't need IAM-specific handling.
What changed
proto/auth/auth.proto:LoginServiceAcceptRequest.cell_id(int64, optional; 0 means not supplied).login_challengesgets a nullablecell_id BIGINTcolumn (both backends), set alongside the other verifier fields (subject/name/email) byAcceptChallenge. The three consent+login join queries now selectl.cell_idtoo, since the non-trusted-client path builds its session from the joinedConsentChallenge.identity.UserInfo.CellIDcarries the value from the gRPC handler through to session construction.core.NewSessionWithClaims: the access-token and ID-token sessions used to share oneextraclaims map by reference. The access-token claims are now a clone withu(always) andc(when known) added, base36-encoded, so the ID token's claim set is unaffected — only the access token is ever cell-routed.uis derived by parsingsubjectback to an integer; if that fails,uis omitted rather than guessing, same asc.pkg/storage.Int8OrNull, mirroring the existingTextOrNull/TimestampOrNullhelpers.docs/oauth-login-consent-api.mdupdated with the new field.docs/routable-tokens.md: new "Routing claims" section coveringu,c, the base36 encoding requirement, and why (cites the design doc and the router's decoder).
Testing
- Unit tests (
go test ./...): pass, including coverage onNewSessionWithClaimsforu/cpresence, base36 conversion, isolation from the ID token, and the non-numeric-subject fallback. - Integration tests against both backends (
ENV=development-l2/ENV=development-l1,go test -tags=integration ./auth/...): pass, including two end-to-end cases inhandler_integration_test.godriving the real DB-backed login+token flow (trusted-client and consent paths), asserting bothuandcland on the access token, base36-encoded, and neither on the ID token. golangci-lint: 0 issues../scripts/verify-agent-docs.sh: all documented claims hold.
Out of scope (tracked separately)
- The Cells HTTP Router ruleset change that will actually parse
u/coffgliamat-tokens (gitlab-org/cells/http-router, separate repo) — @bmarjanovic is picking this up (gitlab-com/gl-infra/tenant-scale/cells-infrastructure/team#866). gitlab-rails' side of populatingcell_idon theLogin.Acceptcall. - Add cell_id to IAM login accept and fix Workhor... (gitlab-org/gitlab!252916 - merged)