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 BearerToken, 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 token: BearerToken,
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 token: BearerToken,
107 user_agent: UserAgent,
108 timeouts: RequestTimeouts,
109 ) -> Self {
110 Self {
111 endpoint,
112 token,
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 let settings = ClientSettings {
135 user_agent: &self.user_agent,
136 timeouts: self.timeouts,
137 #[cfg(feature = "blocking-rustls-fips")]
138 fips_tls_policy: self.fips_tls_policy.as_ref(),
139 };
140 let client = configured_client(settings, https_only)?;
141 Ok(BlockingClient::new(client, self.endpoint, self.token))
142 }
143
144 #[cfg(test)]
145 pub(super) fn build_for_loopback(self) -> Result<BlockingClient, BuildError> {
146 self.build_inner(false)
147 }
148}
149
150impl RawBlockingClientBuilder {
151 #[must_use]
153 pub const fn new(
154 endpoint: HttpsEndpoint,
155 user_agent: UserAgent,
156 timeouts: RequestTimeouts,
157 ) -> Self {
158 Self {
159 endpoint,
160 user_agent,
161 timeouts,
162 #[cfg(feature = "blocking-rustls-fips")]
163 fips_tls_policy: None,
164 }
165 }
166
167 #[cfg(feature = "blocking-rustls-fips")]
169 #[must_use]
170 pub fn with_fips_tls_policy(mut self, policy: FipsTlsPolicy) -> Self {
171 self.fips_tls_policy = Some(policy);
172 self
173 }
174
175 pub fn build(self) -> Result<RawBlockingClient, BuildError> {
177 self.build_inner(true)
178 }
179
180 fn build_inner(self, https_only: bool) -> Result<RawBlockingClient, BuildError> {
181 let settings = ClientSettings {
182 user_agent: &self.user_agent,
183 timeouts: self.timeouts,
184 #[cfg(feature = "blocking-rustls-fips")]
185 fips_tls_policy: self.fips_tls_policy.as_ref(),
186 };
187 let tls_config = raw_tls_config(settings)?;
188 let client = RawHyperClient::new(
189 self.endpoint.clone(),
190 &self.user_agent,
191 self.timeouts,
192 tls_config,
193 https_only,
194 )?;
195 Ok(RawBlockingClient::new(client, self.endpoint))
196 }
197
198 #[cfg(test)]
199 pub(super) fn build_for_loopback(self) -> Result<RawBlockingClient, BuildError> {
200 self.build_inner(false)
201 }
202}
203
204impl fmt::Debug for BlockingClientBuilder {
205 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
206 let mut debug = formatter.debug_struct("BlockingClientBuilder");
207 debug
208 .field("endpoint", &"[redacted]")
209 .field("token", &"[redacted]")
210 .field("user_agent", &self.user_agent)
211 .field("timeouts", &self.timeouts);
212 #[cfg(feature = "blocking-rustls-fips")]
213 debug.field("fips_tls_policy", &self.fips_tls_policy);
214 debug.finish()
215 }
216}
217
218impl fmt::Debug for RawBlockingClientBuilder {
219 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
220 let mut debug = formatter.debug_struct("RawBlockingClientBuilder");
221 debug
222 .field("endpoint", &"[redacted]")
223 .field("user_agent", &self.user_agent)
224 .field("timeouts", &self.timeouts);
225 #[cfg(feature = "blocking-rustls-fips")]
226 debug.field("fips_tls_policy", &self.fips_tls_policy);
227 debug.finish()
228 }
229}
230
231#[derive(Clone, Copy)]
232struct ClientSettings<'a> {
233 user_agent: &'a UserAgent,
234 timeouts: RequestTimeouts,
235 #[cfg(feature = "blocking-rustls-fips")]
236 fips_tls_policy: Option<&'a FipsTlsPolicy>,
237}
238
239fn configured_client(settings: ClientSettings<'_>, https_only: bool) -> Result<Client, BuildError> {
240 configured_tls_builder(settings)?
241 .https_only(https_only)
242 .http1_only()
243 .no_hickory_dns()
244 .min_tls_version(Version::TLS_1_2)
245 .redirect(Policy::none())
246 .retry(reqwest::retry::never())
247 .referer(false)
248 .no_proxy()
249 .no_gzip()
250 .no_brotli()
251 .no_zstd()
252 .no_deflate()
253 .timeout(settings.timeouts.total())
254 .connect_timeout(settings.timeouts.connect())
255 .connection_verbose(false)
256 .user_agent(settings.user_agent.value.clone())
257 .build()
258 .map_err(|_| BuildError::ClientBuildFailed)
259}
260
261#[cfg(all(
262 not(feature = "blocking-rustls-fips"),
263 not(feature = "blocking-rustls-webpki-roots")
264))]
265fn raw_tls_config(_settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
266 platform_client_config()
267}
268
269#[cfg(all(
270 not(feature = "blocking-rustls-fips"),
271 feature = "blocking-rustls-webpki-roots"
272))]
273fn raw_tls_config(_settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
274 webpki_roots_client_config()
275}
276
277#[cfg(feature = "blocking-rustls-fips")]
278fn raw_tls_config(settings: ClientSettings<'_>) -> Result<ClientConfig, BuildError> {
279 let policy = settings
280 .fips_tls_policy
281 .ok_or(BuildError::FipsTlsPolicyRequired)?;
282 fips_client_config(policy)
283}
284
285#[cfg(all(
286 not(feature = "blocking-rustls-fips"),
287 not(feature = "blocking-rustls-webpki-roots")
288))]
289fn configured_tls_builder(
290 _settings: ClientSettings<'_>,
291) -> Result<reqwest::blocking::ClientBuilder, BuildError> {
292 Ok(Client::builder().tls_backend_rustls())
293}
294
295#[cfg(all(
296 not(feature = "blocking-rustls-fips"),
297 feature = "blocking-rustls-webpki-roots"
298))]
299fn configured_tls_builder(_settings: ClientSettings<'_>) -> Result<ClientBuilder, BuildError> {
300 let config = webpki_roots_client_config()?;
301 Ok(Client::builder().tls_backend_preconfigured(config))
302}
303
304#[cfg(all(
305 not(feature = "blocking-rustls-fips"),
306 feature = "blocking-rustls-webpki-roots"
307))]
308fn webpki_roots_client_config() -> Result<ClientConfig, BuildError> {
309 let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
310 let mut roots = RootCertStore::empty();
311 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
312 Ok(ClientConfig::builder_with_provider(provider)
313 .with_safe_default_protocol_versions()
314 .map_err(|_| BuildError::WebPkiRootsProtocolConfigurationFailed)?
315 .with_root_certificates(roots)
316 .with_no_client_auth())
317}
318
319#[cfg(all(
320 test,
321 not(feature = "blocking-rustls-fips"),
322 feature = "blocking-rustls-webpki-roots"
323))]
324pub(super) fn test_webpki_roots_configuration() -> Result<(usize, bool), BuildError> {
325 let config = webpki_roots_client_config()?;
326 Ok((webpki_roots::TLS_SERVER_ROOTS.len(), config.fips()))
327}
328
329#[cfg(feature = "blocking-rustls-fips")]
330fn configured_tls_builder(settings: ClientSettings<'_>) -> Result<ClientBuilder, BuildError> {
331 let policy = settings
332 .fips_tls_policy
333 .ok_or(BuildError::FipsTlsPolicyRequired)?;
334 let config = fips_client_config(policy)?;
335 Ok(Client::builder().tls_backend_preconfigured(config))
336}
337
338#[cfg(feature = "blocking-rustls-fips")]
339fn fips_client_config(policy: &FipsTlsPolicy) -> Result<ClientConfig, BuildError> {
340 let provider = Arc::new(rustls::crypto::default_fips_provider());
341 validate_fips_provider(provider.as_ref())?;
342 let config = client_config_with_provider(provider, policy)?;
343 validate_fips_config(&config)?;
344 Ok(config)
345}
346
347#[cfg(feature = "blocking-rustls-fips")]
348fn client_config_with_provider(
349 provider: Arc<CryptoProvider>,
350 policy: &FipsTlsPolicy,
351) -> Result<ClientConfig, BuildError> {
352 let verifier = WebPkiServerVerifier::builder_with_provider(
353 Arc::clone(&policy.roots),
354 Arc::clone(&provider),
355 )
356 .with_crls(policy.crls.iter().cloned())
357 .enforce_revocation_expiration()
358 .build()
359 .map_err(|_| BuildError::FipsRevocationVerifierFailed)?;
360 Ok(ClientConfig::builder_with_provider(provider)
361 .with_safe_default_protocol_versions()
362 .map_err(|_| BuildError::FipsProtocolConfigurationFailed)?
363 .dangerous()
364 .with_custom_certificate_verifier(verifier)
365 .with_no_client_auth())
366}
367
368#[cfg(feature = "blocking-rustls-fips")]
369fn validate_fips_provider(provider: &CryptoProvider) -> Result<(), BuildError> {
370 if provider.fips() {
371 Ok(())
372 } else {
373 Err(BuildError::FipsProviderRejected)
374 }
375}
376
377#[cfg(feature = "blocking-rustls-fips")]
378fn validate_fips_config(config: &ClientConfig) -> Result<(), BuildError> {
379 if config.fips() {
380 Ok(())
381 } else {
382 Err(BuildError::FipsClientConfigurationRejected)
383 }
384}
385
386#[cfg(all(test, feature = "blocking-rustls-fips"))]
387pub(super) fn test_fips_configuration(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
388 fips_client_config(policy).map(|config| config.fips())
389}
390
391#[cfg(all(test, feature = "blocking-rustls-fips"))]
392fn non_fips_provider() -> CryptoProvider {
393 #[derive(Debug)]
394 struct NonFipsRandom;
395
396 impl rustls::crypto::SecureRandom for NonFipsRandom {
397 fn fill(&self, _buffer: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> {
398 Err(rustls::crypto::GetRandomFailed)
399 }
400 }
401
402 static NON_FIPS_RANDOM: NonFipsRandom = NonFipsRandom;
403 let mut provider = rustls::crypto::default_fips_provider();
404 provider.secure_random = &NON_FIPS_RANDOM;
405 provider
406}
407
408#[cfg(all(test, feature = "blocking-rustls-fips"))]
409pub(super) fn test_non_fips_rejection(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
410 let provider = non_fips_provider();
411 let provider_rejected =
412 validate_fips_provider(&provider) == Err(BuildError::FipsProviderRejected);
413 let config = client_config_with_provider(Arc::new(provider), policy)?;
414 let config_rejected =
415 validate_fips_config(&config) == Err(BuildError::FipsClientConfigurationRejected);
416 Ok(provider_rejected && config_rejected)
417}
418
419#[cfg(all(test, feature = "blocking-rustls-fips"))]
420pub(super) fn test_non_fips_global_independence(policy: &FipsTlsPolicy) -> bool {
421 let provider = non_fips_provider();
422 if provider.fips() || provider.install_default().is_err() {
423 return false;
424 }
425 let global_is_non_fips = CryptoProvider::get_default().is_some_and(|value| !value.fips());
426 global_is_non_fips && test_fips_configuration(policy) == Ok(true)
427}