diff --git a/Cargo.toml b/Cargo.toml index bfe9713..29d39c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,16 @@ redis = { version = "1.2", features = ["tokio-comp"], optional = true } # features (see [features] below); `use_pem` is always required for PEM key # parsing and is therefore enabled unconditionally. jsonwebtoken = { version = "10", default-features = false, features = ["use_pem"] } -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } +# reqwest 0.13's `rustls` feature hardwires the aws-lc-rs provider (C FFI, awkward +# for the musl static build). We keep the pure-Rust `ring` provider instead: +# `rustls-no-provider` builds rustls without a bundled provider, and auth/jwks.rs +# hands reqwest a fully preconfigured rustls ClientConfig (ring provider + bundled +# Mozilla roots from webpki-roots). Bundling the roots keeps the binary self- +# contained, so it works on musl / scratch / distroless images with no system CA +# bundle (matching reqwest 0.12's old `rustls-tls` behaviour). +reqwest = { version = "0.13", default-features = false, features = ["rustls-no-provider", "json"] } +rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } +webpki-roots = "1" globset = "0.4" base64 = "0.22" sha2 = "0.11" diff --git a/src/auth/jwks.rs b/src/auth/jwks.rs index ec2cf7a..342a129 100644 --- a/src/auth/jwks.rs +++ b/src/auth/jwks.rs @@ -33,11 +33,30 @@ const MIN_REFRESH_INTERVAL: Duration = Duration::from_secs(60); /// Bound the worst-case latency of a slow/stalled JWKS endpoint. const JWKS_HTTP_TIMEOUT: Duration = Duration::from_secs(5); +/// Build the rustls client config for the JWKS HTTPS client. +/// +/// Uses the pure-Rust `ring` provider (installed per-config, not process-global) +/// and bundles Mozilla's root store via `webpki-roots`, so the binary needs no +/// system CA bundle and works in musl / scratch / distroless images. +fn build_tls_config() -> rustls::ClientConfig { + let mut roots = rustls::RootCertStore::empty(); + roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider())) + .with_safe_default_protocol_versions() + .expect("ring provider supports the default TLS protocol versions") + .with_root_certificates(roots) + .with_no_client_auth() +} + impl JwksCache { /// Create a cache for `uri` (keys are loaded lazily on first lookup). pub fn new(uri: String) -> Self { let client = reqwest::Client::builder() .timeout(JWKS_HTTP_TIMEOUT) + // Hand reqwest a fully preconfigured rustls backend rather than + // relying on a process-global default provider: no install ordering + // constraint, no global side effect, safe for library/test callers. + .tls_backend_preconfigured(build_tls_config()) .build() .unwrap_or_default(); Self {