Skip to main content

cloud_sdk_reqwest/blocking/
config.rs

1use 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/// Deployment-managed trust anchors and complete CRLs for FIPS TLS.
43#[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    /// Creates a policy that checks the complete certificate chain, rejects
52    /// unknown revocation status, and rejects expired CRLs.
53    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
81/// Builder requiring endpoint, bearer token, user agent, and all timeout
82/// dimensions before a client can be constructed.
83pub 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
92/// Builder for a raw client with no credential, provider, media, or retry policy.
93pub 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    /// Creates a complete blocking-client configuration.
103    #[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    /// Supplies mandatory deployment-managed roots and CRLs for FIPS TLS.
121    #[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    /// Builds a hardened HTTPS-only client.
129    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    /// Creates a complete raw blocking configuration without credentials.
152    #[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    /// Supplies mandatory deployment-managed roots and CRLs for FIPS TLS.
168    #[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    /// Builds an HTTPS-only executor with no implicit authorization.
176    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}