Skip to content

Commit c224698

Browse files
ananth7592Copilot
andauthored
cosmos: add configurable TLS backend, enforced on reqwest (#4649)
## Problem The driver does not expose direct access to its HTTP transport (and won't, in a supported fashion). As a result, customers have no supported way to assert a specific TLS backend on the officially-supported `reqwest` transport. ## Change Introduces a supported, forward-looking mechanism to select and enforce the TLS backend. - **`TlsBackend` enum** — `#[non_exhaustive]`, one variant `TlsBackend::Rustls` (the `#[default]`). Added to `azure_data_cosmos_driver` and re-exported from `azure_data_cosmos`. - **`ConnectionPoolOptions::tls_backend`** — new option with `ConnectionPoolOptionsBuilder::with_tls_backend` and a `tls_backend()` getter, defaulting to `Rustls`. Builder-only (intentionally **not** environment-configurable). - **Enforced on reqwest** — `DefaultHttpClientFactory::build` selects the backend and calls `reqwest::ClientBuilder::tls_backend_rustls()`. The call is cfg-gated on the `rustls` feature because that reqwest method only exists when reqwest's rustls backend is compiled in; under a different TLS feature the reqwest default applies. Because there is a single variant and it is the default, **no customer configuration is required today**. The value is forward-looking: it lets us add new backends in a supported way, and (once #4616 lands) hand the selected backend to a user-provided `HttpClientFactory`. ## Files - `options/policies.rs` — new `TlsBackend` enum. - `options/mod.rs` (driver) and `options/mod.rs` (SDK) — export / re-export `TlsBackend`. - `options/connection_pool.rs` — `tls_backend` field, builder field, `with_tls_backend`, getter, default wiring; extended option tests. - `driver/transport/http_client_factory.rs` — enforce the backend on the reqwest builder; factory build test. - `CHANGELOG.md` (driver `0.6.0`, SDK `0.37.0`) — Features Added entries. ## Testing & coverage **Unit (added):** - `connection_pool_options_builder_defaults` — default `tls_backend()` is `Rustls`. - `connection_pool_options_builder_custom_values` — `with_tls_backend()` round-trips through the builder. - `default_factory_builds_client_with_default_tls_backend` (`reqwest`-gated) — builds a real `reqwest::Client` with the default backend, exercising `tls_backend_rustls()` and asserting `build()` succeeds. **Suites / static checks (green):** 1878 driver lib unit tests; `cargo clippy --all-features --all-targets`; `cargo doc`; `cargo fmt --check`; cSpell. Compile verified for both the `rustls` and the `not(rustls)` (`reqwest` + `native_tls`) feature paths — the main risk surface for the cfg-gated call. **Integration testing:** No dedicated integration test is added, by design. `Rustls` is the only variant and the default, and the default feature set already used reqwest-over-rustls — so behavior is unchanged and the existing emulator/recorded integration suites already exercise the rustls transport path end-to-end. A bespoke test could not assert more, because reqwest exposes no API to introspect which TLS backend negotiated a connection; the added factory test already proves the client builds and runs with the enforced backend. Fixes #4641. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 51ac7c3 commit c224698

7 files changed

Lines changed: 95 additions & 2 deletions

File tree

sdk/cosmos/azure_data_cosmos/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features Added
66

7+
- Added `TlsBackend` (re-exported) and a `tls_backend` option on `ConnectionPoolOptions` (`ConnectionPoolOptionsBuilder::with_tls_backend`), defaulting to `TlsBackend::Rustls`, available under the `rustls` feature, to pin the TLS backend used by the transport. This is additive and changes no behavior for the default (rustls) build; it only has an effect in builds that compile in multiple reqwest TLS backends, where reqwest would otherwise default to native-tls and the driver now pins rustls instead. ([#4649](https://github.com/Azure/azure-sdk-for-rust/pull/4649))
8+
79
### Breaking Changes
810

911
### Bugs Fixed

sdk/cosmos/azure_data_cosmos/src/options/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub use azure_data_cosmos_driver::options::{
2020
ReadConsistencyStrategy, Region, ServerCertificateValidation, ThrottlingRetryOptions,
2121
ThrottlingRetryOptionsBuilder, ThrottlingRetryOptionsView, ThroughputControlGroupOptions,
2222
ThroughputControlOptions, ThroughputControlOptionsBuilder, ThroughputControlOptionsView,
23-
UserAgentSuffix,
23+
TlsBackend, UserAgentSuffix,
2424
};
2525
pub use batch::{
2626
BatchDeleteOptions, BatchOptions, BatchReadOptions, BatchReplaceOptions, BatchUpsertOptions,

sdk/cosmos/azure_data_cosmos_driver/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### Features Added
66

7+
- Added `TlsBackend` (currently `TlsBackend::Rustls`, the default) and a `tls_backend` option on `ConnectionPoolOptions` (`ConnectionPoolOptionsBuilder::with_tls_backend` / `ConnectionPoolOptions::tls_backend`), available under the `rustls` feature. The driver asserts the selected backend on the `reqwest` transport, giving a supported way to pin the TLS backend without direct transport access. This is additive and changes no behavior for the default (rustls-only) build, where reqwest already negotiates rustls; it only has an effect in builds that compile in multiple reqwest TLS backends (e.g. `rustls` plus `native_tls`, absent reqwest's `http3` feature), where reqwest would otherwise default to native-tls and the driver now pins rustls instead. ([#4649](https://github.com/Azure/azure-sdk-for-rust/pull/4649))
8+
79
### Breaking Changes
810

911
### Bugs Fixed

sdk/cosmos/azure_data_cosmos_driver/src/driver/transport/http_client_factory.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@ mod tests {
134134
assert!(!config.allow_invalid_cert);
135135
assert!(config.with_allow_invalid_cert().allow_invalid_cert);
136136
}
137+
138+
#[cfg(feature = "rustls")]
139+
#[test]
140+
fn default_factory_builds_client_with_default_tls_backend() {
141+
// The default backend is `TlsBackend::Rustls`; building the client must
142+
// succeed (i.e. `tls_backend_rustls()` is wired up under the `rustls`
143+
// feature).
144+
let pool = ConnectionPoolOptionsBuilder::new().build().unwrap();
145+
assert_eq!(pool.tls_backend(), crate::options::TlsBackend::Rustls);
146+
let config = HttpClientConfig::metadata(&pool, TransportHttpVersion::Http2);
147+
let factory = DefaultHttpClientFactory::new();
148+
assert!(
149+
factory.build(&pool, config).is_ok(),
150+
"building the reqwest client with the default TLS backend should succeed"
151+
);
152+
}
137153
}
138154

139155
pub trait HttpClientFactory: fmt::Debug + Send + Sync {
@@ -187,6 +203,19 @@ impl HttpClientFactory for DefaultHttpClientFactory {
187203
}
188204
}
189205

206+
// Enforce the selected TLS backend on the reqwest transport. The driver
207+
// does not otherwise expose the transport, so this is the supported way
208+
// to assert a specific backend. The whole block compiles in only under
209+
// the `rustls` feature: `tls_backend_rustls()` (and the `tls_backend()`
210+
// accessor it reads) exist only then. With a different TLS feature the
211+
// backend is not driver-selectable and reqwest's own default applies.
212+
#[cfg(feature = "rustls")]
213+
{
214+
builder = match connection_pool.tls_backend() {
215+
crate::options::TlsBackend::Rustls => builder.tls_backend_rustls(),
216+
};
217+
}
218+
190219
builder = match config.version_policy {
191220
HttpVersionPolicy::Http11Only => {
192221
// HTTP/1.1: TCP keepalive for connection liveness detection.

sdk/cosmos/azure_data_cosmos_driver/src/options/connection_pool.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use super::env_parsing::{
1010
ValidationBounds,
1111
};
1212
use crate::options::ServerCertificateValidation;
13+
#[cfg(feature = "rustls")]
14+
use crate::options::TlsBackend;
1315

1416
/// Configuration for connection pooling behavior.
1517
///
@@ -69,6 +71,9 @@ pub struct ConnectionPoolOptions {
6971

7072
server_certificate_validation: ServerCertificateValidation,
7173

74+
#[cfg(feature = "rustls")]
75+
tls_backend: TlsBackend,
76+
7277
local_address: Option<IpAddr>,
7378
}
7479

@@ -222,6 +227,16 @@ impl ConnectionPoolOptions {
222227
self.server_certificate_validation
223228
}
224229

230+
/// Returns the TLS backend the `reqwest` transport is configured to use.
231+
///
232+
/// Only available when the `rustls` feature is enabled. With a different
233+
/// TLS feature the backend is not driver-selectable, so there is no honest
234+
/// value to report and this accessor is not compiled in.
235+
#[cfg(feature = "rustls")]
236+
pub fn tls_backend(&self) -> TlsBackend {
237+
self.tls_backend
238+
}
239+
225240
/// Returns the local IP address to bind to, if set.
226241
pub fn local_address(&self) -> Option<IpAddr> {
227242
self.local_address
@@ -390,6 +405,8 @@ pub struct ConnectionPoolOptionsBuilder {
390405
parser = parse_env_server_cert_validation
391406
)]
392407
server_certificate_validation: Option<ServerCertificateValidation>,
408+
#[cfg(feature = "rustls")]
409+
tls_backend: Option<TlsBackend>,
393410
#[option(env = "AZURE_COSMOS_LOCAL_ADDRESS")]
394411
local_address: Option<IpAddr>,
395412
}
@@ -598,6 +615,17 @@ impl ConnectionPoolOptionsBuilder {
598615
self
599616
}
600617

618+
/// Sets the TLS backend used by the `reqwest` transport.
619+
///
620+
/// Defaults to [`TlsBackend::Rustls`] when unset. Only available when the
621+
/// `rustls` feature is enabled; with a different TLS feature the backend is
622+
/// not driver-selectable.
623+
#[cfg(feature = "rustls")]
624+
pub fn with_tls_backend(mut self, value: TlsBackend) -> Self {
625+
self.tls_backend = Some(value);
626+
self
627+
}
628+
601629
/// Sets the local IP address to bind to.
602630
pub fn with_local_address(mut self, addr: IpAddr) -> Self {
603631
self.local_address = Some(addr);
@@ -888,6 +916,10 @@ impl ConnectionPoolOptionsBuilder {
888916
.server_certificate_validation
889917
.or(env.server_certificate_validation)
890918
.unwrap_or(ServerCertificateValidation::Required),
919+
// TLS backend is builder-only (not env-configurable); defaults to
920+
// `TlsBackend::Rustls`. Only present under the `rustls` feature.
921+
#[cfg(feature = "rustls")]
922+
tls_backend: self.tls_backend.unwrap_or_default(),
891923
// Builder override wins; otherwise the macro-parsed env value (or
892924
// `None` when unset / unparseable).
893925
local_address: self.local_address.or(env.local_address),
@@ -1101,6 +1133,21 @@ mod tests {
11011133
);
11021134
}
11031135

1136+
#[cfg(feature = "rustls")]
1137+
#[test]
1138+
fn tls_backend_defaults_to_rustls_and_is_settable() {
1139+
// Default build yields `TlsBackend::Rustls`...
1140+
let defaults = ConnectionPoolOptionsBuilder::new().build().unwrap();
1141+
assert_eq!(defaults.tls_backend(), TlsBackend::Rustls);
1142+
1143+
// ...and the builder round-trips an explicitly set backend.
1144+
let configured = ConnectionPoolOptionsBuilder::new()
1145+
.with_tls_backend(TlsBackend::Rustls)
1146+
.build()
1147+
.unwrap();
1148+
assert_eq!(configured.tls_backend(), TlsBackend::Rustls);
1149+
}
1150+
11041151
#[test]
11051152
fn min_connect_timeout_too_small() {
11061153
let result = ConnectionPoolOptionsBuilder::new()

sdk/cosmos/azure_data_cosmos_driver/src/options/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub use operation_options::{
4545
pub use partition_failover::{PartitionFailoverOptions, PartitionFailoverOptionsBuilder};
4646
pub use policies::{
4747
ContentResponseOnWrite, EndToEndOperationLatencyPolicy, ExcludedRegions,
48-
ServerCertificateValidation,
48+
ServerCertificateValidation, TlsBackend,
4949
};
5050
pub use priority::PriorityLevel;
5151
pub use read_consistency::ReadConsistencyStrategy;

sdk/cosmos/azure_data_cosmos_driver/src/options/policies.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,19 @@ impl ServerCertificateValidation {
156156
}
157157
}
158158

159+
/// Selects the TLS backend used by the officially-supported `reqwest` transport.
160+
///
161+
/// The driver does not expose direct access to the underlying HTTP transport, so
162+
/// this option is the supported mechanism for asserting a specific TLS backend.
163+
/// The default is [`TlsBackend::Rustls`].
164+
#[non_exhaustive]
165+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
166+
pub enum TlsBackend {
167+
/// Use the `rustls` TLS backend.
168+
#[default]
169+
Rustls,
170+
}
171+
159172
#[cfg(test)]
160173
mod tests {
161174
use std::time::Duration;

0 commit comments

Comments
 (0)