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)]
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}