Skip to main content

alien_bindings/providers/service_account/
gcp_service_account.rs

1use crate::error::{ErrorData, Result};
2use crate::traits::{
3    Binding, GcpServiceAccountInfo, ImpersonationRequest, ServiceAccount, ServiceAccountInfo,
4};
5use alien_core::bindings::GcpServiceAccountBinding;
6use alien_core::{ClientConfig, GcpClientConfig as CoreGcpClientConfig, GcpCredentials};
7use alien_error::Context;
8use alien_gcp_clients::{
9    iam::{GenerateAccessTokenRequest, IamApi, IamClient},
10    GcpClientConfig,
11};
12use async_trait::async_trait;
13use reqwest::Client;
14
15/// GCP Service Account binding implementation
16#[derive(Debug)]
17pub struct GcpServiceAccount {
18    client: IamClient,
19    config: GcpClientConfig,
20    binding: GcpServiceAccountBinding,
21}
22
23impl GcpServiceAccount {
24    pub fn new(
25        http_client: Client,
26        config: GcpClientConfig,
27        binding: GcpServiceAccountBinding,
28    ) -> Self {
29        let iam_client = IamClient::new(http_client, config.clone());
30        Self {
31            client: iam_client,
32            config,
33            binding,
34        }
35    }
36
37    /// Get the service account email from the binding, resolving template expressions if needed
38    fn get_email(&self) -> Result<String> {
39        self.binding
40            .email
41            .clone()
42            .into_value("service-account", "email")
43            .context(ErrorData::BindingConfigInvalid {
44                binding_name: "service-account".to_string(),
45                reason: "Failed to resolve email from binding".to_string(),
46            })
47    }
48
49    /// Get the unique ID from the binding, resolving template expressions if needed
50    fn get_unique_id(&self) -> Result<String> {
51        self.binding
52            .unique_id
53            .clone()
54            .into_value("service-account", "unique_id")
55            .context(ErrorData::BindingConfigInvalid {
56                binding_name: "service-account".to_string(),
57                reason: "Failed to resolve unique_id from binding".to_string(),
58            })
59    }
60}
61
62impl Binding for GcpServiceAccount {}
63
64#[async_trait]
65impl ServiceAccount for GcpServiceAccount {
66    async fn get_info(&self) -> Result<ServiceAccountInfo> {
67        let email = self.get_email()?;
68        let unique_id = self.get_unique_id()?;
69
70        Ok(ServiceAccountInfo::Gcp(GcpServiceAccountInfo {
71            email,
72            unique_id,
73        }))
74    }
75
76    async fn impersonate(&self, request: ImpersonationRequest) -> Result<ClientConfig> {
77        let email = self.get_email()?;
78        let scopes = request
79            .scopes
80            .unwrap_or_else(|| vec!["https://www.googleapis.com/auth/cloud-platform".to_string()]);
81
82        let token_request = GenerateAccessTokenRequest::builder().scope(scopes).build();
83
84        let response = self
85            .client
86            .generate_access_token(email.clone(), token_request)
87            .await
88            .context(ErrorData::Other {
89                message: format!(
90                    "Failed to generate access token for service account '{}'",
91                    email
92                ),
93            })?;
94
95        // Create new GCP client config with the impersonated access token
96        let impersonated_config = CoreGcpClientConfig {
97            project_id: self.config.project_id.clone(),
98            region: self.config.region.clone(),
99            credentials: GcpCredentials::AccessToken {
100                token: response.access_token,
101            },
102            service_overrides: self.config.service_overrides.clone(),
103        };
104
105        Ok(ClientConfig::Gcp(Box::new(impersonated_config)))
106    }
107
108    fn as_any(&self) -> &dyn std::any::Any {
109        self
110    }
111}