azure_identity/token_credentials/
default_credentials.rs
#[cfg(not(target_arch = "wasm32"))]
use crate::AzureCliCredential;
use crate::{
timeout::TimeoutExt, token_credentials::cache::TokenCache, AppServiceManagedIdentityCredential,
EnvironmentCredential, TokenCredentialOptions, VirtualMachineManagedIdentityCredential,
};
use azure_core::{
auth::{AccessToken, TokenCredential},
error::{Error, ErrorKind, ResultExt},
};
use std::{sync::Arc, time::Duration};
pub struct DefaultAzureCredentialBuilder {
options: TokenCredentialOptions,
include_environment_credential: bool,
include_app_service_managed_identity_credential: bool,
include_virtual_machine_managed_identity_credential: bool,
#[cfg(not(target_arch = "wasm32"))]
include_azure_cli_credential: bool,
}
impl Default for DefaultAzureCredentialBuilder {
fn default() -> Self {
Self {
options: TokenCredentialOptions::default(),
include_environment_credential: true,
include_app_service_managed_identity_credential: true,
include_virtual_machine_managed_identity_credential: true,
#[cfg(not(target_arch = "wasm32"))]
include_azure_cli_credential: true,
}
}
}
impl DefaultAzureCredentialBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_options(&mut self, options: impl Into<TokenCredentialOptions>) -> &mut Self {
self.options = options.into();
self
}
pub fn exclude_environment_credential(&mut self) -> &mut Self {
self.include_environment_credential = false;
self
}
pub fn exclude_managed_identity_credential(&mut self) -> &mut Self {
self.include_app_service_managed_identity_credential = false;
self.include_virtual_machine_managed_identity_credential = false;
self
}
pub fn exclude_virtual_machine_managed_identity_credential(&mut self) -> &mut Self {
self.include_virtual_machine_managed_identity_credential = false;
self
}
pub fn include_virtual_machine_managed_identity_credential(&mut self) -> &mut Self {
self.include_virtual_machine_managed_identity_credential = true;
self
}
pub fn include_app_service_managed_identity_credential(&mut self) -> &mut Self {
self.include_app_service_managed_identity_credential = true;
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn exclude_azure_cli_credential(&mut self) -> &mut Self {
self.include_azure_cli_credential = false;
self
}
fn included(&self) -> Vec<DefaultAzureCredentialType> {
let mut sources = Vec::new();
if self.include_environment_credential {
sources.push(DefaultAzureCredentialType::Environment);
}
if self.include_app_service_managed_identity_credential {
sources.push(DefaultAzureCredentialType::AppService);
}
if self.include_virtual_machine_managed_identity_credential {
sources.push(DefaultAzureCredentialType::VirtualMachine);
}
#[cfg(not(target_arch = "wasm32"))]
if self.include_azure_cli_credential {
sources.push(DefaultAzureCredentialType::AzureCli);
}
sources
}
fn create_sources(
&self,
included: &Vec<DefaultAzureCredentialType>,
) -> azure_core::Result<Vec<DefaultAzureCredentialKind>> {
let mut sources = Vec::<DefaultAzureCredentialKind>::with_capacity(included.len());
let mut errors = Vec::new();
for source in included {
match source {
DefaultAzureCredentialType::Environment => {
match EnvironmentCredential::create(self.options.clone()) {
Ok(credential) => {
sources.push(DefaultAzureCredentialKind::Environment(credential))
}
Err(error) => errors.push(error),
}
}
DefaultAzureCredentialType::AppService => {
match AppServiceManagedIdentityCredential::create(self.options.clone()) {
Ok(credential) => {
sources.push(DefaultAzureCredentialKind::AppService(credential))
}
Err(error) => errors.push(error),
}
}
DefaultAzureCredentialType::VirtualMachine => {
sources.push(DefaultAzureCredentialKind::VirtualMachine(
VirtualMachineManagedIdentityCredential::new(self.options.clone()),
));
}
#[cfg(not(target_arch = "wasm32"))]
DefaultAzureCredentialType::AzureCli => {
if let Ok(credential) = AzureCliCredential::create() {
sources.push(DefaultAzureCredentialKind::AzureCli(credential));
}
}
}
}
if sources.is_empty() {
return Err(Error::with_message(ErrorKind::Credential, || {
format!(
"No credential sources were available to be used for authentication.\n{}",
format_aggregate_error(&errors)
)
}));
}
Ok(sources)
}
pub fn build(&self) -> azure_core::Result<DefaultAzureCredential> {
let included = self.included();
let sources = self.create_sources(&included)?;
Ok(DefaultAzureCredential::with_sources(sources))
}
}
#[derive(Debug, PartialEq)]
enum DefaultAzureCredentialType {
Environment,
AppService,
VirtualMachine,
#[cfg(not(target_arch = "wasm32"))]
AzureCli,
}
#[derive(Debug)]
pub(crate) enum DefaultAzureCredentialKind {
Environment(EnvironmentCredential),
AppService(AppServiceManagedIdentityCredential),
VirtualMachine(VirtualMachineManagedIdentityCredential),
#[cfg(not(target_arch = "wasm32"))]
AzureCli(AzureCliCredential),
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl TokenCredential for DefaultAzureCredentialKind {
async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
match self {
DefaultAzureCredentialKind::Environment(credential) => {
credential.get_token(scopes).await.context(
ErrorKind::Credential,
"error getting environment credential",
)
}
DefaultAzureCredentialKind::AppService(credential) => {
credential.get_token(scopes).await.context(
ErrorKind::Credential,
"error getting managed identity credential for App Service",
)
}
DefaultAzureCredentialKind::VirtualMachine(credential) => {
credential
.get_token(scopes)
.timeout(Duration::from_secs(1))
.await
.context(
ErrorKind::Credential,
"getting virtual machine managed identity credential timed out",
)?
.context(
ErrorKind::Credential,
"error getting virtual machine managed identity credential",
)
}
#[cfg(not(target_arch = "wasm32"))]
DefaultAzureCredentialKind::AzureCli(credential) => {
credential.get_token(scopes).await.context(
ErrorKind::Credential,
"error getting token credential from Azure CLI",
)
}
}
}
async fn clear_cache(&self) -> azure_core::Result<()> {
match self {
DefaultAzureCredentialKind::Environment(credential) => credential.clear_cache().await,
DefaultAzureCredentialKind::AppService(credential) => credential.clear_cache().await,
DefaultAzureCredentialKind::VirtualMachine(credential) => {
credential.clear_cache().await
}
#[cfg(not(target_arch = "wasm32"))]
DefaultAzureCredentialKind::AzureCli(credential) => credential.clear_cache().await,
}
}
}
#[derive(Debug)]
pub struct DefaultAzureCredential {
sources: Vec<DefaultAzureCredentialKind>,
cache: TokenCache,
}
impl DefaultAzureCredential {
pub fn create(options: TokenCredentialOptions) -> azure_core::Result<DefaultAzureCredential> {
DefaultAzureCredentialBuilder::default()
.with_options(options)
.build()
}
fn with_sources(sources: Vec<DefaultAzureCredentialKind>) -> Self {
DefaultAzureCredential {
sources,
cache: TokenCache::new(),
}
}
async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
let mut errors = Vec::new();
for source in &self.sources {
let token_res = source.get_token(scopes).await;
match token_res {
Ok(token) => return Ok(token),
Err(error) => errors.push(error),
}
}
Err(Error::with_message(ErrorKind::Credential, || {
format!(
"Multiple errors were encountered while attempting to authenticate:\n{}",
format_aggregate_error(&errors)
)
}))
}
}
pub fn create_default_credential() -> azure_core::Result<Arc<dyn TokenCredential>> {
DefaultAzureCredentialBuilder::default()
.build()
.map(|cred| Arc::new(cred) as Arc<dyn TokenCredential>)
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl TokenCredential for DefaultAzureCredential {
async fn get_token(&self, scopes: &[&str]) -> azure_core::Result<AccessToken> {
self.cache.get_token(scopes, self.get_token(scopes)).await
}
async fn clear_cache(&self) -> azure_core::Result<()> {
self.cache.clear().await?;
for source in &self.sources {
source.clear_cache().await?;
}
Ok(())
}
}
fn format_aggregate_error(errors: &[Error]) -> String {
use std::error::Error;
errors
.iter()
.map(|e| {
let mut current: Option<&dyn Error> = Some(e);
let mut stack = vec![];
while let Some(err) = current.take() {
stack.push(err.to_string());
current = err.source();
}
stack.join(" - ")
})
.collect::<Vec<String>>()
.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_included_credential_flags() {
let builder = DefaultAzureCredentialBuilder::new();
#[cfg(not(target_arch = "wasm32"))]
assert!(builder.include_azure_cli_credential);
assert!(builder.include_environment_credential);
assert!(builder.include_app_service_managed_identity_credential);
assert!(builder.include_virtual_machine_managed_identity_credential);
#[cfg(not(target_arch = "wasm32"))]
{
let mut builder = DefaultAzureCredentialBuilder::new();
builder.exclude_azure_cli_credential();
assert!(!builder.include_azure_cli_credential);
assert!(builder.include_environment_credential);
assert!(builder.include_app_service_managed_identity_credential);
assert!(builder.include_virtual_machine_managed_identity_credential);
}
let mut builder = DefaultAzureCredentialBuilder::new();
builder.exclude_environment_credential();
#[cfg(not(target_arch = "wasm32"))]
assert!(builder.include_azure_cli_credential);
assert!(!builder.include_environment_credential);
assert!(builder.include_app_service_managed_identity_credential);
assert!(builder.include_virtual_machine_managed_identity_credential);
let mut builder = DefaultAzureCredentialBuilder::new();
builder.exclude_managed_identity_credential();
#[cfg(not(target_arch = "wasm32"))]
assert!(builder.include_azure_cli_credential);
assert!(builder.include_environment_credential);
assert!(!builder.include_app_service_managed_identity_credential);
assert!(!builder.include_virtual_machine_managed_identity_credential);
}
#[test]
fn test_default_included_credential_types() {
let builder = DefaultAzureCredentialBuilder::new();
assert_eq!(
builder.included(),
vec![
DefaultAzureCredentialType::Environment,
DefaultAzureCredentialType::AppService,
DefaultAzureCredentialType::VirtualMachine,
DefaultAzureCredentialType::AzureCli,
]
);
}
#[test]
fn test_exclude_virtual_machine_managed_identity_credential() {
let mut builder = DefaultAzureCredentialBuilder::new();
builder.exclude_virtual_machine_managed_identity_credential();
assert_eq!(
builder.included(),
vec![
DefaultAzureCredentialType::Environment,
DefaultAzureCredentialType::AppService,
DefaultAzureCredentialType::AzureCli,
]
);
}
#[test]
fn test_exclude_environment_credential() -> azure_core::Result<()> {
let mut builder = DefaultAzureCredentialBuilder::new();
builder.exclude_environment_credential();
assert_eq!(
builder.included(),
vec![
DefaultAzureCredentialType::AppService,
DefaultAzureCredentialType::VirtualMachine,
DefaultAzureCredentialType::AzureCli,
]
);
Ok(())
}
#[test]
fn test_exclude_azure_cli_credential() {
let mut builder = DefaultAzureCredentialBuilder::new();
builder.exclude_azure_cli_credential();
assert_eq!(
builder.included(),
vec![
DefaultAzureCredentialType::Environment,
DefaultAzureCredentialType::AppService,
DefaultAzureCredentialType::VirtualMachine,
]
);
}
#[test]
fn test_exclude_managed_identity_credential() {
let mut builder = DefaultAzureCredentialBuilder::new();
builder.exclude_managed_identity_credential();
assert_eq!(
builder.included(),
vec![
DefaultAzureCredentialType::Environment,
DefaultAzureCredentialType::AzureCli,
]
);
}
}