diff --git a/CHANGELOG.md b/CHANGELOG.md index 466311d..0bade2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.33] - 2026-05-13 + +### Fixed + +- **HTTPS connector missing from Entra credential chain.** Every HTTPS + request issued by the Microsoft Entra credential chain — including + `WorkloadIdentityCredential` (AKS / federated identity), + `ManagedIdentityCredential` (when going through AAD over HTTPS), and + the `ClientAssertionCredential` token endpoint — failed at hyper's + connector layer with `"invalid URL, scheme is not http"` before any + network I/O occurred. As a result, `PostgresProvider::new_with_entra` + and `new_with_schema_and_entra` were unreachable in production + topologies that rely on Workload Identity (the developer-tools + branch via `az login` was unaffected, which is why CI did not catch + the regression). Root cause: `azure_core`'s `reqwest` feature flows + through `typespec_client_core/reqwest`, which declares the optional + reqwest dep with `default-features = false`. With no TLS feature + enabled on the resolved reqwest build, hyper rejected every HTTPS URL + at the connector layer. Fixed by declaring an explicit + `reqwest = { version = "0.13", default-features = false, features = ["native-tls"] }` + dep in `Cargo.toml`. Cargo feature unification activates the + native-tls (openssl-backed) connector on the resolved reqwest build + without modifying any source code, preserving the crate's FIPS- + compliance posture (no rustls / ring / aws-lc-rs in the resolved + graph). Added a regression test + (`tests/native_tls_regression.rs`) that constructs the + `azure_core::http` client used by `azure_identity` and asserts the + TLS connector accepts `https://` URLs. + ## [0.1.32] - 2026-05-08 **Release:** diff --git a/Cargo.toml b/Cargo.toml index 9eff4f2..f5f6d05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "pg-stress"] [package] name = "duroxide-pg" -version = "0.1.32" +version = "0.1.33" edition = "2021" authors = ["Affan Dar "] description = "A PostgreSQL-based provider implementation for Duroxide, a durable task orchestration framework" @@ -51,16 +51,32 @@ include_dir = "0.7" # their `reqwest_rustls` default feature, which would otherwise pull a # rustls-based TLS stack into the dependency graph. We enable only the # minimum needed for HTTP transport (`reqwest` + gzip/deflate) and `tokio` -# for the async runtime; this routes through reqwest's own `default-tls` -# feature, keeping the build on native-tls across all platforms. +# for the async runtime. # -# Verified with `cargo tree --target x86_64-unknown-linux-gnu --all-features -# --all-targets` and a fresh Cargo.lock: no rustls-based TLS crates appear in -# the resolved dependency graph. +# The explicit `reqwest` dep below is required: `azure_core/reqwest` activates +# `typespec_client_core/reqwest`, which activates the optional reqwest dep +# with `default-features = false`. Without this line, the resolved reqwest +# build has NO TLS backend compiled in — every HTTPS request fails at the +# hyper connector with `"invalid URL, scheme is not http"`, which breaks +# `WorkloadIdentityCredential`, `ManagedIdentityCredential` (HTTPS-bound) +# and the `ClientAssertionCredential` token endpoint. Note that in +# reqwest 0.13.3 the `default-tls` feature resolves to rustls, NOT +# native-tls, so we cannot rely on enabling reqwest defaults to preserve +# the openssl posture; we must select `native-tls` explicitly. Cargo +# feature unification merges this dep with `typespec_client_core`'s reqwest +# dep at the same resolved version, activating the openssl-backed +# connector throughout the graph. +# +# Verified with `cargo tree --target x86_64-unknown-linux-gnu --edges normal` +# and a fresh Cargo.lock: no rustls / ring / aws-lc-rs / hyper-rustls +# crates appear in the resolved dependency graph; `hyper-tls` + +# `tokio-native-tls` + `native-tls` are activated. azure_core = { version = "0.35", default-features = false, features = ["reqwest", "reqwest_deflate", "reqwest_gzip", "tokio"] } azure_identity = { version = "0.35", default-features = false, features = ["tokio"] } +reqwest = { version = "0.13", default-features = false, features = ["native-tls"] } # Used for FutureExt::catch_unwind in the Entra refresh task panic guard. futures-util = { version = "0.3", default-features = false, features = ["std"] } [dev-dependencies] duroxide-pg-stress = { path = "pg-stress" } +url = "2" diff --git a/tests/native_tls_regression.rs b/tests/native_tls_regression.rs new file mode 100644 index 0000000..6f23e39 --- /dev/null +++ b/tests/native_tls_regression.rs @@ -0,0 +1,50 @@ +// Regression test for the missing-TLS-backend bug in the resolved reqwest build. +// +// Background: `azure_core 0.35`'s `reqwest` feature activates +// `typespec_client_core/reqwest`, which activates the optional reqwest dep +// with `default-features = false`. Without an explicit reqwest feature +// declaration in this crate's Cargo.toml, the resolved reqwest is compiled +// with NO TLS backend, and every HTTPS request fails at hyper's connector +// with `"invalid URL, scheme is not http"` — before any network I/O. +// +// This breaks `WorkloadIdentityCredential`, `ManagedIdentityCredential` +// (HTTPS branch), and `ClientAssertionCredential` token endpoints — the +// entire Entra credential chain assembled in `src/entra.rs`. +// +// This test exercises the exact `azure_core::http::new_http_client()` +// constructor used by `azure_identity` against an `https://` URL. The URL +// targets a closed local port so the request must fail at TCP-connect, +// NOT at connector initialization. The regression contract: the error +// `Display` must NOT contain `"scheme is not http"` — that substring is +// hyper's signature for "no TLS connector compiled in". +// +// See `CHANGELOG.md` (0.1.33) and `Cargo.toml`'s comment block for the +// dep-graph trace and rationale. + +use azure_core::http::{new_http_client, Method, Request}; +use url::Url; + +#[tokio::test(flavor = "current_thread")] +async fn entra_https_connector_is_compiled_in() { + let client = new_http_client(None); + + // Closed local port — fail-fast at TCP connect, no DNS, no network egress. + let url = Url::parse("https://127.0.0.1:1/").expect("valid url"); + let req = Request::new(url, Method::Get); + + let err = client + .execute_request(&req) + .await + .expect_err("https://127.0.0.1:1/ must fail to connect"); + + // Format the full source chain — the bug's signature is buried under + // anyhow-style wrapping otherwise. + let chain = format!("{err:#}"); + assert!( + !chain.contains("scheme is not http"), + "Regression: reqwest has no TLS backend compiled in. \ + The dependency graph dropped its TLS connector — likely the explicit \ + `reqwest = {{ ..., features = [\"native-tls\"] }}` line was removed from \ + Cargo.toml. Full error chain:\n{chain}" + ); +}