alien_bindings/providers/service_account/
azure_managed_identity.rs1use crate::error::{binding_env_var, ErrorData, Result};
2use crate::traits::{
3 AzureServiceAccountInfo, Binding, ImpersonationRequest, ServiceAccount, ServiceAccountInfo,
4};
5use alien_azure_clients::AzureClientConfig;
6use alien_core::bindings::AzureServiceAccountBinding;
7use alien_core::{AzureClientConfig as CoreAzureClientConfig, AzureCredentials, ClientConfig};
8use alien_error::Context;
9use async_trait::async_trait;
10use std::collections::HashMap;
11
12#[derive(Debug)]
19pub struct AzureManagedIdentityServiceAccount {
20 config: AzureClientConfig,
21 binding: AzureServiceAccountBinding,
22}
23
24impl AzureManagedIdentityServiceAccount {
25 pub fn new(config: AzureClientConfig, binding: AzureServiceAccountBinding) -> Self {
26 Self { config, binding }
27 }
28
29 fn get_client_id(&self) -> Result<String> {
31 self.binding
32 .client_id
33 .clone()
34 .into_value("service-account", "client_id")
35 .context(ErrorData::BindingConfigInvalid {
36 env_var: binding_env_var("service-account"),
37 binding_name: "service-account".to_string(),
38 reason: "Failed to resolve client_id from binding".to_string(),
39 })
40 }
41
42 fn get_resource_id(&self) -> Result<String> {
44 self.binding
45 .resource_id
46 .clone()
47 .into_value("service-account", "resource_id")
48 .context(ErrorData::BindingConfigInvalid {
49 env_var: binding_env_var("service-account"),
50 binding_name: "service-account".to_string(),
51 reason: "Failed to resolve resource_id from binding".to_string(),
52 })
53 }
54
55 fn get_principal_id(&self) -> Result<String> {
57 self.binding
58 .principal_id
59 .clone()
60 .into_value("service-account", "principal_id")
61 .context(ErrorData::BindingConfigInvalid {
62 env_var: binding_env_var("service-account"),
63 binding_name: "service-account".to_string(),
64 reason: "Failed to resolve principal_id from binding".to_string(),
65 })
66 }
67}
68
69impl Binding for AzureManagedIdentityServiceAccount {}
70
71#[async_trait]
72impl ServiceAccount for AzureManagedIdentityServiceAccount {
73 async fn get_info(&self) -> Result<ServiceAccountInfo> {
74 let client_id = self.get_client_id()?;
75 let resource_id = self.get_resource_id()?;
76 let principal_id = self.get_principal_id()?;
77
78 Ok(ServiceAccountInfo::Azure(AzureServiceAccountInfo {
79 client_id,
80 resource_id,
81 principal_id,
82 }))
83 }
84
85 async fn impersonate(&self, _request: ImpersonationRequest) -> Result<ClientConfig> {
86 let client_id = self.get_client_id()?;
87
88 let env_vars = std::env::vars().collect::<HashMap<_, _>>();
89 let tenant_id = env_vars
90 .get("AZURE_TENANT_ID")
91 .cloned()
92 .unwrap_or_else(|| self.config.tenant_id.clone());
93
94 let credentials = if let Some(federated_token_file) =
95 env_vars.get("AZURE_FEDERATED_TOKEN_FILE")
96 {
97 AzureCredentials::WorkloadIdentity {
98 client_id: client_id.clone(),
99 tenant_id: tenant_id.clone(),
100 federated_token_file: federated_token_file.clone(),
101 authority_host: env_vars
102 .get("AZURE_AUTHORITY_HOST")
103 .cloned()
104 .unwrap_or_else(|| "https://login.microsoftonline.com/".to_string()),
105 }
106 } else if let (Some(identity_endpoint), Some(identity_header)) = (
107 env_vars.get("IDENTITY_ENDPOINT"),
108 env_vars.get("IDENTITY_HEADER"),
109 ) {
110 AzureCredentials::ManagedIdentity {
111 client_id: client_id.clone(),
112 identity_endpoint: identity_endpoint.clone(),
113 identity_header: identity_header.clone(),
114 }
115 } else {
116 return Err(alien_error::AlienError::new(ErrorData::Other {
117 message: "Azure managed identity impersonation requires workload identity (AZURE_FEDERATED_TOKEN_FILE) or managed identity (IDENTITY_ENDPOINT and IDENTITY_HEADER) credentials".to_string(),
118 }));
119 };
120
121 let impersonated_config = CoreAzureClientConfig {
122 subscription_id: self.config.subscription_id.clone(),
123 tenant_id,
124 region: self.config.region.clone(),
125 credentials,
126 service_overrides: self.config.service_overrides.clone(),
127 };
128
129 Ok(ClientConfig::Azure(Box::new(impersonated_config)))
130 }
131
132 fn as_any(&self) -> &dyn std::any::Any {
133 self
134 }
135}