Skip to main content

s3/auth/
provider.rs

1use std::sync::Arc;
2
3#[cfg(feature = "async")]
4use std::{future::Future, pin::Pin};
5
6#[cfg(all(
7    any(feature = "async", feature = "blocking"),
8    any(feature = "credentials-imds", feature = "credentials-sts")
9))]
10use reqx::advanced::TlsRootStore;
11
12use crate::Result;
13
14use super::{Auth, Credentials};
15
16#[cfg(any(feature = "async", feature = "blocking"))]
17use super::CredentialsSnapshot;
18#[cfg(feature = "credentials-sts")]
19use super::Region;
20#[cfg(any(feature = "credentials-imds", feature = "credentials-sts"))]
21use super::cache::CachedProvider;
22
23#[cfg(feature = "async")]
24/// Async credentials lookup future.
25pub type CredentialsFuture<'a> =
26    Pin<Box<dyn Future<Output = Result<CredentialsSnapshot>> + Send + 'a>>;
27
28/// Source of credential snapshots for request signing.
29///
30/// Implement this trait when credentials may rotate over time. If the underlying provider performs
31/// network calls or expensive refreshes, wrap it in [`crate::CachedProvider`] so multiple requests
32/// can share cached credentials and coalesce refresh work.
33pub trait CredentialsProvider: std::fmt::Debug + Send + Sync {
34    /// Returns credentials asynchronously.
35    #[cfg(feature = "async")]
36    fn credentials_async(&self) -> CredentialsFuture<'_>;
37
38    /// Returns credentials in blocking mode.
39    #[cfg(feature = "blocking")]
40    fn credentials_blocking(&self) -> Result<CredentialsSnapshot>;
41}
42
43/// Shared credentials provider trait object.
44pub type DynCredentialsProvider = Arc<dyn CredentialsProvider>;
45
46/// Trust root selection for credential-provider HTTPS requests (IMDS/STS).
47#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
48pub enum CredentialsTlsRootStore {
49    /// Use the backend default trust roots.
50    ///
51    /// For `rustls`, this maps to WebPKI roots.
52    /// For `native-tls`, this follows backend default behavior.
53    #[default]
54    BackendDefault,
55    /// Force WebPKI roots.
56    WebPki,
57    /// Use platform/system trust verification.
58    System,
59}
60
61impl CredentialsTlsRootStore {
62    #[cfg(all(
63        any(feature = "async", feature = "blocking"),
64        any(feature = "credentials-imds", feature = "credentials-sts")
65    ))]
66    pub(crate) const fn into_reqx(self) -> TlsRootStore {
67        match self {
68            Self::BackendDefault => TlsRootStore::BackendDefault,
69            Self::WebPki => TlsRootStore::WebPki,
70            Self::System => TlsRootStore::System,
71        }
72    }
73}
74
75#[cfg(feature = "credentials-sts")]
76#[derive(Clone, Debug)]
77struct StaticCredentialsProvider {
78    snapshot: CredentialsSnapshot,
79}
80
81#[cfg(feature = "credentials-sts")]
82impl StaticCredentialsProvider {
83    fn new(credentials: Credentials) -> Self {
84        Self {
85            snapshot: CredentialsSnapshot::new(credentials),
86        }
87    }
88}
89
90#[cfg(feature = "credentials-sts")]
91impl CredentialsProvider for StaticCredentialsProvider {
92    #[cfg(feature = "async")]
93    fn credentials_async(&self) -> CredentialsFuture<'_> {
94        let snapshot = self.snapshot.clone();
95        Box::pin(async move { Ok(snapshot) })
96    }
97
98    #[cfg(feature = "blocking")]
99    fn credentials_blocking(&self) -> Result<CredentialsSnapshot> {
100        Ok(self.snapshot.clone())
101    }
102}
103
104#[cfg(feature = "credentials-imds")]
105#[derive(Debug, Clone, Copy)]
106struct ImdsProvider {
107    tls_root_store: CredentialsTlsRootStore,
108}
109
110#[cfg(feature = "credentials-imds")]
111impl CredentialsProvider for ImdsProvider {
112    #[cfg(feature = "async")]
113    fn credentials_async(&self) -> CredentialsFuture<'_> {
114        Box::pin(async move {
115            crate::credentials::imds::load_async(self.tls_root_store.into_reqx()).await
116        })
117    }
118
119    #[cfg(feature = "blocking")]
120    fn credentials_blocking(&self) -> Result<CredentialsSnapshot> {
121        crate::credentials::imds::load_blocking(self.tls_root_store.into_reqx())
122    }
123}
124
125#[cfg(feature = "credentials-sts")]
126#[derive(Debug)]
127struct StsAssumeRoleProvider {
128    region: Region,
129    role_arn: String,
130    role_session_name: String,
131    source: DynCredentialsProvider,
132    tls_root_store: CredentialsTlsRootStore,
133}
134
135#[cfg(feature = "credentials-sts")]
136impl CredentialsProvider for StsAssumeRoleProvider {
137    #[cfg(feature = "async")]
138    fn credentials_async(&self) -> CredentialsFuture<'_> {
139        Box::pin(async move {
140            let source = self.source.credentials_async().await?;
141            crate::credentials::sts::assume_role_async(
142                self.region.clone(),
143                self.role_arn.clone(),
144                self.role_session_name.clone(),
145                source.credentials().clone(),
146                self.tls_root_store.into_reqx(),
147            )
148            .await
149        })
150    }
151
152    #[cfg(feature = "blocking")]
153    fn credentials_blocking(&self) -> Result<CredentialsSnapshot> {
154        let source = self.source.credentials_blocking()?;
155        crate::credentials::sts::assume_role_blocking(
156            self.region.clone(),
157            self.role_arn.clone(),
158            self.role_session_name.clone(),
159            source.credentials().clone(),
160            self.tls_root_store.into_reqx(),
161        )
162    }
163}
164
165#[cfg(feature = "credentials-sts")]
166#[derive(Debug, Clone, Copy)]
167struct StsWebIdentityProvider {
168    tls_root_store: CredentialsTlsRootStore,
169}
170
171#[cfg(feature = "credentials-sts")]
172impl CredentialsProvider for StsWebIdentityProvider {
173    #[cfg(feature = "async")]
174    fn credentials_async(&self) -> CredentialsFuture<'_> {
175        Box::pin(async move {
176            crate::credentials::sts::assume_role_with_web_identity_env_async(
177                self.tls_root_store.into_reqx(),
178            )
179            .await
180        })
181    }
182
183    #[cfg(feature = "blocking")]
184    fn credentials_blocking(&self) -> Result<CredentialsSnapshot> {
185        crate::credentials::sts::assume_role_with_web_identity_env_blocking(
186            self.tls_root_store.into_reqx(),
187        )
188    }
189}
190
191impl Auth {
192    /// Loads static credentials from standard AWS env vars.
193    ///
194    /// Reads `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and optionally `AWS_SESSION_TOKEN`.
195    pub fn from_env() -> Result<Self> {
196        let access_key_id = crate::util::env::required_var("AWS_ACCESS_KEY_ID")?;
197        let secret_access_key = crate::util::env::required_var("AWS_SECRET_ACCESS_KEY")?;
198        let session_token = crate::util::env::optional_var("AWS_SESSION_TOKEN")?;
199
200        let mut creds = Credentials::new(access_key_id, secret_access_key)?;
201        if let Some(token) = session_token {
202            creds = creds.with_session_token(token)?;
203        }
204
205        Ok(Self::Static(creds))
206    }
207
208    /// Uses a dynamic credentials provider.
209    pub fn provider(provider: DynCredentialsProvider) -> Self {
210        Self::Provider(provider)
211    }
212
213    /// Loads credentials from a named profile.
214    #[cfg(feature = "credentials-profile")]
215    pub fn from_profile(profile: impl AsRef<str>) -> Result<Self> {
216        let creds = crate::credentials::profile::load_profile_credentials(profile.as_ref())?;
217        Ok(Self::Static(creds))
218    }
219
220    /// Loads credentials from the profile defined by environment variables.
221    #[cfg(feature = "credentials-profile")]
222    pub fn from_profile_env() -> Result<Self> {
223        Self::from_profile(crate::credentials::profile::profile_from_env()?)
224    }
225
226    /// Loads IMDS credentials and wraps them in a cached provider.
227    #[cfg(all(feature = "credentials-imds", feature = "async"))]
228    pub async fn from_imds() -> Result<Self> {
229        Self::from_imds_with_tls_root_store(CredentialsTlsRootStore::BackendDefault).await
230    }
231
232    /// Loads IMDS credentials and wraps them in a cached provider.
233    #[cfg(all(feature = "credentials-imds", feature = "async"))]
234    pub async fn from_imds_with_tls_root_store(
235        tls_root_store: CredentialsTlsRootStore,
236    ) -> Result<Self> {
237        let initial = crate::credentials::imds::load_async(tls_root_store.into_reqx()).await?;
238        let provider = CachedProvider::new(ImdsProvider { tls_root_store }).with_initial(initial);
239        Ok(Self::Provider(Arc::new(provider)))
240    }
241
242    /// Loads IMDS credentials and wraps them in a cached provider.
243    #[cfg(all(feature = "credentials-imds", feature = "blocking"))]
244    pub fn from_imds_blocking() -> Result<Self> {
245        Self::from_imds_blocking_with_tls_root_store(CredentialsTlsRootStore::BackendDefault)
246    }
247
248    /// Loads IMDS credentials and wraps them in a cached provider.
249    #[cfg(all(feature = "credentials-imds", feature = "blocking"))]
250    pub fn from_imds_blocking_with_tls_root_store(
251        tls_root_store: CredentialsTlsRootStore,
252    ) -> Result<Self> {
253        let initial = crate::credentials::imds::load_blocking(tls_root_store.into_reqx())?;
254        let provider = CachedProvider::new(ImdsProvider { tls_root_store }).with_initial(initial);
255        Ok(Self::Provider(Arc::new(provider)))
256    }
257
258    /// Assumes a role using static source credentials (async).
259    #[cfg(all(feature = "credentials-sts", feature = "async"))]
260    pub async fn assume_role(
261        region: Region,
262        role_arn: impl Into<String>,
263        role_session_name: impl Into<String>,
264        source_credentials: Credentials,
265    ) -> Result<Self> {
266        Self::assume_role_with_tls_root_store(
267            region,
268            role_arn,
269            role_session_name,
270            source_credentials,
271            CredentialsTlsRootStore::BackendDefault,
272        )
273        .await
274    }
275
276    /// Assumes a role using static source credentials and a specific trust root policy (async).
277    #[cfg(all(feature = "credentials-sts", feature = "async"))]
278    pub async fn assume_role_with_tls_root_store(
279        region: Region,
280        role_arn: impl Into<String>,
281        role_session_name: impl Into<String>,
282        source_credentials: Credentials,
283        tls_root_store: CredentialsTlsRootStore,
284    ) -> Result<Self> {
285        Self::assume_role_with_provider_with_tls_root_store(
286            region,
287            role_arn,
288            role_session_name,
289            Arc::new(StaticCredentialsProvider::new(source_credentials)),
290            tls_root_store,
291        )
292        .await
293    }
294
295    /// Assumes a role using static source credentials (blocking).
296    #[cfg(all(feature = "credentials-sts", feature = "blocking"))]
297    pub fn assume_role_blocking(
298        region: Region,
299        role_arn: impl Into<String>,
300        role_session_name: impl Into<String>,
301        source_credentials: Credentials,
302    ) -> Result<Self> {
303        Self::assume_role_blocking_with_tls_root_store(
304            region,
305            role_arn,
306            role_session_name,
307            source_credentials,
308            CredentialsTlsRootStore::BackendDefault,
309        )
310    }
311
312    /// Assumes a role using static source credentials and a specific trust root policy (blocking).
313    #[cfg(all(feature = "credentials-sts", feature = "blocking"))]
314    pub fn assume_role_blocking_with_tls_root_store(
315        region: Region,
316        role_arn: impl Into<String>,
317        role_session_name: impl Into<String>,
318        source_credentials: Credentials,
319        tls_root_store: CredentialsTlsRootStore,
320    ) -> Result<Self> {
321        Self::assume_role_with_provider_blocking_with_tls_root_store(
322            region,
323            role_arn,
324            role_session_name,
325            Arc::new(StaticCredentialsProvider::new(source_credentials)),
326            tls_root_store,
327        )
328    }
329
330    /// Loads web identity credentials from env vars (async).
331    #[cfg(all(feature = "credentials-sts", feature = "async"))]
332    pub async fn from_web_identity_env() -> Result<Self> {
333        Self::from_web_identity_env_with_tls_root_store(CredentialsTlsRootStore::BackendDefault)
334            .await
335    }
336
337    /// Loads web identity credentials from env vars and a specific trust root policy (async).
338    #[cfg(all(feature = "credentials-sts", feature = "async"))]
339    pub async fn from_web_identity_env_with_tls_root_store(
340        tls_root_store: CredentialsTlsRootStore,
341    ) -> Result<Self> {
342        let provider = StsWebIdentityProvider { tls_root_store };
343        let initial = provider.credentials_async().await?;
344        let provider = CachedProvider::new(provider).with_initial(initial);
345        Ok(Self::Provider(Arc::new(provider)))
346    }
347
348    /// Loads web identity credentials from env vars (blocking).
349    #[cfg(all(feature = "credentials-sts", feature = "blocking"))]
350    pub fn from_web_identity_env_blocking() -> Result<Self> {
351        Self::from_web_identity_env_blocking_with_tls_root_store(
352            CredentialsTlsRootStore::BackendDefault,
353        )
354    }
355
356    /// Loads web identity credentials from env vars and a specific trust root policy (blocking).
357    #[cfg(all(feature = "credentials-sts", feature = "blocking"))]
358    pub fn from_web_identity_env_blocking_with_tls_root_store(
359        tls_root_store: CredentialsTlsRootStore,
360    ) -> Result<Self> {
361        let provider = StsWebIdentityProvider { tls_root_store };
362        let initial = provider.credentials_blocking()?;
363        let provider = CachedProvider::new(provider).with_initial(initial);
364        Ok(Self::Provider(Arc::new(provider)))
365    }
366
367    /// Assumes a role using a credentials provider (async).
368    #[cfg(all(feature = "credentials-sts", feature = "async"))]
369    pub async fn assume_role_with_provider(
370        region: Region,
371        role_arn: impl Into<String>,
372        role_session_name: impl Into<String>,
373        source: DynCredentialsProvider,
374    ) -> Result<Self> {
375        Self::assume_role_with_provider_with_tls_root_store(
376            region,
377            role_arn,
378            role_session_name,
379            source,
380            CredentialsTlsRootStore::BackendDefault,
381        )
382        .await
383    }
384
385    /// Assumes a role using a credentials provider and a specific trust root policy (async).
386    #[cfg(all(feature = "credentials-sts", feature = "async"))]
387    pub async fn assume_role_with_provider_with_tls_root_store(
388        region: Region,
389        role_arn: impl Into<String>,
390        role_session_name: impl Into<String>,
391        source: DynCredentialsProvider,
392        tls_root_store: CredentialsTlsRootStore,
393    ) -> Result<Self> {
394        let provider = StsAssumeRoleProvider {
395            region,
396            role_arn: role_arn.into(),
397            role_session_name: role_session_name.into(),
398            source,
399            tls_root_store,
400        };
401        let initial = provider.credentials_async().await?;
402        let provider = CachedProvider::new(provider).with_initial(initial);
403        Ok(Self::Provider(Arc::new(provider)))
404    }
405
406    /// Assumes a role using a credentials provider (blocking).
407    #[cfg(all(feature = "credentials-sts", feature = "blocking"))]
408    pub fn assume_role_with_provider_blocking(
409        region: Region,
410        role_arn: impl Into<String>,
411        role_session_name: impl Into<String>,
412        source: DynCredentialsProvider,
413    ) -> Result<Self> {
414        Self::assume_role_with_provider_blocking_with_tls_root_store(
415            region,
416            role_arn,
417            role_session_name,
418            source,
419            CredentialsTlsRootStore::BackendDefault,
420        )
421    }
422
423    /// Assumes a role using a credentials provider and a specific trust root policy (blocking).
424    #[cfg(all(feature = "credentials-sts", feature = "blocking"))]
425    pub fn assume_role_with_provider_blocking_with_tls_root_store(
426        region: Region,
427        role_arn: impl Into<String>,
428        role_session_name: impl Into<String>,
429        source: DynCredentialsProvider,
430        tls_root_store: CredentialsTlsRootStore,
431    ) -> Result<Self> {
432        let provider = StsAssumeRoleProvider {
433            region,
434            role_arn: role_arn.into(),
435            role_session_name: role_session_name.into(),
436            source,
437            tls_root_store,
438        };
439        let initial = provider.credentials_blocking()?;
440        let provider = CachedProvider::new(provider).with_initial(initial);
441        Ok(Self::Provider(Arc::new(provider)))
442    }
443
444    #[cfg(feature = "async")]
445    pub(crate) fn static_credentials(&self) -> Option<&Credentials> {
446        match self {
447            Self::Static(creds) => Some(creds),
448            Self::Anonymous | Self::Provider(_) => None,
449        }
450    }
451
452    #[cfg(feature = "async")]
453    pub(crate) async fn credentials_snapshot_async(&self) -> Result<Option<CredentialsSnapshot>> {
454        match self {
455            Self::Anonymous => Ok(None),
456            Self::Static(creds) => Ok(Some(CredentialsSnapshot::new(creds.clone()))),
457            Self::Provider(provider) => provider.credentials_async().await.map(Some),
458        }
459    }
460
461    #[cfg(feature = "blocking")]
462    pub(crate) fn credentials_snapshot_blocking(&self) -> Result<Option<CredentialsSnapshot>> {
463        match self {
464            Self::Anonymous => Ok(None),
465            Self::Static(creds) => Ok(Some(CredentialsSnapshot::new(creds.clone()))),
466            Self::Provider(provider) => provider.credentials_blocking().map(Some),
467        }
468    }
469}