azure_identity/token_credentials/
default_credentials.rs1#[cfg(not(target_arch = "wasm32"))]
2use crate::AzureCliCredential;
3use crate::{
4 timeout::TimeoutExt, token_credentials::cache::TokenCache, AppServiceManagedIdentityCredential,
5 EnvironmentCredential, TokenCredentialOptions, VirtualMachineManagedIdentityCredential,
6};
7use azure_core::{
8 auth::{AccessToken, TokenCredential},
9 error::{Error, ErrorKind, ResultExt},
10};
11use std::{sync::Arc, time::Duration};
12
13pub struct DefaultAzureCredentialBuilder {
15 options: TokenCredentialOptions,
16 include_environment_credential: bool,
17 include_app_service_managed_identity_credential: bool,
18 include_virtual_machine_managed_identity_credential: bool,
19 #[cfg(not(target_arch = "wasm32"))]
20 include_azure_cli_credential: bool,
21}
22
23impl Default for DefaultAzureCredentialBuilder {
24 fn default() -> Self {
25 Self {
26 options: TokenCredentialOptions::default(),
27 include_environment_credential: true,
28 include_app_service_managed_identity_credential: true,
29 include_virtual_machine_managed_identity_credential: true,
30 #[cfg(not(target_arch = "wasm32"))]
31 include_azure_cli_credential: true,
32 }
33 }
34}
35
36impl DefaultAzureCredentialBuilder {
37 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn with_options(&mut self, options: impl Into<TokenCredentialOptions>) -> &mut Self {
43 self.options = options.into();
44 self
45 }
46
47 pub fn exclude_environment_credential(&mut self) -> &mut Self {
49 self.include_environment_credential = false;
50 self
51 }
52
53 pub fn exclude_managed_identity_credential(&mut self) -> &mut Self {
55 self.include_app_service_managed_identity_credential = false;
56 self.include_virtual_machine_managed_identity_credential = false;
57 self
58 }
59
60 pub fn exclude_virtual_machine_managed_identity_credential(&mut self) -> &mut Self {
62 self.include_virtual_machine_managed_identity_credential = false;
63 self
64 }
65
66 pub fn include_virtual_machine_managed_identity_credential(&mut self) -> &mut Self {
68 self.include_virtual_machine_managed_identity_credential = true;
69 self
70 }
71
72 pub fn include_app_service_managed_identity_credential(&mut self) -> &mut Self {
74 self.include_app_service_managed_identity_credential = true;
75 self
76 }
77
78 #[cfg(not(target_arch = "wasm32"))]
80 pub fn exclude_azure_cli_credential(&mut self) -> &mut Self {
81 self.include_azure_cli_credential = false;
82 self
83 }
84
85 fn included(&self) -> Vec<DefaultAzureCredentialType> {
87 let mut sources = Vec::new();
88 if self.include_environment_credential {
89 sources.push(DefaultAzureCredentialType::Environment);
90 }
91 if self.include_app_service_managed_identity_credential {
92 sources.push(DefaultAzureCredentialType::AppService);
93 }
94 if self.include_virtual_machine_managed_identity_credential {
95 sources.push(DefaultAzureCredentialType::VirtualMachine);
96 }
97 #[cfg(not(target_arch = "wasm32"))]
98 if self.include_azure_cli_credential {
99 sources.push(DefaultAzureCredentialType::AzureCli);
100 }
101 sources
102 }
103
104 fn create_sources(
107 &self,
108 included: &Vec<DefaultAzureCredentialType>,
109 ) -> azure_core::Result<Vec<DefaultAzureCredentialKind>> {
110 let mut sources = Vec::<DefaultAzureCredentialKind>::with_capacity(included.len());
111 let mut errors = Vec::new();
112 for source in included {
113 match source {
114 DefaultAzureCredentialType::Environment => {
115 match EnvironmentCredential::create(self.options.clone()) {
116 Ok(credential) => {
117 sources.push(DefaultAzureCredentialKind::Environment(credential))
118 }
119 Err(error) => errors.push(error),
120 }
121 }
122 DefaultAzureCredentialType::AppService => {
123 match AppServiceManagedIdentityCredential::create(self.options.clone()) {
124 Ok(credential) => {
125 sources.push(DefaultAzureCredentialKind::AppService(credential))
126 }
127 Err(error) => errors.push(error),
128 }
129 }
130 DefaultAzureCredentialType::VirtualMachine => {
131 sources.push(DefaultAzureCredentialKind::VirtualMachine(
132 VirtualMachineManagedIdentityCredential::new(self.options.clone()),
133 ));
134 }
135 #[cfg(not(target_arch = "wasm32"))]
136 DefaultAzureCredentialType::AzureCli => {
137 if let Ok(credential) = AzureCliCredential::create() {
138 sources.push(DefaultAzureCredentialKind::AzureCli(credential));
139 }
140 }
141 }
142 }
143 if sources.is_empty() {
144 return Err(Error::with_message(ErrorKind::Credential, || {
145 format!(
146 "No credential sources were available to be used for authentication.\n{}",
147 format_aggregate_error(&errors)
148 )
149 }));
150 }
151 Ok(sources)
152 }
153
154 pub fn build(&self) -> azure_core::Result<DefaultAzureCredential> {
156 let included = self.included();
157 let sources = self.create_sources(&included)?;
158 Ok(DefaultAzureCredential::with_sources(sources))
159 }
160}
161
162#[derive(Debug, PartialEq)]
164enum DefaultAzureCredentialType {
165 Environment,
166 AppService,
167 VirtualMachine,
168 #[cfg(not(target_arch = "wasm32"))]
169 AzureCli,
170}
171
172#[derive(Debug)]
174pub(crate) enum DefaultAzureCredentialKind {
175 Environment(EnvironmentCredential),
177 AppService(AppServiceManagedIdentityCredential),
179 VirtualMachine(VirtualMachineManagedIdentityCredential),
181 #[cfg(not(target_arch = "wasm32"))]
182 AzureCli(AzureCliCredential),
184}
185
186#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
187#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
188impl TokenCredential for DefaultAzureCredentialKind {
189 async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
190 match self {
191 DefaultAzureCredentialKind::Environment(credential) => {
192 credential.get_token(scopes).await.context(
193 ErrorKind::Credential,
194 "error getting environment credential",
195 )
196 }
197 DefaultAzureCredentialKind::AppService(credential) => {
198 credential.get_token(scopes).await.context(
199 ErrorKind::Credential,
200 "error getting managed identity credential for App Service",
201 )
202 }
203 DefaultAzureCredentialKind::VirtualMachine(credential) => {
204 credential
206 .get_token(scopes)
207 .timeout(Duration::from_secs(1))
208 .await
209 .context(
210 ErrorKind::Credential,
211 "getting virtual machine managed identity credential timed out",
212 )?
213 .context(
214 ErrorKind::Credential,
215 "error getting virtual machine managed identity credential",
216 )
217 }
218 #[cfg(not(target_arch = "wasm32"))]
219 DefaultAzureCredentialKind::AzureCli(credential) => {
220 credential.get_token(scopes).await.context(
221 ErrorKind::Credential,
222 "error getting token credential from Azure CLI",
223 )
224 }
225 }
226 }
227
228 async fn clear_cache(&self) -> azure_core::Result<()> {
230 match self {
231 DefaultAzureCredentialKind::Environment(credential) => credential.clear_cache().await,
232 DefaultAzureCredentialKind::AppService(credential) => credential.clear_cache().await,
233 DefaultAzureCredentialKind::VirtualMachine(credential) => {
234 credential.clear_cache().await
235 }
236 #[cfg(not(target_arch = "wasm32"))]
237 DefaultAzureCredentialKind::AzureCli(credential) => credential.clear_cache().await,
238 }
239 }
240}
241
242#[derive(Debug)]
251pub struct DefaultAzureCredential {
252 sources: Vec<DefaultAzureCredentialKind>,
253 cache: TokenCache,
254}
255
256impl DefaultAzureCredential {
257 pub fn create(options: TokenCredentialOptions) -> azure_core::Result<DefaultAzureCredential> {
258 DefaultAzureCredentialBuilder::default()
259 .with_options(options)
260 .build()
261 }
262
263 fn with_sources(sources: Vec<DefaultAzureCredentialKind>) -> Self {
265 DefaultAzureCredential {
266 sources,
267 cache: TokenCache::new(),
268 }
269 }
270
271 async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
273 let mut errors = Vec::new();
274 for source in &self.sources {
275 let token_res = source.get_token(scopes).await;
276
277 match token_res {
278 Ok(token) => return Ok(token),
279 Err(error) => errors.push(error),
280 }
281 }
282 Err(Error::with_message(ErrorKind::Credential, || {
283 format!(
284 "Multiple errors were encountered while attempting to authenticate:\n{}",
285 format_aggregate_error(&errors)
286 )
287 }))
288 }
289}
290
291pub fn create_default_credential() -> azure_core::Result<Arc<dyn TokenCredential>> {
293 DefaultAzureCredentialBuilder::default()
294 .build()
295 .map(|cred| Arc::new(cred) as Arc<dyn TokenCredential>)
296}
297
298#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
299#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
300impl TokenCredential for DefaultAzureCredential {
301 async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
302 self.cache.get_token(scopes, self.get_token(scopes)).await
303 }
304
305 async fn clear_cache(&self) -> azure_core::Result<()> {
307 self.cache.clear().await?;
309
310 for source in &self.sources {
311 source.clear_cache().await?;
312 }
313
314 Ok(())
315 }
316}
317
318fn format_aggregate_error(errors: &[Error]) -> String {
319 use std::error::Error;
320 errors
321 .iter()
322 .map(|e| {
323 let mut current: Option<&dyn Error> = Some(e);
324 let mut stack = vec![];
325 while let Some(err) = current.take() {
326 stack.push(err.to_string());
327 current = err.source();
328 }
329 stack.join(" - ")
330 })
331 .collect::<Vec<String>>()
332 .join("\n")
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn test_builder_included_credential_flags() {
341 let builder = DefaultAzureCredentialBuilder::new();
342 #[cfg(not(target_arch = "wasm32"))]
343 assert!(builder.include_azure_cli_credential);
344 assert!(builder.include_environment_credential);
345 assert!(builder.include_app_service_managed_identity_credential);
346 assert!(builder.include_virtual_machine_managed_identity_credential);
347
348 #[cfg(not(target_arch = "wasm32"))]
349 {
350 let mut builder = DefaultAzureCredentialBuilder::new();
351 builder.exclude_azure_cli_credential();
352 assert!(!builder.include_azure_cli_credential);
353 assert!(builder.include_environment_credential);
354 assert!(builder.include_app_service_managed_identity_credential);
355 assert!(builder.include_virtual_machine_managed_identity_credential);
356 }
357
358 let mut builder = DefaultAzureCredentialBuilder::new();
359 builder.exclude_environment_credential();
360 #[cfg(not(target_arch = "wasm32"))]
361 assert!(builder.include_azure_cli_credential);
362 assert!(!builder.include_environment_credential);
363 assert!(builder.include_app_service_managed_identity_credential);
364 assert!(builder.include_virtual_machine_managed_identity_credential);
365
366 let mut builder = DefaultAzureCredentialBuilder::new();
367 builder.exclude_managed_identity_credential();
368 #[cfg(not(target_arch = "wasm32"))]
369 assert!(builder.include_azure_cli_credential);
370 assert!(builder.include_environment_credential);
371 assert!(!builder.include_app_service_managed_identity_credential);
372 assert!(!builder.include_virtual_machine_managed_identity_credential);
373 }
374
375 #[test]
376 fn test_default_included_credential_types() {
378 let builder = DefaultAzureCredentialBuilder::new();
379 assert_eq!(
380 builder.included(),
381 vec![
382 DefaultAzureCredentialType::Environment,
383 DefaultAzureCredentialType::AppService,
384 DefaultAzureCredentialType::VirtualMachine,
385 DefaultAzureCredentialType::AzureCli,
386 ]
387 );
388 }
389
390 #[test]
392 fn test_exclude_virtual_machine_managed_identity_credential() {
393 let mut builder = DefaultAzureCredentialBuilder::new();
394 builder.exclude_virtual_machine_managed_identity_credential();
395 assert_eq!(
396 builder.included(),
397 vec![
398 DefaultAzureCredentialType::Environment,
399 DefaultAzureCredentialType::AppService,
400 DefaultAzureCredentialType::AzureCli,
401 ]
402 );
403 }
404
405 #[test]
407 fn test_exclude_environment_credential() -> azure_core::Result<()> {
408 let mut builder = DefaultAzureCredentialBuilder::new();
409 builder.exclude_environment_credential();
410 assert_eq!(
411 builder.included(),
412 vec![
413 DefaultAzureCredentialType::AppService,
414 DefaultAzureCredentialType::VirtualMachine,
415 DefaultAzureCredentialType::AzureCli,
416 ]
417 );
418 Ok(())
419 }
420
421 #[test]
423 fn test_exclude_azure_cli_credential() {
424 let mut builder = DefaultAzureCredentialBuilder::new();
425 builder.exclude_azure_cli_credential();
426 assert_eq!(
427 builder.included(),
428 vec![
429 DefaultAzureCredentialType::Environment,
430 DefaultAzureCredentialType::AppService,
431 DefaultAzureCredentialType::VirtualMachine,
432 ]
433 );
434 }
435
436 #[test]
438 fn test_exclude_managed_identity_credential() {
439 let mut builder = DefaultAzureCredentialBuilder::new();
440 builder.exclude_managed_identity_credential();
441 assert_eq!(
442 builder.included(),
443 vec![
444 DefaultAzureCredentialType::Environment,
445 DefaultAzureCredentialType::AzureCli,
446 ]
447 );
448 }
449}