azure_identity/token_credentials/
specific_azure_credential.rs

1use super::options;
2#[cfg(not(target_arch = "wasm32"))]
3use crate::AzureCliCredential;
4#[cfg(feature = "client_certificate")]
5use crate::ClientCertificateCredential;
6use crate::{
7    AppServiceManagedIdentityCredential, ClientSecretCredential, EnvironmentCredential,
8    TokenCredentialOptions, VirtualMachineManagedIdentityCredential, WorkloadIdentityCredential,
9};
10use azure_core::{
11    auth::{AccessToken, TokenCredential},
12    error::{ErrorKind, ResultExt},
13    Error,
14};
15use std::sync::Arc;
16
17pub const AZURE_CREDENTIAL_KIND: &str = "AZURE_CREDENTIAL_KIND";
18
19pub mod azure_credential_kinds {
20    pub const ENVIRONMENT: &str = "environment";
21    #[cfg(not(target_arch = "wasm32"))]
22    pub const AZURE_CLI: &str = "azurecli";
23    pub const VIRTUAL_MACHINE: &str = "virtualmachine";
24    pub const APP_SERVICE: &str = "appservice";
25    pub const CLIENT_SECRET: &str = "clientsecret";
26    pub const WORKLOAD_IDENTITY: &str = "workloadidentity";
27    #[cfg(feature = "client_certificate")]
28    pub const CLIENT_CERTIFICATE: &str = "clientcertificate";
29}
30
31/// Creates a `DefaultAzureCredential` by default with default options.
32/// If `AZURE_CREDENTIAL_KIND` environment variable is set, it creates a `SpecificAzureCredential` with default options.
33pub fn create_credential() -> azure_core::Result<Arc<dyn TokenCredential>> {
34    create_credential_with_options(options::TokenCredentialOptions::default())
35}
36
37fn create_credential_with_options(
38    options: TokenCredentialOptions,
39) -> azure_core::Result<Arc<dyn TokenCredential>> {
40    let env = options.env();
41    match env.var(AZURE_CREDENTIAL_KIND) {
42        Ok(_) => SpecificAzureCredential::create(options)
43            .map(|cred| Arc::new(cred) as Arc<dyn TokenCredential>),
44        Err(_) => crate::DefaultAzureCredentialBuilder::default()
45            .with_options(options)
46            .build()
47            .map(|cred| Arc::new(cred) as Arc<dyn TokenCredential>),
48    }
49}
50
51/// Creates a new `SpecificAzureCredential` with the default options.
52pub fn create_specific_credential() -> azure_core::Result<Arc<dyn TokenCredential>> {
53    Ok(Arc::new(SpecificAzureCredential::create(
54        TokenCredentialOptions::default(),
55    )?))
56}
57
58#[derive(Debug)]
59pub(crate) enum SpecificAzureCredentialKind {
60    Environment(EnvironmentCredential),
61    #[cfg(not(target_arch = "wasm32"))]
62    AzureCli(AzureCliCredential),
63    VirtualMachine(VirtualMachineManagedIdentityCredential),
64    AppService(AppServiceManagedIdentityCredential),
65    ClientSecret(ClientSecretCredential),
66    WorkloadIdentity(WorkloadIdentityCredential),
67    #[cfg(feature = "client_certificate")]
68    ClientCertificate(ClientCertificateCredential),
69}
70
71#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
72#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
73impl TokenCredential for SpecificAzureCredentialKind {
74    async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
75        match self {
76            SpecificAzureCredentialKind::Environment(credential) => {
77                credential.get_token(scopes).await
78            }
79            #[cfg(not(target_arch = "wasm32"))]
80            SpecificAzureCredentialKind::AzureCli(credential) => credential.get_token(scopes).await,
81            SpecificAzureCredentialKind::VirtualMachine(credential) => {
82                credential.get_token(scopes).await
83            }
84            SpecificAzureCredentialKind::AppService(credential) => {
85                credential.get_token(scopes).await
86            }
87            SpecificAzureCredentialKind::ClientSecret(credential) => {
88                credential.get_token(scopes).await
89            }
90            SpecificAzureCredentialKind::WorkloadIdentity(credential) => {
91                credential.get_token(scopes).await
92            }
93            #[cfg(feature = "client_certificate")]
94            SpecificAzureCredentialKind::ClientCertificate(credential) => {
95                credential.get_token(scopes).await
96            }
97        }
98    }
99
100    async fn clear_cache(&self) -> azure_core::Result<()> {
101        match self {
102            SpecificAzureCredentialKind::Environment(credential) => credential.clear_cache().await,
103            #[cfg(not(target_arch = "wasm32"))]
104            SpecificAzureCredentialKind::AzureCli(credential) => credential.clear_cache().await,
105            SpecificAzureCredentialKind::VirtualMachine(credential) => {
106                credential.clear_cache().await
107            }
108            SpecificAzureCredentialKind::AppService(credential) => credential.clear_cache().await,
109            SpecificAzureCredentialKind::ClientSecret(credential) => credential.clear_cache().await,
110            SpecificAzureCredentialKind::WorkloadIdentity(credential) => {
111                credential.clear_cache().await
112            }
113            #[cfg(feature = "client_certificate")]
114            SpecificAzureCredentialKind::ClientCertificate(credential) => {
115                credential.clear_cache().await
116            }
117        }
118    }
119}
120
121#[derive(Debug)]
122pub struct SpecificAzureCredential {
123    source: SpecificAzureCredentialKind,
124}
125
126impl SpecificAzureCredential {
127    pub fn create(options: TokenCredentialOptions) -> azure_core::Result<SpecificAzureCredential> {
128        let env = options.env();
129        let credential_type = env.var(AZURE_CREDENTIAL_KIND)?;
130        let source: SpecificAzureCredentialKind =
131            // case insensitive and allow spaces
132            match credential_type.replace(' ', "").to_lowercase().as_str() {
133                azure_credential_kinds::ENVIRONMENT => EnvironmentCredential::create(options)
134                    .map(SpecificAzureCredentialKind::Environment)
135                    .with_context(ErrorKind::Credential, || {
136                        format!(
137                            "unable to create AZURE_CREDENTIAL_KIND of {}",
138                            azure_credential_kinds::ENVIRONMENT
139                        )
140                    })?,
141                azure_credential_kinds::APP_SERVICE => {
142                    AppServiceManagedIdentityCredential::create(options)
143                        .map(SpecificAzureCredentialKind::AppService)
144                        .with_context(ErrorKind::Credential, || {
145                            format!(
146                                "unable to create AZURE_CREDENTIAL_KIND of {}",
147                                azure_credential_kinds::APP_SERVICE
148                            )
149                        })?
150                }
151                azure_credential_kinds::VIRTUAL_MACHINE => {
152                    SpecificAzureCredentialKind::VirtualMachine(
153                        VirtualMachineManagedIdentityCredential::new(options),
154                    )
155                }
156                #[cfg(not(target_arch = "wasm32"))]
157                azure_credential_kinds::AZURE_CLI => AzureCliCredential::create()
158                    .map(SpecificAzureCredentialKind::AzureCli)
159                    .with_context(ErrorKind::Credential, || {
160                        format!(
161                            "unable to create AZURE_CREDENTIAL_KIND of {}",
162                            azure_credential_kinds::AZURE_CLI
163                        )
164                    })?,
165                azure_credential_kinds::CLIENT_SECRET => ClientSecretCredential::create(options)
166                    .map(SpecificAzureCredentialKind::ClientSecret)?,
167                azure_credential_kinds::WORKLOAD_IDENTITY => {
168                    WorkloadIdentityCredential::create(options)
169                        .map(SpecificAzureCredentialKind::WorkloadIdentity)
170                        .with_context(ErrorKind::Credential, || {
171                            format!(
172                                "unable to create AZURE_CREDENTIAL_KIND of {}",
173                                azure_credential_kinds::WORKLOAD_IDENTITY
174                            )
175                        })?
176                }
177                #[cfg(feature = "client_certificate")]
178                azure_credential_kinds::CLIENT_CERTIFICATE => {
179                    ClientCertificateCredential::create(options)
180                        .map(SpecificAzureCredentialKind::ClientCertificate)
181                        .with_context(ErrorKind::Credential, || {
182                            format!(
183                                "unable to create AZURE_CREDENTIAL_KIND of {}",
184                                azure_credential_kinds::CLIENT_CERTIFICATE
185                            )
186                        })?
187                }
188                _ => {
189                    return Err(Error::with_message(ErrorKind::Credential, || {
190                        format!("unknown AZURE_CREDENTIAL_KIND of {}", credential_type)
191                    }))
192                }
193            };
194        Ok(Self { source })
195    }
196
197    #[cfg(test)]
198    pub(crate) fn source(&self) -> &SpecificAzureCredentialKind {
199        &self.source
200    }
201}
202
203#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
204#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
205impl TokenCredential for SpecificAzureCredential {
206    async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
207        self.source.get_token(scopes).await
208    }
209
210    async fn clear_cache(&self) -> azure_core::Result<()> {
211        self.source.clear_cache().await
212    }
213}
214
215#[cfg(test)]
216pub fn test_options(env_vars: &[(&str, &str)]) -> TokenCredentialOptions {
217    let env = crate::env::Env::from(env_vars);
218    let http_client = azure_core::new_http_client();
219    TokenCredentialOptions::new(env, http_client)
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use crate::EnvironmentCredentialKind;
226
227    /// test AZURE_CREDENTIAL_KIND of "environment"
228    #[test]
229    fn test_environment() -> azure_core::Result<()> {
230        let credential = SpecificAzureCredential::create(test_options(
231            &[
232                ("AZURE_CREDENTIAL_KIND", "environment"),
233                ("AZURE_TENANT_ID", "1"),
234                ("AZURE_CLIENT_ID", "2"),
235                ("AZURE_CLIENT_SECRET", "3"),
236            ][..],
237        ))?;
238        match credential.source() {
239            SpecificAzureCredentialKind::Environment(credential) => match credential.source() {
240                EnvironmentCredentialKind::ClientSecret(_) => {}
241                _ => panic!("expect client secret credential"),
242            },
243            _ => panic!("expected environment credential"),
244        }
245        Ok(())
246    }
247
248    /// test AZURE_CREDENTIAL_KIND of "azurecli"
249    #[test]
250    #[cfg(not(target_arch = "wasm32"))]
251    fn test_azure_cli() -> azure_core::Result<()> {
252        let credential = SpecificAzureCredential::create(test_options(
253            &[("AZURE_CREDENTIAL_KIND", "azurecli")][..],
254        ))?;
255        match credential.source() {
256            SpecificAzureCredentialKind::AzureCli(_) => {}
257            _ => panic!("expected azure cli credential"),
258        }
259        Ok(())
260    }
261
262    /// test naming "Azure CLI"
263    #[test]
264    #[cfg(not(target_arch = "wasm32"))]
265    fn test_azure_cli_naming() -> azure_core::Result<()> {
266        let credential = SpecificAzureCredential::create(test_options(
267            &[("AZURE_CREDENTIAL_KIND", "Azure CLI")][..],
268        ))?;
269        match credential.source() {
270            SpecificAzureCredentialKind::AzureCli(_) => {}
271            _ => panic!("expected azure cli credential"),
272        }
273        Ok(())
274    }
275
276    /// test AZURE_CREDENTIAL_KIND of "virtualmachine"
277    #[test]
278    fn test_virtual_machine() -> azure_core::Result<()> {
279        let credential = SpecificAzureCredential::create(test_options(
280            &[("AZURE_CREDENTIAL_KIND", "virtualmachine")][..],
281        ))?;
282        match credential.source() {
283            SpecificAzureCredentialKind::VirtualMachine(_) => {}
284            _ => panic!("expected virtual machine credential"),
285        }
286        Ok(())
287    }
288
289    /// test AZURE_CREDENTIAL_KIND of "appservice"
290    #[test]
291    fn test_app_service() -> azure_core::Result<()> {
292        let credential = SpecificAzureCredential::create(test_options(
293            &[
294                ("AZURE_CREDENTIAL_KIND", "appservice"),
295                ("IDENTITY_ENDPOINT", "https://identityendpoint/token"),
296            ][..],
297        ))?;
298        match credential.source() {
299            SpecificAzureCredentialKind::AppService(_) => {}
300            _ => panic!("expected app service credential"),
301        }
302        Ok(())
303    }
304
305    /// test AZURE_CREDENTIAL_KIND of "clientsecret"
306    #[test]
307    fn test_client_secret() -> azure_core::Result<()> {
308        let credential = SpecificAzureCredential::create(test_options(
309            &[
310                ("AZURE_CREDENTIAL_KIND", "clientsecret"),
311                ("AZURE_TENANT_ID", "1"),
312                ("AZURE_CLIENT_ID", "2"),
313                ("AZURE_CLIENT_SECRET", "3"),
314            ][..],
315        ))?;
316        match credential.source() {
317            SpecificAzureCredentialKind::ClientSecret(_) => {}
318            _ => panic!("expected client secret credential"),
319        }
320        Ok(())
321    }
322
323    /// test AZURE_CREDENTIAL_KIND of "workloadidentity"
324    #[test]
325    fn test_workload_identity() -> azure_core::Result<()> {
326        let credential = SpecificAzureCredential::create(test_options(
327            &[
328                ("AZURE_CREDENTIAL_KIND", "workloadidentity"),
329                ("AZURE_TENANT_ID", "1"),
330                ("AZURE_CLIENT_ID", "2"),
331                ("AZURE_FEDERATED_TOKEN", "3"),
332            ][..],
333        ))?;
334        match credential.source() {
335            SpecificAzureCredentialKind::WorkloadIdentity(_) => {}
336            _ => panic!("expected workload identity credential"),
337        }
338        Ok(())
339    }
340}