Skip to main content

alien_bindings/providers/service_account/
aws_iam.rs

1use crate::error::{binding_env_var, ErrorData, Result};
2use crate::traits::{
3    AwsServiceAccountInfo, Binding, ImpersonationRequest, ServiceAccount, ServiceAccountInfo,
4};
5use alien_aws_clients::{
6    sts::{AssumeRoleRequest, StsApi, StsClient},
7    AwsClientConfig,
8};
9use alien_core::bindings::AwsServiceAccountBinding;
10use alien_core::{AwsClientConfig as CoreAwsClientConfig, AwsCredentials, ClientConfig};
11use alien_error::Context;
12use async_trait::async_trait;
13use reqwest::Client;
14
15/// AWS IAM Role service account binding implementation
16#[derive(Debug)]
17pub struct AwsIamServiceAccount {
18    client: StsClient,
19    config: AwsClientConfig,
20    binding: AwsServiceAccountBinding,
21}
22
23impl AwsIamServiceAccount {
24    pub fn new(
25        http_client: Client,
26        config: AwsClientConfig,
27        binding: AwsServiceAccountBinding,
28    ) -> Self {
29        let sts_client = StsClient::new(http_client, config.clone());
30        Self {
31            client: sts_client,
32            config,
33            binding,
34        }
35    }
36
37    /// Get the role ARN from the binding, resolving template expressions if needed
38    fn get_role_arn(&self) -> Result<String> {
39        self.binding
40            .role_arn
41            .clone()
42            .into_value("service-account", "role_arn")
43            .context(ErrorData::BindingConfigInvalid {
44                env_var: binding_env_var("service-account"),
45                binding_name: "service-account".to_string(),
46                reason: "Failed to resolve role_arn from binding".to_string(),
47            })
48    }
49
50    /// Get the role name from the binding, resolving template expressions if needed
51    fn get_role_name(&self) -> Result<String> {
52        self.binding
53            .role_name
54            .clone()
55            .into_value("service-account", "role_name")
56            .context(ErrorData::BindingConfigInvalid {
57                env_var: binding_env_var("service-account"),
58                binding_name: "service-account".to_string(),
59                reason: "Failed to resolve role_name from binding".to_string(),
60            })
61    }
62}
63
64impl Binding for AwsIamServiceAccount {}
65
66#[async_trait]
67impl ServiceAccount for AwsIamServiceAccount {
68    async fn get_info(&self) -> Result<ServiceAccountInfo> {
69        let role_name = self.get_role_name()?;
70        let role_arn = self.get_role_arn()?;
71
72        Ok(ServiceAccountInfo::Aws(AwsServiceAccountInfo {
73            role_name,
74            role_arn,
75        }))
76    }
77
78    async fn impersonate(&self, request: ImpersonationRequest) -> Result<ClientConfig> {
79        let role_arn = self.get_role_arn()?;
80        let session_name = request
81            .session_name
82            .unwrap_or_else(|| "alien-impersonation".to_string());
83        let duration = request.duration_seconds.unwrap_or(3600);
84
85        let assume_role_request = AssumeRoleRequest::builder()
86            .role_arn(role_arn.clone())
87            .role_session_name(session_name)
88            .duration_seconds(duration)
89            .build();
90
91        let response =
92            self.client
93                .assume_role(assume_role_request)
94                .await
95                .context(ErrorData::Other {
96                    message: format!("Failed to assume IAM role '{}'", role_arn),
97                })?;
98
99        let credentials = response.assume_role_result.credentials;
100
101        // Create new AWS client config with the temporary credentials
102        let impersonated_config = CoreAwsClientConfig {
103            account_id: self.config.account_id.clone(),
104            region: self.config.region.clone(),
105            credentials: AwsCredentials::SessionCredentials {
106                access_key_id: credentials.access_key_id,
107                secret_access_key: credentials.secret_access_key,
108                session_token: credentials.session_token,
109                expires_at: credentials.expiration,
110            },
111            service_overrides: self.config.service_overrides.clone(),
112        };
113
114        Ok(ClientConfig::Aws(Box::new(impersonated_config)))
115    }
116
117    fn as_any(&self) -> &dyn std::any::Any {
118        self
119    }
120}