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;
11#[cfg(feature = "blocking-rustls-fips")]
12use rustls::client::WebPkiServerVerifier;
13#[cfg(feature = "blocking-rustls-fips")]
14use rustls::crypto::CryptoProvider;
15#[cfg(feature = "blocking-rustls-fips")]
16use rustls::pki_types::CertificateRevocationListDer;
17#[cfg(any(
18    feature = "blocking-rustls-fips",
19    feature = "blocking-rustls-webpki-roots"
20))]
21use rustls::{ClientConfig, RootCertStore};
22#[cfg(any(
23    feature = "blocking-rustls-fips",
24    feature = "blocking-rustls-webpki-roots"
25))]
26use std::sync::Arc;
27#[cfg(feature = "blocking-rustls-fips")]
28use std::vec::Vec;
29
30use crate::shared::{BearerToken, BuildError, HttpsEndpoint, RequestTimeouts, UserAgent};
31
32use super::BlockingClient;
33
34/// Deployment-managed trust anchors and complete CRLs for FIPS TLS.
35#[cfg(feature = "blocking-rustls-fips")]
36pub struct FipsTlsPolicy {
37    roots: Arc<RootCertStore>,
38    crls: Vec<CertificateRevocationListDer<'static>>,
39}
40
41#[cfg(feature = "blocking-rustls-fips")]
42impl FipsTlsPolicy {
43    /// Creates a policy that checks the complete certificate chain, rejects
44    /// unknown revocation status, and rejects expired CRLs.
45    pub fn new(
46        roots: RootCertStore,
47        crls: Vec<CertificateRevocationListDer<'static>>,
48    ) -> Result<Self, BuildError> {
49        if roots.is_empty() {
50            return Err(BuildError::FipsTrustRootsRequired);
51        }
52        if crls.is_empty() {
53            return Err(BuildError::FipsCertificateRevocationListsRequired);
54        }
55        Ok(Self {
56            roots: Arc::new(roots),
57            crls,
58        })
59    }
60}
61
62#[cfg(feature = "blocking-rustls-fips")]
63impl fmt::Debug for FipsTlsPolicy {
64    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65        formatter
66            .debug_struct("FipsTlsPolicy")
67            .field("trust_anchors", &self.roots.len())
68            .field("crls", &self.crls.len())
69            .finish_non_exhaustive()
70    }
71}
72
73/// Builder requiring endpoint, bearer token, user agent, and all timeout
74/// dimensions before a client can be constructed.
75pub struct BlockingClientBuilder {
76    endpoint: HttpsEndpoint,
77    token: BearerToken,
78    user_agent: UserAgent,
79    timeouts: RequestTimeouts,
80    #[cfg(feature = "blocking-rustls-fips")]
81    fips_tls_policy: Option<FipsTlsPolicy>,
82}
83
84impl BlockingClientBuilder {
85    /// Creates a complete blocking-client configuration.
86    #[must_use]
87    pub const fn new(
88        endpoint: HttpsEndpoint,
89        token: BearerToken,
90        user_agent: UserAgent,
91        timeouts: RequestTimeouts,
92    ) -> Self {
93        Self {
94            endpoint,
95            token,
96            user_agent,
97            timeouts,
98            #[cfg(feature = "blocking-rustls-fips")]
99            fips_tls_policy: None,
100        }
101    }
102
103    /// Supplies mandatory deployment-managed roots and CRLs for FIPS TLS.
104    #[cfg(feature = "blocking-rustls-fips")]
105    #[must_use]
106    pub fn with_fips_tls_policy(mut self, policy: FipsTlsPolicy) -> Self {
107        self.fips_tls_policy = Some(policy);
108        self
109    }
110
111    /// Builds a hardened HTTPS-only client.
112    pub fn build(self) -> Result<BlockingClient, BuildError> {
113        self.build_inner(true)
114    }
115
116    fn build_inner(self, https_only: bool) -> Result<BlockingClient, BuildError> {
117        let client = configured_client(&self, https_only)?;
118        Ok(BlockingClient::new(client, self.endpoint, self.token))
119    }
120
121    #[cfg(test)]
122    pub(super) fn build_for_loopback(self) -> Result<BlockingClient, BuildError> {
123        self.build_inner(false)
124    }
125}
126
127impl fmt::Debug for BlockingClientBuilder {
128    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
129        let mut debug = formatter.debug_struct("BlockingClientBuilder");
130        debug
131            .field("endpoint", &"[redacted]")
132            .field("token", &"[redacted]")
133            .field("user_agent", &self.user_agent)
134            .field("timeouts", &self.timeouts);
135        #[cfg(feature = "blocking-rustls-fips")]
136        debug.field("fips_tls_policy", &self.fips_tls_policy);
137        debug.finish()
138    }
139}
140
141fn configured_client(
142    configuration: &BlockingClientBuilder,
143    https_only: bool,
144) -> Result<Client, BuildError> {
145    configured_tls_builder(configuration)?
146        .https_only(https_only)
147        .http1_only()
148        .no_hickory_dns()
149        .min_tls_version(Version::TLS_1_2)
150        .redirect(Policy::none())
151        .retry(reqwest::retry::never())
152        .referer(false)
153        .no_proxy()
154        .no_gzip()
155        .no_brotli()
156        .no_zstd()
157        .no_deflate()
158        .timeout(configuration.timeouts.total())
159        .connect_timeout(configuration.timeouts.connect())
160        .connection_verbose(false)
161        .user_agent(configuration.user_agent.value.clone())
162        .build()
163        .map_err(|_| BuildError::ClientBuildFailed)
164}
165
166#[cfg(all(
167    not(feature = "blocking-rustls-fips"),
168    not(feature = "blocking-rustls-webpki-roots")
169))]
170fn configured_tls_builder(
171    _configuration: &BlockingClientBuilder,
172) -> Result<reqwest::blocking::ClientBuilder, BuildError> {
173    Ok(Client::builder().tls_backend_rustls())
174}
175
176#[cfg(all(
177    not(feature = "blocking-rustls-fips"),
178    feature = "blocking-rustls-webpki-roots"
179))]
180fn configured_tls_builder(
181    _configuration: &BlockingClientBuilder,
182) -> Result<ClientBuilder, BuildError> {
183    let config = webpki_roots_client_config()?;
184    Ok(Client::builder().tls_backend_preconfigured(config))
185}
186
187#[cfg(all(
188    not(feature = "blocking-rustls-fips"),
189    feature = "blocking-rustls-webpki-roots"
190))]
191fn webpki_roots_client_config() -> Result<ClientConfig, BuildError> {
192    let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider());
193    let mut roots = RootCertStore::empty();
194    roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
195    Ok(ClientConfig::builder_with_provider(provider)
196        .with_safe_default_protocol_versions()
197        .map_err(|_| BuildError::WebPkiRootsProtocolConfigurationFailed)?
198        .with_root_certificates(roots)
199        .with_no_client_auth())
200}
201
202#[cfg(all(
203    test,
204    not(feature = "blocking-rustls-fips"),
205    feature = "blocking-rustls-webpki-roots"
206))]
207pub(super) fn test_webpki_roots_configuration() -> Result<(usize, bool), BuildError> {
208    let config = webpki_roots_client_config()?;
209    Ok((webpki_roots::TLS_SERVER_ROOTS.len(), config.fips()))
210}
211
212#[cfg(feature = "blocking-rustls-fips")]
213fn configured_tls_builder(
214    configuration: &BlockingClientBuilder,
215) -> Result<ClientBuilder, BuildError> {
216    let policy = configuration
217        .fips_tls_policy
218        .as_ref()
219        .ok_or(BuildError::FipsTlsPolicyRequired)?;
220    let config = fips_client_config(policy)?;
221    Ok(Client::builder().tls_backend_preconfigured(config))
222}
223
224#[cfg(feature = "blocking-rustls-fips")]
225fn fips_client_config(policy: &FipsTlsPolicy) -> Result<ClientConfig, BuildError> {
226    let provider = Arc::new(rustls::crypto::default_fips_provider());
227    validate_fips_provider(provider.as_ref())?;
228    let config = client_config_with_provider(provider, policy)?;
229    validate_fips_config(&config)?;
230    Ok(config)
231}
232
233#[cfg(feature = "blocking-rustls-fips")]
234fn client_config_with_provider(
235    provider: Arc<CryptoProvider>,
236    policy: &FipsTlsPolicy,
237) -> Result<ClientConfig, BuildError> {
238    let verifier = WebPkiServerVerifier::builder_with_provider(
239        Arc::clone(&policy.roots),
240        Arc::clone(&provider),
241    )
242    .with_crls(policy.crls.iter().cloned())
243    .enforce_revocation_expiration()
244    .build()
245    .map_err(|_| BuildError::FipsRevocationVerifierFailed)?;
246    Ok(ClientConfig::builder_with_provider(provider)
247        .with_safe_default_protocol_versions()
248        .map_err(|_| BuildError::FipsProtocolConfigurationFailed)?
249        .dangerous()
250        .with_custom_certificate_verifier(verifier)
251        .with_no_client_auth())
252}
253
254#[cfg(feature = "blocking-rustls-fips")]
255fn validate_fips_provider(provider: &CryptoProvider) -> Result<(), BuildError> {
256    if provider.fips() {
257        Ok(())
258    } else {
259        Err(BuildError::FipsProviderRejected)
260    }
261}
262
263#[cfg(feature = "blocking-rustls-fips")]
264fn validate_fips_config(config: &ClientConfig) -> Result<(), BuildError> {
265    if config.fips() {
266        Ok(())
267    } else {
268        Err(BuildError::FipsClientConfigurationRejected)
269    }
270}
271
272#[cfg(all(test, feature = "blocking-rustls-fips"))]
273pub(super) fn test_fips_configuration(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
274    fips_client_config(policy).map(|config| config.fips())
275}
276
277#[cfg(all(test, feature = "blocking-rustls-fips"))]
278fn non_fips_provider() -> CryptoProvider {
279    #[derive(Debug)]
280    struct NonFipsRandom;
281
282    impl rustls::crypto::SecureRandom for NonFipsRandom {
283        fn fill(&self, _buffer: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> {
284            Err(rustls::crypto::GetRandomFailed)
285        }
286    }
287
288    static NON_FIPS_RANDOM: NonFipsRandom = NonFipsRandom;
289    let mut provider = rustls::crypto::default_fips_provider();
290    provider.secure_random = &NON_FIPS_RANDOM;
291    provider
292}
293
294#[cfg(all(test, feature = "blocking-rustls-fips"))]
295pub(super) fn test_non_fips_rejection(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
296    let provider = non_fips_provider();
297    let provider_rejected =
298        validate_fips_provider(&provider) == Err(BuildError::FipsProviderRejected);
299    let config = client_config_with_provider(Arc::new(provider), policy)?;
300    let config_rejected =
301        validate_fips_config(&config) == Err(BuildError::FipsClientConfigurationRejected);
302    Ok(provider_rejected && config_rejected)
303}
304
305#[cfg(all(test, feature = "blocking-rustls-fips"))]
306pub(super) fn test_non_fips_global_independence(policy: &FipsTlsPolicy) -> bool {
307    let provider = non_fips_provider();
308    if provider.fips() || provider.install_default().is_err() {
309        return false;
310    }
311    let global_is_non_fips = CryptoProvider::get_default().is_some_and(|value| !value.fips());
312    global_is_non_fips && test_fips_configuration(policy) == Ok(true)
313}