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    BearerCredential, 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    credential: BearerCredential,
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        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    /// 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        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    /// Creates a complete raw blocking configuration without credentials.
160    #[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    /// Supplies mandatory deployment-managed roots and CRLs for FIPS TLS.
176    #[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    /// Builds an HTTPS-only executor with no implicit authorization.
184    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}