From 81b24ec1afad4c947a972b8c7f834806d7c0205e Mon Sep 17 00:00:00 2001 From: Anthony Lukach Date: Thu, 4 Jun 2026 23:18:52 -0700 Subject: [PATCH 1/2] feat(oidc-provider): make OidcCredentialProvider/CredentialCache cheaply cloneable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Put the cache's entries map behind an Arc and derive Clone on CredentialCache and OidcCredentialProvider. Cloning a provider now shares the same cache, so a runtime that rebuilds its dispatch chain per request (e.g. a Cloudflare Worker) can hold one provider in a shared/static slot and reuse it across requests — keeping the credential cache warm instead of re-minting a JWT and re-running AssumeRoleWithWebIdentity on every call. No behavior change for existing single-instance use. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/oidc-provider/src/cache.rs | 17 +++++++++-------- crates/oidc-provider/src/lib.rs | 5 +++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/oidc-provider/src/cache.rs b/crates/oidc-provider/src/cache.rs index 9ef156a..e684f52 100644 --- a/crates/oidc-provider/src/cache.rs +++ b/crates/oidc-provider/src/cache.rs @@ -16,21 +16,22 @@ use crate::BackendCredentials; const EXPIRY_MARGIN_SECS: i64 = 60; /// Thread-safe TTL cache for cloud credentials. +/// +/// `Clone` shares the same underlying store (the entries map is behind an +/// `Arc`), so a cloned [`OidcCredentialProvider`](crate::OidcCredentialProvider) +/// keeps hitting the same cache — letting a runtime hold the provider in a +/// shared/`static` slot and reuse it across requests instead of re-minting and +/// re-exchanging every time. +#[derive(Clone, Default)] pub struct CredentialCache { - entries: Mutex>>, -} - -impl Default for CredentialCache { - fn default() -> Self { - Self::new() - } + entries: Arc>>>, } impl CredentialCache { /// Create an empty credential cache. pub fn new() -> Self { Self { - entries: Mutex::new(HashMap::new()), + entries: Arc::new(Mutex::new(HashMap::new())), } } diff --git a/crates/oidc-provider/src/lib.rs b/crates/oidc-provider/src/lib.rs index 18d642d..a383a00 100644 --- a/crates/oidc-provider/src/lib.rs +++ b/crates/oidc-provider/src/lib.rs @@ -56,6 +56,11 @@ pub trait HttpExchange: } /// Top-level provider that combines signing, exchange, and caching. +/// +/// `Clone` is cheap and shares the credential cache (and the `Clone` HTTP +/// client), so a runtime can construct one provider and reuse it across requests +/// — keeping the cache warm instead of re-minting + re-exchanging every call. +#[derive(Clone)] pub struct OidcCredentialProvider { signer: JwtSigner, cache: CredentialCache, From c6ad03c999d2879ac6a4b0c61666beea28eaaae2 Mon Sep 17 00:00:00 2001 From: Anthony Lukach Date: Fri, 19 Jun 2026 21:33:50 -0700 Subject: [PATCH 2/2] chore: cargo fmt --- crates/oidc-provider/src/exchange/aws.rs | 6 +----- crates/oidc-provider/src/exchange/azure.rs | 6 +----- crates/oidc-provider/src/exchange/gcp.rs | 6 +----- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/crates/oidc-provider/src/exchange/aws.rs b/crates/oidc-provider/src/exchange/aws.rs index 821a4b7..f61e19b 100644 --- a/crates/oidc-provider/src/exchange/aws.rs +++ b/crates/oidc-provider/src/exchange/aws.rs @@ -83,11 +83,7 @@ impl AwsExchange { } impl CredentialExchange for AwsExchange { - async fn exchange( - &self, - http: &H, - jwt: &str, - ) -> Result { + async fn exchange(&self, http: &H, jwt: &str) -> Result { // Build the request with this module's `AssumeRoleWithWebIdentity`, hand // its (unencoded) pairs to the runtime's HTTP client — which // form-urlencodes them — then parse the reply. diff --git a/crates/oidc-provider/src/exchange/azure.rs b/crates/oidc-provider/src/exchange/azure.rs index af55c99..31403f7 100644 --- a/crates/oidc-provider/src/exchange/azure.rs +++ b/crates/oidc-provider/src/exchange/azure.rs @@ -49,11 +49,7 @@ impl AzureExchange { } impl CredentialExchange for AzureExchange { - async fn exchange( - &self, - http: &H, - jwt: &str, - ) -> Result { + async fn exchange(&self, http: &H, jwt: &str) -> Result { let form = [ ("grant_type", "client_credentials"), ( diff --git a/crates/oidc-provider/src/exchange/gcp.rs b/crates/oidc-provider/src/exchange/gcp.rs index cd4959a..c332735 100644 --- a/crates/oidc-provider/src/exchange/gcp.rs +++ b/crates/oidc-provider/src/exchange/gcp.rs @@ -51,11 +51,7 @@ impl GcpExchange { } impl CredentialExchange for GcpExchange { - async fn exchange( - &self, - http: &H, - jwt: &str, - ) -> Result { + async fn exchange(&self, http: &H, jwt: &str) -> Result { // Step 1: Exchange JWT for federated access token via GCP STS let sts_form = [ (