fix: Reuse IdP-issued client tokens until near expiry - #6687
Open
larrysingleton007 wants to merge 4 commits into
Open
fix: Reuse IdP-issued client tokens until near expiry#6687larrysingleton007 wants to merge 4 commits into
larrysingleton007 wants to merge 4 commits into
Conversation
The client auth interceptors build a fresh manager for every outbound RPC, so the client_secret flow paid a discovery GET plus a token POST against the IdP per request and discarded the token. A batch loop multiplies IdP load by request count and can trip IdP rate limits. Cache issued tokens in a module-level store keyed by the token-request identity, reusing each until 30 seconds before its expiry, read from the token's exp claim with the token response's expires_in as fallback. A token whose expiry cannot be determined is not cached, preserving the per-call behavior for opaque tokens. Static, env-var, and mounted service-account token sources are unaffected. Measured with counting mocks: 5 outbound calls previously performed 5 discovery GETs and 5 token POSTs; now 1 and 1. Signed-off-by: Larry Singleton <166439969+larrysingleton007@users.noreply.github.com>
Review feedback pattern from feast-dev#6683: the maintainer asked there for the hardcoded JWKS cache tunables to move onto OidcAuthConfig, and this change had the same shape of hardcoded constant. Expose token_refresh_margin_seconds on OidcClientAuthConfig (rather than the shared OidcAuthConfig, since only clients fetch tokens from the IdP), defaulting to 30 and rejecting non-positive values: a zero or negative margin would let a token be reused right up to or past its expiry. Also from review: soften a comment that claimed a reused token 'cannot' expire in flight, which is a fixed heuristic rather than a guarantee under clock skew; state in the docstring that near-expiry tokens are not cached either; and rewrite a tuple-with-conditional return that two reviewers had to verify empirically before accepting it was not a precedence bug. Signed-off-by: Larry Singleton <166439969+larrysingleton007@users.noreply.github.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #6687 +/- ##
==========================================
+ Coverage 46.77% 46.81% +0.04%
==========================================
Files 414 414
Lines 50191 50229 +38
Branches 7181 7188 +7
==========================================
+ Hits 23475 23513 +38
+ Misses 25077 25074 -3
- Partials 1639 1642 +3
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does / why we need it
This is the client-side sibling of #6683. On the
client_secret(client credentials / ROPC) branch, every outbound RPC builds a fresh auth manager and throws away the token it fetched:get_auth_tokenconstructs a new factory, manager, andOIDCDiscoveryServiceper call (client_auth_token.py).sending_headers, and the HTTP wrapper on every session reuse.So each request pays a discovery GET plus a token POST against the IdP for a token that is typically valid for an hour. Measured with counting mocks on master: 5 outbound calls produce 5 discovery GETs and 5 token POSTs. Beyond the added latency, a batch loop multiplies IdP load by request count, which can trip provider rate limits.
The fix caches issued tokens keyed by the token-request identity (discovery URL, client id and secret, username, password) and reuses each until shortly before expiry, taken from the token's own
expclaim with the token response'sexpires_inas a fallback. After the change the same measurement performs 1 discovery GET and 1 token POST.The cache is module level because the interceptors construct a fresh manager per RPC, so instance state cannot survive between calls. Only the
client_secretbranch is affected; static tokens,token_env_var, and mounted service account tokens were already cheap and are untouched.A few details worth reviewer attention:
token_refresh_margin_seconds(default 30) is exposed onOidcClientAuthConfigrather than hardcoded, following the same request you made on fix: Reuse the OIDC JWKS client across requests #6683. It rejects non-positive values, since a zero or negative margin would allow reuse right up to or past expiry. It sits on the client config rather than the sharedOidcAuthConfigbecause only clients fetch tokens from the IdP.expires_in) is deliberately not cached, preserving today's per-call behavior rather than guessing a lifetime.One interaction worth flagging:
HttpSessionManager.get_sessionre-callsget_auth_tokenon every session cache hit, with a comment from #5895 explaining it does so "in case it expired". After this change that call returns a cached token rather than a freshly fetched one. The intent still holds, because the cache never returns a token with less than the margin remaining, but the mechanism changes and it seemed better to say so than to let a reviewer find it.Testing: 7 new unit tests covering reuse until expiry, refusal to cache inside the refresh margin, the
expires_infallback for opaque tokens, no caching when expiry is unknowable, cache keying across distinct configs, the configurable margin changing reuse behavior, and rejection of non-positive margins. An autouse fixture resets the module cache around every test so no test inherits or leaks a cached token. 315 permissions tests pass; ruff and mypy are clean.Which issue(s) this PR fixes
Fixes #6684