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