cloud_sdk_reqwest/blocking/
config.rs1use core::fmt;
2
3use reqwest::blocking::Client;
4#[cfg(any(
5 feature = "blocking-rustls-fips",
6 feature = "blocking-rustls-webpki-roots"
7))]
8use reqwest::blocking::ClientBuilder;
9use reqwest::redirect::Policy;
10use reqwest::tls::Version;
11use rustls::ClientConfig;
12#[cfg(any(
13 feature = "blocking-rustls-fips",
14 feature = "blocking-rustls-webpki-roots"
15))]
16use rustls::RootCertStore;
17#[cfg(feature = "blocking-rustls-fips")]
18use rustls::client::WebPkiServerVerifier;
19#[cfg(feature = "blocking-rustls-fips")]
20use rustls::crypto::CryptoProvider;
21#[cfg(feature = "blocking-rustls-fips")]
22use rustls::pki_types::CertificateRevocationListDer;
23#[cfg(any(
24 feature = "blocking-rustls-fips",
25 feature = "blocking-rustls-webpki-roots"
26))]
27use std::sync::Arc;
28#[cfg(feature = "blocking-rustls-fips")]
29use std::vec::Vec;
30
31#[cfg(all(
32 not(feature = "blocking-rustls-fips"),
33 not(feature = "blocking-rustls-webpki-roots")
34))]
35use crate::shared::platform_client_config;
36use crate::shared::{
37 BearerCredential, BuildError, HttpsEndpoint, RawHyperClient, RequestTimeouts, UserAgent,
38};
39
40use super::{BlockingClient, RawBlockingClient};
41
42#[cfg(feature = "blocking-rustls-fips")]
44pub struct FipsTlsPolicy {
45 roots: Arc<RootCertStore>,
46 crls: Vec<CertificateRevocationListDer<'static>>,
47}
48
49#[cfg(feature = "blocking-rustls-fips")]
50impl FipsTlsPolicy {
51 pub fn new(
54 roots: RootCertStore,
55 crls: Vec<CertificateRevocationListDer<'static>>,
56 ) -> Result<Self, BuildError> {
57 if roots.is_empty() {
58 return Err(BuildError::FipsTrustRootsRequired);
59 }
60 if crls.is_empty() {
61 return Err(BuildError::FipsCertificateRevocationListsRequired);
62 }
63 Ok(Self {
64 roots: Arc::new(roots),
65 crls,
66 })
67 }
68}
69
70#[cfg(feature = "blocking-rustls-fips")]
71impl fmt::Debug for FipsTlsPolicy {
72 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73 formatter
74 .debug_struct("FipsTlsPolicy")
75 .field("trust_anchors", &self.roots.len())
76 .field("crls", &self.crls.len())
77 .finish_non_exhaustive()
78 }
79}
80
81pub struct BlockingClientBuilder {
84 endpoint: HttpsEndpoint,
85 credential: BearerCredential,
86 user_agent: UserAgent,
87 timeouts: RequestTimeouts,
88 #[cfg(feature = "blocking-rustls-fips")]
89 fips_tls_policy: Option<FipsTlsPolicy>,
90}
91
92pub struct RawBlockingClientBuilder {
94 endpoint: HttpsEndpoint,
95 user_agent: UserAgent,
96 timeouts: RequestTimeouts,
97 #[cfg(feature = "blocking-rustls-fips")]
98 fips_tls_policy: Option<FipsTlsPolicy>,
99}
100
101impl BlockingClientBuilder {
102 #[must_use]
104 pub const fn new(
105 endpoint: HttpsEndpoint,
106 credential: BearerCredential,
107 user_agent: UserAgent,
108 timeouts: RequestTimeouts,
109 ) -> Self {
110 Self {
111 endpoint,
112 credential,
113 user_agent,
114 timeouts,
115 #[cfg(feature = "blocking-rustls-fips")]
116 fips_tls_policy: None,
117 }
118 }
119
120 #[cfg(feature = "blocking-rustls-fips")]
122 #[must_use]
123 pub fn with_fips_tls_policy(mut self, policy: FipsTlsPolicy) -> Self {
124 self.fips_tls_policy = Some(policy);
125 self
126 }
127
128 pub fn build(self) -> Result<BlockingClient, BuildError> {
130 self.build_inner(true)
131 }
132
133 fn build_inner(self, https_only: bool) -> Result<BlockingClient, BuildError> {
134 if !self.credential.scope.matches_endpoint(&self.endpoint) {
135 return Err(BuildError::CredentialEndpointMismatch);
136 }
137 let settings = ClientSettings {
138 user_agent: &self.user_agent,
139 timeouts: self.timeouts,
140 #[cfg(feature = "blocking-rustls-fips")]
141 fips_tls_policy: self.fips_tls_policy.as_ref(),
142 };
143 let client = configured_client(settings, https_only)?;
144 Ok(BlockingClient::new(
145 client,
146 self.endpoint,
147 self.credential,
148 !https_only,
149 ))
150 }
151
152 #[cfg(test)]
153 pub(super) fn build_for_loopback(self) -> Result<BlockingClient, BuildError> {
154 self.build_inner(false)
155 }
156}
157
158impl RawBlockingClientBuilder {
159 #[must_use]
161 pub const fn new(
162 endpoint: HttpsEndpoint,
163 user_agent: UserAgent,
164 timeouts: RequestTimeouts,
165 ) -> Self {
166 Self {
167 endpoint,
168 user_agent,
169 timeouts,
170 #[cfg(feature = "blocking-rustls-fips")]
171 fips_tls_policy: None,
172 }
173 }
174
175 #[cfg(feature = "blocking-rustls-fips")]
177 #[must_use]
178 pub fn with_fips_tls_policy(mut self, policy: FipsTlsPolicy) -> Self {
179 self.fips_tls_policy = Some(policy);
180 self
181 }
182
183 pub fn build(self) -> Result<RawBlockingClient, BuildError> {
185 self.build_inner(true)
186 }
187
188 fn build_inner(self, https_only: bool) -> Result<RawBlockingClient, BuildError> {
189 let settings = ClientSettings {
190 user_agent: &self.user_agent,
191 timeouts: self.timeouts,
192 #[cfg(feature = "blocking-rustls-fips")]
193 fips_tls_policy: self.fips_tls_policy.as_ref(),
194 };
195 let tls_config = raw_tls_config(settings)?;
196 let client = RawHyperClient::new(
197 self.endpoint.clone(),
198 &self.user_agent,
199 self.timeouts,
200 tls_config,
201 https_only,
202 )?;
203 Ok(RawBlockingClient::new(client, self.endpoint))
204 }
205
206 #[cfg(test)]
207 pub(super) fn build_for_loopback(self) -> Result<RawBlockingClient, BuildError> {
208 self.build_inner(false)
209 }
210}
211
212impl fmt::Debug for BlockingClientBuilder {
213 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
214 let mut debug = formatter.debug_struct("BlockingClientBuilder");
215 debug
216 .field("endpoint", &"[redacted]")
217 .field("credential", &"[redacted]")
218 .field("user_agent", &self.user_agent)
219 .field("timeouts", &self.timeouts);
220 #[cfg(feature = "blocking-rustls-fips")]
221 debug.field("fips_tls_policy", &self.fips_tls_policy);
222 debug.finish()
223 }
224}
225
226impl fmt::Debug for RawBlockingClientBuilder {
227 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
228 let mut debug = formatter.debug_struct("RawBlockingClientBuilder");
229 debug
230 .field("endpoint", &"[redacted]")
231 .field("user_agent", &self.user_agent)
232 .field("timeouts", &self.timeouts);
233 #[cfg(feature = "blocking-rustls-fips")]
234 debug.field("fips_tls_policy", &self.fips_tls_policy);
235 debug.finish()
236 }
237}
238
239#[derive(Clone, Copy)]
240pub(super) struct ClientSettings<'a> {
241 pub(super) user_agent: &'a UserAgent,
242 pub(super) timeouts: RequestTimeouts,
243 #[cfg(feature = "blocking-rustls-fips")]
244 pub(super) fips_tls_policy: Option<&'a FipsTlsPolicy>,
245}
246
247pub(super) fn configured_client(
248 settings: ClientSettings<'_>,
249 https_only: bool,
250) -> Result<Client, BuildError> {
251 configured_tls_builder(settings)?
252 .https_only(https_only)
253 .http1_only()
254 .no_hickory_dns()
255 .min_tls_version(Version::TLS_1_2)
256 .redirect(Policy::none())
257 .retry(reqwest::retry::never())
258 .referer(false)
259 .no_proxy()
260 .no_gzip()
261 .no_brotli()
262 .no_zstd()
263 .no_deflate()
264 .timeout(settings.timeouts.total())
265 .connect_timeout(settings.timeouts.connect())
266 .connection_verbose(false)
267 .user_agent(settings.user_agent.value.clone())
268 .build()
269 .map_err(|_| BuildError::ClientBuildFailed)
270}
271
272#[cfg(all(
273 not(feature = "blocking-rustls-fips"),
274 not(feature = "blocking-rustls-webpki-roots")
275))]
276fn raw_tls_config(_settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
277 platform_client_config()
278}
279
280#[cfg(all(
281 not(feature = "blocking-rustls-fips"),
282 feature = "blocking-rustls-webpki-roots"
283))]
284fn raw_tls_config(_settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
285 webpki_roots_client_config()
286}
287
288#[cfg(feature = "blocking-rustls-fips")]
289fn raw_tls_config(settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
290 let policy = settings
291 .fips_tls_policy
292 .ok_or(BuildError::FipsTlsPolicyRequired)?;
293 fips_client_config(policy)
294}
295
296#[cfg(all(
297 not(feature = "blocking-rustls-fips"),
298 not(feature = "blocking-rustls-webpki-roots")
299))]
300fn configured_tls_builder(
301 _settings: ClientSettings<'_>,
302) -> Result<reqwest::blocking::ClientBuilder, BuildError> {
303 Ok(Client::builder().tls_backend_rustls())
304}
305
306#[cfg(all(
307 not(feature = "blocking-rustls-fips"),
308 feature = "blocking-rustls-webpki-roots"
309))]
310fn configured_tls_builder(_settings: ClientSettings<'_>) -> Result<ClientBuilder, BuildError> {
311 let config = webpki_roots_client_config()?;
312 Ok(Client::builder().tls_backend_preconfigured(config))
313}
314
315#[cfg(all(
316 not(feature = "blocking-rustls-fips"),
317 feature = "blocking-rustls-webpki-roots"
318))]
319fn webpki_roots_client_config() -> Result<ClientConfig, BuildError> {
320 let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
321 let mut roots = RootCertStore::empty();
322 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
323 Ok(ClientConfig::builder_with_provider(provider)
324 .with_safe_default_protocol_versions()
325 .map_err(|_| BuildError::WebPkiRootsProtocolConfigurationFailed)?
326 .with_root_certificates(roots)
327 .with_no_client_auth())
328}
329
330#[cfg(all(
331 test,
332 not(feature = "blocking-rustls-fips"),
333 feature = "blocking-rustls-webpki-roots"
334))]
335pub(super) fn test_webpki_roots_configuration() -> Result<(usize, bool), BuildError> {
336 let config = webpki_roots_client_config()?;
337 Ok((webpki_roots::TLS_SERVER_ROOTS.len(), config.fips()))
338}
339
340#[cfg(feature = "blocking-rustls-fips")]
341fn configured_tls_builder(settings: ClientSettings<'_>) -> Result<ClientBuilder, BuildError> {
342 let policy = settings
343 .fips_tls_policy
344 .ok_or(BuildError::FipsTlsPolicyRequired)?;
345 let config = fips_client_config(policy)?;
346 Ok(Client::builder().tls_backend_preconfigured(config))
347}
348
349#[cfg(feature = "blocking-rustls-fips")]
350fn fips_client_config(policy: &FipsTlsPolicy) -> Result<ClientConfig, BuildError> {
351 let provider = Arc::new(rustls::crypto::default_fips_provider());
352 validate_fips_provider(provider.as_ref())?;
353 let config = client_config_with_provider(provider, policy)?;
354 validate_fips_config(&config)?;
355 Ok(config)
356}
357
358#[cfg(feature = "blocking-rustls-fips")]
359fn client_config_with_provider(
360 provider: Arc<CryptoProvider>,
361 policy: &FipsTlsPolicy,
362) -> Result<ClientConfig, BuildError> {
363 let verifier = WebPkiServerVerifier::builder_with_provider(
364 Arc::clone(&policy.roots),
365 Arc::clone(&provider),
366 )
367 .with_crls(policy.crls.iter().cloned())
368 .enforce_revocation_expiration()
369 .build()
370 .map_err(|_| BuildError::FipsRevocationVerifierFailed)?;
371 Ok(ClientConfig::builder_with_provider(provider)
372 .with_safe_default_protocol_versions()
373 .map_err(|_| BuildError::FipsProtocolConfigurationFailed)?
374 .dangerous()
375 .with_custom_certificate_verifier(verifier)
376 .with_no_client_auth())
377}
378
379#[cfg(feature = "blocking-rustls-fips")]
380fn validate_fips_provider(provider: &CryptoProvider) -> Result<(), BuildError> {
381 if provider.fips() {
382 Ok(())
383 } else {
384 Err(BuildError::FipsProviderRejected)
385 }
386}
387
388#[cfg(feature = "blocking-rustls-fips")]
389fn validate_fips_config(config: &ClientConfig) -> Result<(), BuildError> {
390 if config.fips() {
391 Ok(())
392 } else {
393 Err(BuildError::FipsClientConfigurationRejected)
394 }
395}
396
397#[cfg(all(test, feature = "blocking-rustls-fips"))]
398pub(super) fn test_fips_configuration(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
399 fips_client_config(policy).map(|config| config.fips())
400}
401
402#[cfg(all(test, feature = "blocking-rustls-fips"))]
403fn non_fips_provider() -> CryptoProvider {
404 #[derive(Debug)]
405 struct NonFipsRandom;
406
407 impl rustls::crypto::SecureRandom for NonFipsRandom {
408 fn fill(&self, _buffer: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> {
409 Err(rustls::crypto::GetRandomFailed)
410 }
411 }
412
413 static NON_FIPS_RANDOM: NonFipsRandom = NonFipsRandom;
414 let mut provider = rustls::crypto::default_fips_provider();
415 provider.secure_random = &NON_FIPS_RANDOM;
416 provider
417}
418
419#[cfg(all(test, feature = "blocking-rustls-fips"))]
420pub(super) fn test_non_fips_rejection(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
421 let provider = non_fips_provider();
422 let provider_rejected =
423 validate_fips_provider(&provider) == Err(BuildError::FipsProviderRejected);
424 let config = client_config_with_provider(Arc::new(provider), policy)?;
425 let config_rejected =
426 validate_fips_config(&config) == Err(BuildError::FipsClientConfigurationRejected);
427 Ok(provider_rejected && config_rejected)
428}
429
430#[cfg(all(test, feature = "blocking-rustls-fips"))]
431pub(super) fn test_non_fips_global_independence(policy: &FipsTlsPolicy) -> bool {
432 let provider = non_fips_provider();
433 if provider.fips() || provider.install_default().is_err() {
434 return false;
435 }
436 let global_is_non_fips = CryptoProvider::get_default().is_some_and(|value| !value.fips());
437 global_is_non_fips && test_fips_configuration(policy) == Ok(true)
438}