cloud_sdk_reqwest/blocking/
config.rs1use core::fmt;
2
3use reqwest::blocking::Client;
4#[cfg(feature = "blocking-rustls-fips")]
5use reqwest::blocking::ClientBuilder;
6use reqwest::redirect::Policy;
7use reqwest::tls::Version;
8#[cfg(feature = "blocking-rustls-fips")]
9use rustls::client::WebPkiServerVerifier;
10#[cfg(feature = "blocking-rustls-fips")]
11use rustls::crypto::CryptoProvider;
12#[cfg(feature = "blocking-rustls-fips")]
13use rustls::pki_types::CertificateRevocationListDer;
14#[cfg(feature = "blocking-rustls-fips")]
15use rustls::{ClientConfig, RootCertStore};
16#[cfg(feature = "blocking-rustls-fips")]
17use std::sync::Arc;
18#[cfg(feature = "blocking-rustls-fips")]
19use std::vec::Vec;
20
21use crate::shared::{BearerToken, BuildError, HttpsEndpoint, RequestTimeouts, UserAgent};
22
23use super::BlockingClient;
24
25#[cfg(feature = "blocking-rustls-fips")]
27pub struct FipsTlsPolicy {
28 roots: Arc<RootCertStore>,
29 crls: Vec<CertificateRevocationListDer<'static>>,
30}
31
32#[cfg(feature = "blocking-rustls-fips")]
33impl FipsTlsPolicy {
34 pub fn new(
37 roots: RootCertStore,
38 crls: Vec<CertificateRevocationListDer<'static>>,
39 ) -> Result<Self, BuildError> {
40 if roots.is_empty() {
41 return Err(BuildError::FipsTrustRootsRequired);
42 }
43 if crls.is_empty() {
44 return Err(BuildError::FipsCertificateRevocationListsRequired);
45 }
46 Ok(Self {
47 roots: Arc::new(roots),
48 crls,
49 })
50 }
51}
52
53#[cfg(feature = "blocking-rustls-fips")]
54impl fmt::Debug for FipsTlsPolicy {
55 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
56 formatter
57 .debug_struct("FipsTlsPolicy")
58 .field("trust_anchors", &self.roots.len())
59 .field("crls", &self.crls.len())
60 .finish_non_exhaustive()
61 }
62}
63
64pub struct BlockingClientBuilder {
67 endpoint: HttpsEndpoint,
68 token: BearerToken,
69 user_agent: UserAgent,
70 timeouts: RequestTimeouts,
71 #[cfg(feature = "blocking-rustls-fips")]
72 fips_tls_policy: Option<FipsTlsPolicy>,
73}
74
75impl BlockingClientBuilder {
76 #[must_use]
78 pub const fn new(
79 endpoint: HttpsEndpoint,
80 token: BearerToken,
81 user_agent: UserAgent,
82 timeouts: RequestTimeouts,
83 ) -> Self {
84 Self {
85 endpoint,
86 token,
87 user_agent,
88 timeouts,
89 #[cfg(feature = "blocking-rustls-fips")]
90 fips_tls_policy: None,
91 }
92 }
93
94 #[cfg(feature = "blocking-rustls-fips")]
96 #[must_use]
97 pub fn with_fips_tls_policy(mut self, policy: FipsTlsPolicy) -> Self {
98 self.fips_tls_policy = Some(policy);
99 self
100 }
101
102 pub fn build(self) -> Result<BlockingClient, BuildError> {
104 self.build_inner(true)
105 }
106
107 fn build_inner(self, https_only: bool) -> Result<BlockingClient, BuildError> {
108 let client = configured_client(&self, https_only)?;
109 Ok(BlockingClient::new(client, self.endpoint, self.token))
110 }
111
112 #[cfg(test)]
113 pub(super) fn build_for_loopback(self) -> Result<BlockingClient, BuildError> {
114 self.build_inner(false)
115 }
116}
117
118impl fmt::Debug for BlockingClientBuilder {
119 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
120 let mut debug = formatter.debug_struct("BlockingClientBuilder");
121 debug
122 .field("endpoint", &"[redacted]")
123 .field("token", &"[redacted]")
124 .field("user_agent", &self.user_agent)
125 .field("timeouts", &self.timeouts);
126 #[cfg(feature = "blocking-rustls-fips")]
127 debug.field("fips_tls_policy", &self.fips_tls_policy);
128 debug.finish()
129 }
130}
131
132fn configured_client(
133 configuration: &BlockingClientBuilder,
134 https_only: bool,
135) -> Result<Client, BuildError> {
136 configured_tls_builder(configuration)?
137 .https_only(https_only)
138 .http1_only()
139 .no_hickory_dns()
140 .min_tls_version(Version::TLS_1_2)
141 .redirect(Policy::none())
142 .retry(reqwest::retry::never())
143 .referer(false)
144 .no_proxy()
145 .no_gzip()
146 .no_brotli()
147 .no_zstd()
148 .no_deflate()
149 .timeout(configuration.timeouts.total())
150 .connect_timeout(configuration.timeouts.connect())
151 .connection_verbose(false)
152 .user_agent(configuration.user_agent.value.clone())
153 .build()
154 .map_err(|_| BuildError::ClientBuildFailed)
155}
156
157#[cfg(not(feature = "blocking-rustls-fips"))]
158fn configured_tls_builder(
159 _configuration: &BlockingClientBuilder,
160) -> Result<reqwest::blocking::ClientBuilder, BuildError> {
161 Ok(Client::builder().tls_backend_rustls())
162}
163
164#[cfg(feature = "blocking-rustls-fips")]
165fn configured_tls_builder(
166 configuration: &BlockingClientBuilder,
167) -> Result<ClientBuilder, BuildError> {
168 let policy = configuration
169 .fips_tls_policy
170 .as_ref()
171 .ok_or(BuildError::FipsTlsPolicyRequired)?;
172 let config = fips_client_config(policy)?;
173 Ok(Client::builder().tls_backend_preconfigured(config))
174}
175
176#[cfg(feature = "blocking-rustls-fips")]
177fn fips_client_config(policy: &FipsTlsPolicy) -> Result<ClientConfig, BuildError> {
178 let provider = Arc::new(rustls::crypto::default_fips_provider());
179 validate_fips_provider(provider.as_ref())?;
180 let config = client_config_with_provider(provider, policy)?;
181 validate_fips_config(&config)?;
182 Ok(config)
183}
184
185#[cfg(feature = "blocking-rustls-fips")]
186fn client_config_with_provider(
187 provider: Arc<CryptoProvider>,
188 policy: &FipsTlsPolicy,
189) -> Result<ClientConfig, BuildError> {
190 let verifier = WebPkiServerVerifier::builder_with_provider(
191 Arc::clone(&policy.roots),
192 Arc::clone(&provider),
193 )
194 .with_crls(policy.crls.iter().cloned())
195 .enforce_revocation_expiration()
196 .build()
197 .map_err(|_| BuildError::FipsRevocationVerifierFailed)?;
198 Ok(ClientConfig::builder_with_provider(provider)
199 .with_safe_default_protocol_versions()
200 .map_err(|_| BuildError::FipsProtocolConfigurationFailed)?
201 .dangerous()
202 .with_custom_certificate_verifier(verifier)
203 .with_no_client_auth())
204}
205
206#[cfg(feature = "blocking-rustls-fips")]
207fn validate_fips_provider(provider: &CryptoProvider) -> Result<(), BuildError> {
208 if provider.fips() {
209 Ok(())
210 } else {
211 Err(BuildError::FipsProviderRejected)
212 }
213}
214
215#[cfg(feature = "blocking-rustls-fips")]
216fn validate_fips_config(config: &ClientConfig) -> Result<(), BuildError> {
217 if config.fips() {
218 Ok(())
219 } else {
220 Err(BuildError::FipsClientConfigurationRejected)
221 }
222}
223
224#[cfg(all(test, feature = "blocking-rustls-fips"))]
225pub(super) fn test_fips_configuration(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
226 fips_client_config(policy).map(|config| config.fips())
227}
228
229#[cfg(all(test, feature = "blocking-rustls-fips"))]
230fn non_fips_provider() -> CryptoProvider {
231 #[derive(Debug)]
232 struct NonFipsRandom;
233
234 impl rustls::crypto::SecureRandom for NonFipsRandom {
235 fn fill(&self, _buffer: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> {
236 Err(rustls::crypto::GetRandomFailed)
237 }
238 }
239
240 static NON_FIPS_RANDOM: NonFipsRandom = NonFipsRandom;
241 let mut provider = rustls::crypto::default_fips_provider();
242 provider.secure_random = &NON_FIPS_RANDOM;
243 provider
244}
245
246#[cfg(all(test, feature = "blocking-rustls-fips"))]
247pub(super) fn test_non_fips_rejection(policy: &FipsTlsPolicy) -> Result<bool, BuildError> {
248 let provider = non_fips_provider();
249 let provider_rejected =
250 validate_fips_provider(&provider) == Err(BuildError::FipsProviderRejected);
251 let config = client_config_with_provider(Arc::new(provider), policy)?;
252 let config_rejected =
253 validate_fips_config(&config) == Err(BuildError::FipsClientConfigurationRejected);
254 Ok(provider_rejected && config_rejected)
255}
256
257#[cfg(all(test, feature = "blocking-rustls-fips"))]
258pub(super) fn test_non_fips_global_independence(policy: &FipsTlsPolicy) -> bool {
259 let provider = non_fips_provider();
260 if provider.fips() || provider.install_default().is_err() {
261 return false;
262 }
263 let global_is_non_fips = CryptoProvider::get_default().is_some_and(|value| !value.fips());
264 global_is_non_fips && test_fips_configuration(policy) == Ok(true)
265}