[TT-17312] Gateway: client-IdP registry for JWT key & scope resolution#8268
Conversation
🎯 Recommended Merge TargetsBased on JIRA ticket TT-17312: Gateway: client IdP registry + JWT integration Fix Version: Tyk 5.14.0
Required:
📋 Workflow
|
|
This PR introduces an in-memory Files Changed AnalysisThe changes introduce a new, self-contained IdP registry and integrate it into the gateway's core systems. The majority of the new logic is in
Architecture & Impact Assessment
graph TD |
✅ Security Check PassedNo security issues found – changes LGTM. ✅ Architecture Check PassedNo architecture issues found – changes LGTM. ✅ Security Check PassedNo security issues found – changes LGTM. \n\n✅ Architecture Check PassedNo architecture issues found – changes LGTM. \n\nPerformance Issues (1)
Quality Issues (2)
Powered by Visor from Probelabs Last updated: 2026-06-05T14:30:14.584Z | Triggered by: pr_updated | Commit: 4ce1012 💡 TIP: You can chat with Visor using |
7599216 to
7df65f9
Compare
Validated end-to-end against the e2e framework (T1)Built the gateway from this branch + the dashboard from tyk-analytics #5756 ( This exercises the full chain (registry refresh via Bug caught & fixed by the e2eThe OAS run initially crashed the gateway with a nil-pointer panic in |
108999e to
a247b17
Compare
Addressed Visor findings (all Visor checks now green)Folded into the branch:
New tests: Note: the |
499c9da to
2d9dc70
Compare
…lution
Add an in-memory IdPRegistry (gateway/idp_registry.go) that joins APIs to
their client IdPs at request time, as a sibling dataset of the API definition
travelling the same transports. The JWT middleware probes it only as a last
fallback after the existing manual API-def branches, so manual config
(JWTSource/JWTJwksURIs/scope map) stays authoritative and byte-identical.
- Registry refresh mirrors the API-def load path: HTTP /system/clientidps for
direct-Dashboard (via executeDashboardRequestWithRecovery), RPC GetClientIdPs
for MDCB/edge, file mode deferred. Debounced ~100ms; build-then-swap rebuild
with a segment-aware backstop keyed on apisByID.
- JWT path: secretFromIdPRegistry resolves the signing key via a URL-keyed JWKS
helper (iss is an unverified hint only; JWKS fetches coalesced via singleflight
to avoid thundering herd); per-request matched binding travels on the request
context (ctx.MatchedIdPBinding), never the shared middleware struct.
Manual-first/binding-fill scope resolution in processCentralisedJWT.
- Direct-Dashboard: NoticeClientIdPChanged Redis signal refreshes only the
registry (no router rebuild, no APISpec reload). Payload is the
{org_id, client_idp_id} JSON contract.
- MDCB/edge: a parallel idpReloadLoop() long-polls the dedicated CheckIdPReload
RPC and refreshes ONLY the registry (one GetClientIdPs RPC) — no full
API/policy resync. GetClientIdPs + CheckIdPReload are registered in
dispatcherFuncs so the gorpc client can call them.
- Registry also refreshes on every DoReload.
Works for classic and OAS APIs.
2d9dc70 to
e76b0a9
Compare
…loadLoop SonarCloud: extract unverifiedIssuerHint + keyForBinding helpers to bring secretFromIdPRegistry cognitive complexity under the limit (S3776); inline the idpReloadLoop reload check (S8193).
Add unit coverage for the registry/JWT helpers and the RPC methods to clear the SonarCloud 80% new-code coverage gate: - unverifiedIssuerHint, keyForBinding, keyFromJWKSet, cachedJWKSet, secretFromIdPRegistry early returns, fetchJWKSKey cache-hit + fetch-error, unmarshalIdPs invalid input, fetchFromDashboard non-200. - Registry: Refresh debounce rebuild, fetchFromRPC default loader, doRefresh. - RPC: GetClientIdPs + CheckForIdPReload RPC-down error paths.
Mirror the API/policy RPC backup so an edge gateway keeps resolving registry-only JWT APIs when MDCB is unreachable: - saveRPCIdPsBackup / LoadIdPsFromRPCBackup store the raw GetClientIdPs payload under node-clientidp-backup:<tags> (encrypted), guarded against a nil storage handler. - fetchFromRPC loads from backup in emergency mode and refreshes the backup on a successful sync. Tests: save/load round-trip, malformed-payload rejection, and the emergency-mode fetch path.
sredxny
left a comment
There was a problem hiding this comment.
left some comments, we can discuss it
- Drop the brittle api_loader.go:403 line reference from the ctxSetMatchedBinding comment. - fetchFromDashboard uses http.NewRequestWithContext(gw.ctx, ...) so the request is cancelled on gateway shutdown. - Remove the no-op compress/decompress wrappers for the IdP backup; the small payload is stored raw (encrypted only).
- Inline two if-condition variable declarations in tests (idp_registry_test, rpc_test). - ST1005: drop trailing period and lowercase the client-IdP backup error strings in rpc_backup_handlers.
MDCB's CheckIdPReload returns the list of changed client_idp_id values, not a bool. Decode []string and use it as a 'something changed' signal: on a non-empty result, refresh the whole registry once via doRefresh (one GetClientIdPs RPC), which also handles deletes. Keeps the gateway's bulk-refresh design while matching the tyk-sink RPC signature.
errcheck (check-type-assertions) flagged the discarded ok in changed.([]string) as a reliability bug; check it explicitly.
|



