Skip to main content

alien_bindings/providers/service_account/
aws_iam.rs

1use crate::error::{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                binding_name: "service-account".to_string(),
45                reason: "Failed to resolve role_arn from binding".to_string(),
46            })
47    }
48
49    /// Get the role name from the binding, resolving template expressions if needed
50    fn get_role_name(&self) -> Result<String> {
51        self.binding
52            .role_name
53            .clone()
54            .into_value("service-account", "role_name")
55            .context(ErrorData::BindingConfigInvalid {
56                binding_name: "service-account".to_string(),
57                reason: "Failed to resolve role_name from binding".to_string(),
58            })
59    }
60}
61
62impl Binding for AwsIamServiceAccount {}
63
64#[async_trait]
65impl ServiceAccount for AwsIamServiceAccount {
66    async fn get_info(&self) -> Result<ServiceAccountInfo> {
67        let role_name = self.get_role_name()?;
68        let role_arn = self.get_role_arn()?;
69
70        Ok(ServiceAccountInfo::Aws(AwsServiceAccountInfo {
71            role_name,
72            role_arn,
73        }))
74    }
75
76    async fn impersonate(&self, request: ImpersonationRequest) -> Result<ClientConfig> {
77        let role_arn = self.get_role_arn()?;
78        let session_name = request
79            .session_name
80            .unwrap_or_else(|| "alien-impersonation".to_string());
81        let duration = request.duration_seconds.unwrap_or(3600);
82
83        let assume_role_request = AssumeRoleRequest::builder()
84            .role_arn(role_arn.clone())
85            .role_session_name(session_name)
86            .duration_seconds(duration)
87            .build();
88
89        let response =
90            self.client
91                .assume_role(assume_role_request)
92                .await
93                .context(ErrorData::Other {
94                    message: format!("Failed to assume IAM role '{}'", role_arn),
95                })?;
96
97        let credentials = response.assume_role_result.credentials;
98
99        // Create new AWS client config with the temporary credentials
100        let impersonated_config = CoreAwsClientConfig {
101            account_id: self.config.account_id.clone(),
102            region: self.config.region.clone(),
103            credentials: AwsCredentials::AccessKeys {
104                access_key_id: credentials.access_key_id,
105                secret_access_key: credentials.secret_access_key,
106                session_token: Some(credentials.session_token),
107            },
108            service_overrides: self.config.service_overrides.clone(),
109        };
110
111        Ok(ClientConfig::Aws(Box::new(impersonated_config)))
112    }
113
114    fn as_any(&self) -> &dyn std::any::Any {
115        self
116    }
117}