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;
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#[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 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
73pub 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 #[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 #[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 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}