Skip to main content

olai_http/aws/
mod.rs

1use std::sync::Arc;
2
3use futures::future::BoxFuture;
4
5use crate::service::make_service;
6use crate::token::TemporaryToken;
7use crate::{ClientOptions, RequestSigner, Result, RetryConfig, TokenProvider};
8
9use self::credential::{AssumeRoleProvider, AwsAuthorizer, CredentialExt};
10use crate::CredentialProvider;
11
12mod builder;
13pub(crate) mod credential;
14
15pub use builder::*;
16pub use credential::AwsCredential;
17
18pub type AwsCredentialProvider = Arc<dyn CredentialProvider<Credential = AwsCredential>>;
19
20#[derive(Debug, Clone)]
21pub struct AmazonConfig {
22    pub region: String,
23    pub credentials: AwsCredentialProvider,
24    pub retry_config: RetryConfig,
25    pub client_options: ClientOptions,
26    pub skip_signature: bool,
27}
28
29impl AmazonConfig {
30    pub(crate) async fn get_credential(&self) -> Result<Option<Arc<AwsCredential>>> {
31        Ok(match self.skip_signature {
32            false => Some(self.credentials.get_credential().await?),
33            true => None,
34        })
35    }
36}
37
38impl RequestSigner for AmazonConfig {
39    fn sign<'a>(
40        &'a self,
41        req: reqwest::RequestBuilder,
42    ) -> BoxFuture<'a, Result<reqwest::RequestBuilder>> {
43        Box::pin(async move {
44            if let Some(cred) = self.get_credential().await? {
45                let authorizer = AwsAuthorizer::new(&cred, "execute-api", &self.region);
46                Ok(req.with_aws_sigv4(Some(authorizer), None))
47            } else {
48                Ok(req)
49            }
50        })
51    }
52}
53
54/// Assume an AWS IAM role using explicit base credentials.
55///
56/// Like [`assume_role`] but accepts an explicit `base_credentials` provider
57/// instead of falling back to environment-based credential discovery.
58/// Use this when the base identity (the caller's access key + secret) is
59/// stored in a credential registry rather than in the server environment.
60pub async fn assume_role_with_base(
61    role_arn: &str,
62    region: &str,
63    sts_endpoint: Option<&str>,
64    policy: Option<String>,
65    base_credentials: AwsCredentialProvider,
66) -> Result<TemporaryToken<Arc<AwsCredential>>> {
67    let endpoint = sts_endpoint
68        .map(|s| s.to_owned())
69        .unwrap_or_else(|| format!("https://sts.{region}.amazonaws.com"));
70
71    let provider = AssumeRoleProvider {
72        role_arn: role_arn.to_owned(),
73        session_name: "TrestleVending".to_owned(),
74        endpoint,
75        base_credentials,
76        region: region.to_owned(),
77        policy,
78    };
79
80    let client = ClientOptions::default().client()?;
81    let service = make_service(client.clone(), None);
82    provider
83        .fetch_token(&client, &service, &RetryConfig::default())
84        .await
85}
86
87/// Assume an AWS IAM role using ambient server credentials.
88///
89/// Uses the environment credential chain (`AWS_*` env vars, instance profile,
90/// ECS task role, WebIdentity, etc.) as the base identity for the STS
91/// `AssumeRole` call. Prefer [`assume_role_with_base`] when the base
92/// credentials are stored in a credential registry.
93pub async fn assume_role(
94    role_arn: &str,
95    region: &str,
96    sts_endpoint: Option<&str>,
97    policy: Option<String>,
98) -> Result<TemporaryToken<Arc<AwsCredential>>> {
99    let base_credentials = AmazonBuilder::from_env()
100        .with_region(region)
101        .build(None)?
102        .credentials;
103    assume_role_with_base(role_arn, region, sts_endpoint, policy, base_credentials).await
104}