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)]
240struct ClientSettings<'a> {
241 user_agent: &'a UserAgent,
242 timeouts: RequestTimeouts,
243 #[cfg(feature = "blocking-rustls-fips")]
244 fips_tls_policy: Option<&'a FipsTlsPolicy>,
245}
246
247fn configured_client(settings: ClientSettings<'_>, https_only: bool) -> Result<Client, BuildError> {
248 configured_tls_builder(settings)?
249 .https_only(https_only)
250 .http1_only()
251 .no_hickory_dns()
252 .min_tls_version(Version::TLS_1_2)
253 .redirect(Policy::none())
254 .retry(reqwest::retry::never())
255 .referer(false)
256 .no_proxy()
257 .no_gzip()
258 .no_brotli()
259 .no_zstd()
260 .no_deflate()
261 .timeout(settings.timeouts.total())
262 .connect_timeout(settings.timeouts.connect())
263 .connection_verbose(false)
264 .user_agent(settings.user_agent.value.clone())
265 .build()
266 .map_err(|_| BuildError::ClientBuildFailed)
267}
268
269#[cfg(all(
270 not(feature = "blocking-rustls-fips"),
271 not(feature = "blocking-rustls-webpki-roots")
272))]
273fn raw_tls_config(_settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
274 platform_client_config()
275}
276
277#[cfg(all(
278 not(feature = "blocking-rustls-fips"),
279 feature = "blocking-rustls-webpki-roots"
280))]
281fn raw_tls_config(_settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
282 webpki_roots_client_config()
283}
284
285#[cfg(feature = "blocking-rustls-fips")]
286fn raw_tls_config(settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
287 let policy = settings
288 .fips_tls_policy
289 .ok_or(BuildError::FipsTlsPolicyRequired)?;
290 fips_client_config(policy)
291}
292
293#[cfg(all(
294 not(feature = "blocking-rustls-fips"),
295 not(feature = "blocking-rustls-webpki-roots")
296))]
297fn configured_tls_builder(
298 _settings: ClientSettings<'_>,
299) -> Result<reqwest::blocking::ClientBuilder, BuildError> {
300 Ok(Client::builder().tls_backend_rustls())
301}
302
303#[cfg(all(
304 not(feature = "blocking-rustls-fips"),
305 feature = "blocking-rustls-webpki-roots"
306))]
307fn configured_tls_builder(_settings: ClientSettings<'_>) -> Result<ClientBuilder, BuildError> {
308 let config = webpki_roots_client_config()?;
309 Ok(Client::builder().tls_backend_preconfigured(config))
310}
311
312#[cfg(all(
313 not(feature = "blocking-rustls-fips"),
314 feature = "blocking-rustls-webpki-roots"
315))]
316fn webpki_roots_client_config() -> Result<ClientConfig, BuildError> {
317 let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
318 let mut roots = RootCertStore::empty();
319 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
320 Ok(ClientConfig::builder_with_provider(provider)
321 .with_safe_default_protocol_versions()
322 .map_err(|_| BuildError::WebPkiRootsProtocolConfigurationFailed)?
323 .with_root_certificates(roots)
324 .with_no_client_auth())
325}
326
327#[cfg(all(
328 test,
329 not(feature = "blocking-rustls-fips"),
330 feature = "blocking-rustls-webpki-roots"
331))]
332pub(super) fn test_webpki_roots_configuration() -> Result<(usize, bool), BuildError> {
333 let config = webpki_roots_client_config()?;
334 Ok((webpki_roots::TLS_SERVER_ROOTS.len(), config.fips()))
335}
336
337#[cfg(feature = "blocking-rustls-fips")]
338fn configured_tls_builder(settings: ClientSettings<'_>) -> Result<ClientBuilder, BuildError> {
339 let policy = settings
340 .fips_tls_policy
341 .ok_or(BuildError::FipsTlsPolicyRequired)?;
342 let config = fips_client_config(policy)?;
343 Ok(Client::builder().tls_backend_preconfigured(config))
344}
345
346#[cfg(feature = "blocking-rustls-fips")]
347fn fips_client_config(policy: &FipsTlsPolicy) -> Result<ClientConfig, BuildError> {
348 let provider = Arc::new(rustls::crypto::default_fips_provider());
349 validate_fips_provider(provider.as_ref())?;
350 let config = client_config_with_provider(provider, policy)?;
351 validate_fips_config(&config)?;
352 Ok(config)
353}
354
355#[cfg(feature = "blocking-rustls-fips")]
356fn client_config_with_provider(
357 provider: Arc<CryptoProvider>,
358 policy: &FipsTlsPolicy,
359) -> Result<ClientConfig, BuildError> {
360 let verifier = WebPkiServerVerifier::builder_with_provider(
361 Arc::clone(&policy.roots),
362 Arc::clone(&provider),
363 )
364 .with_crls(policy.crls.iter().cloned())
365 .enforce_revocation_expiration()
366 .build()
367 .map_err(|_| BuildError::FipsRevocationVerifierFailed)?;
368 Ok(ClientConfig::builder_with_provider(provider)
369 .with_safe_default_protocol_versions()
370 .map_err(|_| BuildError::FipsProtocolConfigurationFailed)?
371 .dangerous()
372 .with_custom_certificate_verifier(verifier)
373 .with_no_client_auth())
374}
375
376#[cfg(feature = "blocking-rustls-fips")]
377fn validate_fips_provider(provider: &CryptoProvider) -> Result<(), BuildError> {
378 if provider.fips() {
379 Ok(())
380 } else {
381 Err(BuildError::FipsProviderRejected)
382 }
383}
384
385#[cfg(feature = "blocking-rustls-fips")]
386fn validate_fips_config(config: &ClientConfig) -> Result<(), BuildError> {
387 if config.fips() {
388 Ok(())
389 } else {
390 Err(BuildError::FipsClientConfigurationRejected)
391 }
392}
393
394#[cfg(all(test, feature = "blocking-rustls-fips"))]
395pub(super) fn test_fips_configuration(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
396 fips_client_config(policy).map(|config| config.fips())
397}
398
399#[cfg(all(test, feature = "blocking-rustls-fips"))]
400fn non_fips_provider() -> CryptoProvider {
401 #[derive(Debug)]
402 struct NonFipsRandom;
403
404 impl rustls::crypto::SecureRandom for NonFipsRandom {
405 fn fill(&self, _buffer: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> {
406 Err(rustls::crypto::GetRandomFailed)
407 }
408 }
409
410 static NON_FIPS_RANDOM: NonFipsRandom = NonFipsRandom;
411 let mut provider = rustls::crypto::default_fips_provider();
412 provider.secure_random = &NON_FIPS_RANDOM;
413 provider
414}
415
416#[cfg(all(test, feature = "blocking-rustls-fips"))]
417pub(super) fn test_non_fips_rejection(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
418 let provider = non_fips_provider();
419 let provider_rejected =
420 validate_fips_provider(&provider) == Err(BuildError::FipsProviderRejected);
421 let config = client_config_with_provider(Arc::new(provider), policy)?;
422 let config_rejected =
423 validate_fips_config(&config) == Err(BuildError::FipsClientConfigurationRejected);
424 Ok(provider_rejected && config_rejected)
425}
426
427#[cfg(all(test, feature = "blocking-rustls-fips"))]
428pub(super) fn test_non_fips_global_independence(policy: &FipsTlsPolicy) -> bool {
429 let provider = non_fips_provider();
430 if provider.fips() || provider.install_default().is_err() {
431 return false;
432 }
433 let global_is_non_fips = CryptoProvider::get_default().is_some_and(|value| !value.fips());
434 global_is_non_fips && test_fips_configuration(policy) == Ok(true)
435}