Summary
Adds an in-memory
IdPRegistry(gateway/idp_registry.go) that joins an API to its client IdPs at request time, as a sibling dataset of the API definition travelling the same transports. The JWT middleware consults it only as a last fallback after the existing manual API-def branches, so DCR IdPs resolve without ever entering the API def, manual config stays authoritative, and a registry change refreshes just the registry — no router rebuild, no fleet-wide reload.Pairs with the dashboard side (
TykTechnologies/tyk-analytics#5756, the/system/clientidpsfeed +NoticeClientIdPChangedsignal) and the MDCBGetClientIdPsRPC handler (separate ticket).What changed
gateway/idp_registry.go(new) — registry with debounced (~100ms)Refresh, mode-branchingfetch(direct-Dashboard HTTP/system/clientidps, RPCGetClientIdPs, file-deferred), build-then-swaprebuildwith a segment-aware backstop keyed onapisByID, andscopeToPolicyMapForRequest(manual wins on collision, binding fills gaps).gateway/mw_jwt.go—secretFromIdPRegistrylast-fallback probe ingetSecretToVerifySignature; URL-keyedfetchJWKSKey(avoidsgetSecretFromURL'sJWTSource-gated cache miss); manual-first scope resolution inprocessCentralisedJWT;hasRegistryBindingrouting gate inProcessRequest.ctx/ctx.go+gateway/api.go—MatchedIdPBindingcontext key + helpers. Per-request binding lives on the request context, never the shared middleware struct (oneJWTMiddlewareper API,ProcessRequestruns concurrently).gateway/rpc_storage_handler.go+internal/policy/{rpc.go,store_mock.gen.go}—GetClientIdPsonRPCDataLoader+ impl + regenerated/handwritten mocks.gateway/redis_signals.go—NoticeClientIdPChanged(registry-only refresh).gateway/server.go—idpRegistryfield, init inNewGateway,doRefreshafterloadGlobalAppsinDoReloadWithError.Key properties
JWTSource/JWTJwksURIs/scope map always wins. For manual APIs the probe never runs (cost zero, byte-identical behaviour).issis an unverified hint — only selects which JWKS to try; signature verification +validateIssuerstill run afterward.scope_to_policymakes scope-name collisions between IdPs structurally impossible.executeDashboardRequestWithRecovery, inheriting nonce-failure re-registration and client config likeFromDashboardService.Testing
-race): decoder envelope/bare-array tolerance, scope-merge collision/fill, segment backstop, build-then-swap, RPC + Dashboard fetch (incl. nonce capture), ctx roundtrip, shutdown no-op, and the three acceptance criteria — AC1 registry resolves key+scope (200), AC2 manualJWTSourcewins, AC3NoticeClientIdPChangedrefreshes registry without an API reload.go build ./...+go vetclean; existingTestJWT*/TestJWKS*/TestJWTScopeToPolicyMapping/TestManagementNodeRedisEventspass (no regressions).Acceptance criteria
JWTSource/JWTJwksURIs+ a registry binding returns 200; token scope maps to the bound policy.JWTSourcebypasses the registry; manual wins on collision, binding fills absent scopes.loadGlobalApps.Notes / follow-ups
GetClientIdPsRPC call on edge nodes requires the MDCB-side handler (parallel ticket); they ship coordinated. Against an older sink the call errors and is swallowed (previous snapshot kept).iss(walk-all per spec). Bounded by binding count (typically 1) and on par with existing manual-JWKS refetch behaviour; negative-caching is a possible future hardening.Ticket Details
TT-17312
Generated at: 2026-06-05 14:28:38