feat(cache): shared single-flight credential cache, wired into OIDC backend auth#61
Open
alukach wants to merge 2 commits into
Open
feat(cache): shared single-flight credential cache, wired into OIDC backend auth#61alukach wants to merge 2 commits into
alukach wants to merge 2 commits into
Conversation
…ive refresh) Roadmap item 2 of #56. Caches short-lived FederatedCredentials per credential identity so a proxy doesn't re-mint on every request: - proactive refresh once within a configurable lead window of expiry (so a credential never expires mid-use), - single-flight: concurrent callers for the same key await one in-flight fetch via a per-key futures::lock::Mutex, - runtime-agnostic: the caller passes `now` (no clock dep) and no async runtime is required. Closure-based get_or_fetch(key, now, fetch) keeps it flexible; invalidate(key) supports drop-and-refetch on backend rejection. Tested: miss/hit/refresh/ invalidate/key-isolation and concurrent single-flight. Rebased onto main: the federation crate landed on main as backend-federation (#57), so this folds the cache into that crate instead of re-adding a duplicate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🚀 Latest commit deployed to https://multistore-proxy-pr-61.development-seed.workers.dev
|
de19e6b to
a6d0e15
Compare
…onto it PR #61 added a single-flight, proactively-refreshing CredentialCache to backend-federation, but oidc-provider already had its own simpler get/put cache (no single-flight, calls Utc::now() directly). Two caches for one concept, and the weaker one was the one actually on the hot path. Consolidate onto one implementation: - New crate `multistore-credential-cache` holds a generic `CredentialCache<T>` over any `T: Expiring`, plus the `Expiring` trait (with a blanket impl for `Arc<T>`). This is where #61's single-flight + proactive-refresh + closure- based get_or_fetch primitive now lives, generalized off the concrete FederatedCredentials type. - backend-federation drops its own cache module and just implements `Expiring` for FederatedCredentials. - oidc-provider deletes its bespoke cache and rewires `get_credentials` onto `get_or_fetch`, gaining single-flight and proactive refresh for free. The clock is now injected (a `now` param) instead of `Utc::now()` inside the cache, keeping the cache runtime-agnostic; backend_auth supplies it at the one production call site (consistent with the rest of the wasm-path crates). Also document caching across runtimes: new docs/architecture/caching.md covers the in-memory (per-isolate) / Cache API (per-colo) / KV (global) / Durable Object tiers, the layering pattern, and external-cache security best practices; backend-auth.md links to it. Verified: all affected crates' tests pass; native build, wasm checks (cf-workers + example), fmt, and clippy clean; docs site builds (links valid). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
📖 Docs preview deployed to https://multistore-docs-pr-61.development-seed.workers.dev
|
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.
Refs #56 (roadmap item 2: caching credential provider).
Rebased onto
main(#57 has landed, so the federation crate is nowmultistore-backend-federation).What
Adds a shared, single-flight, proactively-refreshing credential cache and wires the OIDC backend-auth hot path onto it — replacing the simpler bespoke cache that previously lived in
multistore-oidc-provider.Two commits:
feat— theCredentialCacheprimitive (single-flight + proactive refresh).refactor— extract it into a sharedmultistore-credential-cachecrate, generalize it over anyExpiringcredential type, and rewireoidc-provideronto it.The cache
Short-lived credentials are expensive to mint, so re-minting on every request hammers the issuing STS and adds latency.
CredentialCache<T>caches the current value per credential identity (an opaque key the caller chooses — e.g. a role ARN or the rendered OIDC subject) and:refresh_leadof expiry (so a credential never expires mid-use), andfutures::lock::Mutex) rather than each launching their own.It's generic over any
T: Expiring(with a blanket impl forArc<T>), so bothFederatedCredentials(AWS STS, inbackend-federation) andoidc-provider'sCloudCredentials(multi-cloud) share one implementation. Closure-basedget_or_fetchkeeps it flexible — the caller supplies the fetch, and the cache owns dedup + refresh.Consolidation
Before this PR there were two credential caches for one concept, and the one actually on the hot path was the weaker one:
oidc-providercache (removed)get_or_fetchclosureget/putnow(runtime-agnostic)Utc::now()inside the cacheoidc-provider'sget_credentialsnow goes throughget_or_fetch, gaining single-flight + proactive refresh. The clock is injected at the one production call site (backend_auth), consistent with the rest of the wasm-path crates.The cache lives in its own
multistore-credential-cachecrate (rather thancore) to keep the runtime-agnostic core uncoupled and match the workspace's small-focused-crate layout.Runtime-agnostic
nowas a parameter instead of reading a clock (Utc::now()isn't available onwasm32-unknown-unknownwithout extra features).futures::lock::Mutex, so it needs no async runtime (tokio is dev-only, for the concurrency test).&selfthroughout; cheap to share behind anArc.Docs
New Architecture → Caching page (
docs/architecture/caching.md): the in-memory (per-isolate) / Cloudflare Cache API (per-colo) / Workers KV (global) / Durable Object cache tiers, the layering pattern (Cache API as an L2 inside the fetch closure), and security best practices for an external credential cache. Added to the sidebar and cross-linked from Backend Auth.Tests / verification
multistore-credential-cache: 6 unit tests (miss / hit-while-fresh / refresh-within-lead / invalidate / key-isolation / concurrentsingle_flights_concurrent_fetches) + doctest.multistore-oidc-provider: existing 16 tests pass against the rewired cache.cf-workerscrate and the example) clean;cargo fmt --check+clippyclean; VitePress docs site builds with valid links.Scope note
Proactive refresh is single-flighted and synchronous: the caller that crosses the lead window awaits the refresh; concurrent callers await the per-key lock and then see the fresh value. True background refresh-ahead — return the still-valid credential instantly and refresh off-task — needs runtime task spawning (
tokio::spawnvswasm_bindgen_futures::spawn_local), so it's intentionally left as a follow-up rather than pulling a spawn abstraction into the crate now. Cross-isolate / cross-colo sharing (Cache API / KV / Durable Objects) is documented as a layering pattern but not implemented here.