You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by tu...@apache.org on 2023/01/25 14:51:04 UTC
[arrow-rs] branch master updated: [object_store] support azure managed and workload identities (#3581)
This is an automated email from the ASF dual-hosted git repository.
tustvold pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
The following commit(s) were added to refs/heads/master by this push:
new 73ce0760d [object_store] support azure managed and workload identities (#3581)
73ce0760d is described below
commit 73ce0760d849722e71a8a1a44b6b3230ae68f8b3
Author: Robert Pack <42...@users.noreply.github.com>
AuthorDate: Wed Jan 25 15:50:58 2023 +0100
[object_store] support azure managed and workload identities (#3581)
* feat: add azure managed identity credential
* test: azure managed identity credential
* feat: add azure federated token credential
* test: add workload identity test
* refactor: PR feedback
* Update object_store/src/azure/mod.rs
Co-authored-by: Raphael Taylor-Davies <17...@users.noreply.github.com>
* refactor: id priorities
* refactor: use managed identity as default credential
* chore: remove usused parameter
Co-authored-by: Raphael Taylor-Davies <17...@users.noreply.github.com>
---
object_store/src/azure/client.rs | 8 +-
object_store/src/azure/credential.rs | 337 +++++++++++++++++++++++++++++++++--
object_store/src/azure/mod.rs | 147 +++++++++++++--
3 files changed, 459 insertions(+), 33 deletions(-)
diff --git a/object_store/src/azure/client.rs b/object_store/src/azure/client.rs
index 426b3b164..e42950b90 100644
--- a/object_store/src/azure/client.rs
+++ b/object_store/src/azure/client.rs
@@ -169,9 +169,11 @@ impl AzureClient {
CredentialProvider::AccessKey(key) => {
Ok(AzureCredential::AccessKey(key.to_owned()))
}
- CredentialProvider::ClientSecret(cred) => {
- let token = cred
- .fetch_token(&self.client, &self.config.retry_config)
+ CredentialProvider::TokenCredential(cache, cred) => {
+ let token = cache
+ .get_or_insert_with(|| {
+ cred.fetch_token(&self.client, &self.config.retry_config)
+ })
.await
.context(AuthorizationSnafu)?;
Ok(AzureCredential::AuthorizationToken(
diff --git a/object_store/src/azure/credential.rs b/object_store/src/azure/credential.rs
index 96ff8ce15..280d84300 100644
--- a/object_store/src/azure/credential.rs
+++ b/object_store/src/azure/credential.rs
@@ -31,6 +31,7 @@ use reqwest::{
},
Client, Method, RequestBuilder,
};
+use serde::Deserialize;
use snafu::{ResultExt, Snafu};
use std::borrow::Cow;
use std::str;
@@ -44,8 +45,11 @@ pub(crate) static DELETE_SNAPSHOTS: HeaderName =
HeaderName::from_static("x-ms-delete-snapshots");
pub(crate) static COPY_SOURCE: HeaderName = HeaderName::from_static("x-ms-copy-source");
static CONTENT_MD5: HeaderName = HeaderName::from_static("content-md5");
-pub(crate) static RFC1123_FMT: &str = "%a, %d %h %Y %T GMT";
+pub(crate) const RFC1123_FMT: &str = "%a, %d %h %Y %T GMT";
const CONTENT_TYPE_JSON: &str = "application/json";
+const MSI_SECRET_ENV_KEY: &str = "IDENTITY_HEADER";
+const MSI_API_VERSION: &str = "2019-08-01";
+const AZURE_STORAGE_SCOPE: &str = "https://storage.azure.com/.default";
#[derive(Debug, Snafu)]
pub enum Error {
@@ -54,6 +58,9 @@ pub enum Error {
#[snafu(display("Error getting token response body: {}", source))]
TokenResponseBody { source: reqwest::Error },
+
+ #[snafu(display("Error reading federated token file "))]
+ FederatedTokenFile,
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
@@ -63,7 +70,7 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
pub enum CredentialProvider {
AccessKey(String),
SASToken(Vec<(String, String)>),
- ClientSecret(ClientSecretOAuthProvider),
+ TokenCredential(TokenCache<String>, Box<dyn TokenCredential>),
}
pub(crate) enum AzureCredential {
@@ -273,7 +280,16 @@ fn lexy_sort<'a>(
values
}
-#[derive(serde::Deserialize, Debug)]
+#[async_trait::async_trait]
+pub trait TokenCredential: std::fmt::Debug + Send + Sync + 'static {
+ async fn fetch_token(
+ &self,
+ client: &Client,
+ retry: &RetryConfig,
+ ) -> Result<TemporaryToken<String>>;
+}
+
+#[derive(Deserialize, Debug)]
struct TokenResponse {
access_token: String,
expires_in: u64,
@@ -282,11 +298,9 @@ struct TokenResponse {
/// Encapsulates the logic to perform an OAuth token challenge
#[derive(Debug)]
pub struct ClientSecretOAuthProvider {
- scope: String,
token_url: String,
client_id: String,
client_secret: String,
- cache: TokenCache<String>,
}
impl ClientSecretOAuthProvider {
@@ -294,45 +308,220 @@ impl ClientSecretOAuthProvider {
pub fn new(
client_id: String,
client_secret: String,
- tenant_id: String,
+ tenant_id: impl AsRef<str>,
authority_host: Option<String>,
) -> Self {
let authority_host = authority_host
.unwrap_or_else(|| authority_hosts::AZURE_PUBLIC_CLOUD.to_owned());
Self {
- scope: "https://storage.azure.com/.default".to_owned(),
- token_url: format!("{}/{}/oauth2/v2.0/token", authority_host, tenant_id),
+ token_url: format!(
+ "{}/{}/oauth2/v2.0/token",
+ authority_host,
+ tenant_id.as_ref()
+ ),
client_id,
client_secret,
- cache: TokenCache::default(),
}
}
+}
+#[async_trait::async_trait]
+impl TokenCredential for ClientSecretOAuthProvider {
/// Fetch a token
- pub async fn fetch_token(
+ async fn fetch_token(
&self,
client: &Client,
retry: &RetryConfig,
- ) -> Result<String> {
- self.cache
- .get_or_insert_with(|| self.fetch_token_inner(client, retry))
+ ) -> Result<TemporaryToken<String>> {
+ let response: TokenResponse = client
+ .request(Method::POST, &self.token_url)
+ .header(ACCEPT, HeaderValue::from_static(CONTENT_TYPE_JSON))
+ .form(&[
+ ("client_id", self.client_id.as_str()),
+ ("client_secret", self.client_secret.as_str()),
+ ("scope", AZURE_STORAGE_SCOPE),
+ ("grant_type", "client_credentials"),
+ ])
+ .send_retry(retry)
+ .await
+ .context(TokenRequestSnafu)?
+ .json()
.await
+ .context(TokenResponseBodySnafu)?;
+
+ let token = TemporaryToken {
+ token: response.access_token,
+ expiry: Instant::now() + Duration::from_secs(response.expires_in),
+ };
+
+ Ok(token)
}
+}
+
+fn expires_in_string<'de, D>(deserializer: D) -> std::result::Result<u64, D::Error>
+where
+ D: serde::de::Deserializer<'de>,
+{
+ let v = String::deserialize(deserializer)?;
+ v.parse::<u64>().map_err(serde::de::Error::custom)
+}
+
+// NOTE: expires_on is a String version of unix epoch time, not an integer.
+// <https://learn.microsoft.com/en-gb/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http>
+#[derive(Debug, Clone, Deserialize)]
+struct MsiTokenResponse {
+ pub access_token: String,
+ #[serde(deserialize_with = "expires_in_string")]
+ pub expires_in: u64,
+}
+
+/// Attempts authentication using a managed identity that has been assigned to the deployment environment.
+///
+/// This authentication type works in Azure VMs, App Service and Azure Functions applications, as well as the Azure Cloud Shell
+/// <https://learn.microsoft.com/en-gb/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http>
+#[derive(Debug)]
+pub struct ImdsManagedIdentityOAuthProvider {
+ msi_endpoint: String,
+ client_id: Option<String>,
+ object_id: Option<String>,
+ msi_res_id: Option<String>,
+ client: Client,
+}
+
+impl ImdsManagedIdentityOAuthProvider {
+ /// Create a new [`ImdsManagedIdentityOAuthProvider`] for an azure backed store
+ pub fn new(
+ client_id: Option<String>,
+ object_id: Option<String>,
+ msi_res_id: Option<String>,
+ msi_endpoint: Option<String>,
+ client: Client,
+ ) -> Self {
+ let msi_endpoint = msi_endpoint.unwrap_or_else(|| {
+ "http://169.254.169.254/metadata/identity/oauth2/token".to_owned()
+ });
- /// Fetch a fresh token
- async fn fetch_token_inner(
+ Self {
+ msi_endpoint,
+ client_id,
+ object_id,
+ msi_res_id,
+ client,
+ }
+ }
+}
+
+#[async_trait::async_trait]
+impl TokenCredential for ImdsManagedIdentityOAuthProvider {
+ /// Fetch a token
+ async fn fetch_token(
+ &self,
+ _client: &Client,
+ retry: &RetryConfig,
+ ) -> Result<TemporaryToken<String>> {
+ let mut query_items = vec![
+ ("api-version", MSI_API_VERSION),
+ ("resource", AZURE_STORAGE_SCOPE),
+ ];
+
+ let mut identity = None;
+ if let Some(client_id) = &self.client_id {
+ identity = Some(("client_id", client_id));
+ }
+ if let Some(object_id) = &self.object_id {
+ identity = Some(("object_id", object_id));
+ }
+ if let Some(msi_res_id) = &self.msi_res_id {
+ identity = Some(("msi_res_id", msi_res_id));
+ }
+ if let Some((key, value)) = identity {
+ query_items.push((key, value));
+ }
+
+ let mut builder = self
+ .client
+ .request(Method::GET, &self.msi_endpoint)
+ .header("metadata", "true")
+ .query(&query_items);
+
+ if let Ok(val) = std::env::var(MSI_SECRET_ENV_KEY) {
+ builder = builder.header("x-identity-header", val);
+ };
+
+ let response: MsiTokenResponse = builder
+ .send_retry(retry)
+ .await
+ .context(TokenRequestSnafu)?
+ .json()
+ .await
+ .context(TokenResponseBodySnafu)?;
+
+ let token = TemporaryToken {
+ token: response.access_token,
+ expiry: Instant::now() + Duration::from_secs(response.expires_in),
+ };
+
+ Ok(token)
+ }
+}
+
+/// Credential for using workload identity dfederation
+///
+/// <https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation>
+#[derive(Debug)]
+pub struct WorkloadIdentityOAuthProvider {
+ token_url: String,
+ client_id: String,
+ federated_token_file: String,
+}
+
+impl WorkloadIdentityOAuthProvider {
+ /// Create a new [`WorkloadIdentityOAuthProvider`] for an azure backed store
+ pub fn new(
+ client_id: impl Into<String>,
+ federated_token_file: impl Into<String>,
+ tenant_id: impl AsRef<str>,
+ authority_host: Option<String>,
+ ) -> Self {
+ let authority_host = authority_host
+ .unwrap_or_else(|| authority_hosts::AZURE_PUBLIC_CLOUD.to_owned());
+
+ Self {
+ token_url: format!(
+ "{}/{}/oauth2/v2.0/token",
+ authority_host,
+ tenant_id.as_ref()
+ ),
+ client_id: client_id.into(),
+ federated_token_file: federated_token_file.into(),
+ }
+ }
+}
+
+#[async_trait::async_trait]
+impl TokenCredential for WorkloadIdentityOAuthProvider {
+ /// Fetch a token
+ async fn fetch_token(
&self,
client: &Client,
retry: &RetryConfig,
) -> Result<TemporaryToken<String>> {
+ let token_str = std::fs::read_to_string(&self.federated_token_file)
+ .map_err(|_| Error::FederatedTokenFile)?;
+
+ // https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential
let response: TokenResponse = client
.request(Method::POST, &self.token_url)
.header(ACCEPT, HeaderValue::from_static(CONTENT_TYPE_JSON))
.form(&[
("client_id", self.client_id.as_str()),
- ("client_secret", self.client_secret.as_str()),
- ("scope", self.scope.as_str()),
+ (
+ "client_assertion_type",
+ "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
+ ),
+ ("client_assertion", token_str.as_str()),
+ ("scope", AZURE_STORAGE_SCOPE),
("grant_type", "client_credentials"),
])
.send_retry(retry)
@@ -350,3 +539,117 @@ impl ClientSecretOAuthProvider {
Ok(token)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::client::mock_server::MockServer;
+ use futures::executor::block_on;
+ use hyper::body::to_bytes;
+ use hyper::{Body, Response};
+ use reqwest::{Client, Method};
+ use tempfile::NamedTempFile;
+
+ #[tokio::test]
+ async fn test_managed_identity() {
+ let server = MockServer::new();
+
+ std::env::set_var(MSI_SECRET_ENV_KEY, "env-secret");
+
+ let endpoint = server.url();
+ let client = Client::new();
+ let retry_config = RetryConfig::default();
+
+ // Test IMDS
+ server.push_fn(|req| {
+ assert_eq!(req.uri().path(), "/metadata/identity/oauth2/token");
+ assert!(req.uri().query().unwrap().contains("client_id=client_id"));
+ assert_eq!(req.method(), &Method::GET);
+ let t = req
+ .headers()
+ .get("x-identity-header")
+ .unwrap()
+ .to_str()
+ .unwrap();
+ assert_eq!(t, "env-secret");
+ let t = req.headers().get("metadata").unwrap().to_str().unwrap();
+ assert_eq!(t, "true");
+ Response::new(Body::from(
+ r#"
+ {
+ "access_token": "TOKEN",
+ "refresh_token": "",
+ "expires_in": "3599",
+ "expires_on": "1506484173",
+ "not_before": "1506480273",
+ "resource": "https://management.azure.com/",
+ "token_type": "Bearer"
+ }
+ "#,
+ ))
+ });
+
+ let credential = ImdsManagedIdentityOAuthProvider::new(
+ Some("client_id".into()),
+ None,
+ None,
+ Some(format!("{}/metadata/identity/oauth2/token", endpoint)),
+ client.clone(),
+ );
+
+ let token = credential
+ .fetch_token(&client, &retry_config)
+ .await
+ .unwrap();
+
+ assert_eq!(&token.token, "TOKEN");
+ }
+
+ #[tokio::test]
+ async fn test_workload_identity() {
+ let server = MockServer::new();
+ let tokenfile = NamedTempFile::new().unwrap();
+ let tenant = "tenant";
+ std::fs::write(tokenfile.path(), "federated-token").unwrap();
+
+ let endpoint = server.url();
+ let client = Client::new();
+ let retry_config = RetryConfig::default();
+
+ // Test IMDS
+ server.push_fn(move |req| {
+ assert_eq!(req.uri().path(), format!("/{}/oauth2/v2.0/token", tenant));
+ assert_eq!(req.method(), &Method::POST);
+ let body = block_on(to_bytes(req.into_body())).unwrap();
+ let body = String::from_utf8(body.to_vec()).unwrap();
+ assert!(body.contains("federated-token"));
+ Response::new(Body::from(
+ r#"
+ {
+ "access_token": "TOKEN",
+ "refresh_token": "",
+ "expires_in": 3599,
+ "expires_on": "1506484173",
+ "not_before": "1506480273",
+ "resource": "https://management.azure.com/",
+ "token_type": "Bearer"
+ }
+ "#,
+ ))
+ });
+
+ let credential = WorkloadIdentityOAuthProvider::new(
+ "client_id",
+ tokenfile.path().to_str().unwrap(),
+ tenant,
+ Some(endpoint.to_string()),
+ );
+
+ let token = credential
+ .fetch_token(&client, &retry_config)
+ .await
+ .unwrap();
+
+ assert_eq!(&token.token, "TOKEN");
+ }
+}
diff --git a/object_store/src/azure/mod.rs b/object_store/src/azure/mod.rs
index 3bce8e598..1eea27801 100644
--- a/object_store/src/azure/mod.rs
+++ b/object_store/src/azure/mod.rs
@@ -27,6 +27,7 @@
//! a way to drop old blocks. Instead unused blocks are automatically cleaned up
//! after 7 days.
use self::client::{BlockId, BlockList};
+use crate::client::token::TokenCache;
use crate::{
multipart::{CloudMultiPartUpload, CloudMultiPartUploadImpl, UploadPart},
path::Path,
@@ -65,6 +66,8 @@ const EMULATOR_ACCOUNT: &str = "devstoreaccount1";
const EMULATOR_ACCOUNT_KEY: &str =
"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
+const MSI_ENDPOINT_ENV_KEY: &str = "IDENTITY_ENDPOINT";
+
/// A specialized `Error` for Azure object store-related errors
#[derive(Debug, Snafu)]
#[allow(missing_docs)]
@@ -393,6 +396,10 @@ pub struct MicrosoftAzureBuilder {
authority_host: Option<String>,
url: Option<String>,
use_emulator: bool,
+ msi_endpoint: Option<String>,
+ object_id: Option<String>,
+ msi_resource_id: Option<String>,
+ federated_token_file: Option<String>,
retry_config: RetryConfig,
client_options: ClientOptions,
}
@@ -496,6 +503,36 @@ pub enum AzureConfigKey {
/// - `object_store_use_emulator`
/// - `use_emulator`
UseEmulator,
+
+ /// Endpoint to request a imds managed identity token
+ ///
+ /// Supported keys:
+ /// - `azure_msi_endpoint`
+ /// - `azure_identity_endpoint`
+ /// - `identity_endpoint`
+ /// - `msi_endpoint`
+ MsiEndpoint,
+
+ /// Object id for use with managed identity authentication
+ ///
+ /// Supported keys:
+ /// - `azure_object_id`
+ /// - `object_id`
+ ObjectId,
+
+ /// Msi resource id for use with managed identity authentication
+ ///
+ /// Supported keys:
+ /// - `azure_msi_resource_id`
+ /// - `msi_resource_id`
+ MsiResourceId,
+
+ /// File containing token for Azure AD workload identity federation
+ ///
+ /// Supported keys:
+ /// - `azure_federated_token_file`
+ /// - `federated_token_file`
+ FederatedTokenFile,
}
impl AsRef<str> for AzureConfigKey {
@@ -509,6 +546,10 @@ impl AsRef<str> for AzureConfigKey {
Self::SasKey => "azure_storage_sas_key",
Self::Token => "azure_storage_token",
Self::UseEmulator => "azure_storage_use_emulator",
+ Self::MsiEndpoint => "azure_msi_endpoint",
+ Self::ObjectId => "azure_object_id",
+ Self::MsiResourceId => "azure_msi_resource_id",
+ Self::FederatedTokenFile => "azure_federated_token_file",
}
}
}
@@ -543,6 +584,15 @@ impl FromStr for AzureConfigKey {
| "sas_token" => Ok(Self::SasKey),
"azure_storage_token" | "bearer_token" | "token" => Ok(Self::Token),
"azure_storage_use_emulator" | "use_emulator" => Ok(Self::UseEmulator),
+ "azure_msi_endpoint"
+ | "azure_identity_endpoint"
+ | "identity_endpoint"
+ | "msi_endpoint" => Ok(Self::MsiEndpoint),
+ "azure_object_id" | "object_id" => Ok(Self::ObjectId),
+ "azure_msi_resource_id" | "msi_resource_id" => Ok(Self::MsiResourceId),
+ "azure_federated_token_file" | "federated_token_file" => {
+ Ok(Self::FederatedTokenFile)
+ }
_ => Err(Error::UnknownConfigurationKey { key: s.into() }.into()),
}
}
@@ -600,6 +650,10 @@ impl MicrosoftAzureBuilder {
builder.client_options.with_allow_http(str_is_truthy(&text));
}
+ if let Ok(text) = std::env::var(MSI_ENDPOINT_ENV_KEY) {
+ builder = builder.with_msi_endpoint(text);
+ }
+
builder
}
@@ -644,6 +698,12 @@ impl MicrosoftAzureBuilder {
AzureConfigKey::AuthorityId => self.tenant_id = Some(value.into()),
AzureConfigKey::SasKey => self.sas_key = Some(value.into()),
AzureConfigKey::Token => self.bearer_token = Some(value.into()),
+ AzureConfigKey::MsiEndpoint => self.msi_endpoint = Some(value.into()),
+ AzureConfigKey::ObjectId => self.object_id = Some(value.into()),
+ AzureConfigKey::MsiResourceId => self.msi_resource_id = Some(value.into()),
+ AzureConfigKey::FederatedTokenFile => {
+ self.federated_token_file = Some(value.into())
+ }
AzureConfigKey::UseEmulator => {
self.use_emulator = str_is_truthy(&value.into())
}
@@ -743,6 +803,24 @@ impl MicrosoftAzureBuilder {
self
}
+ /// Sets the client id for use in client secret or k8s federated credential flow
+ pub fn with_client_id(mut self, client_id: impl Into<String>) -> Self {
+ self.client_id = Some(client_id.into());
+ self
+ }
+
+ /// Sets the client secret for use in client secret flow
+ pub fn with_client_secret(mut self, client_secret: impl Into<String>) -> Self {
+ self.client_secret = Some(client_secret.into());
+ self
+ }
+
+ /// Sets the tenant id for use in client secret or k8s federated credential flow
+ pub fn with_tenant_id(mut self, tenant_id: impl Into<String>) -> Self {
+ self.tenant_id = Some(tenant_id.into());
+ self
+ }
+
/// Set query pairs appended to the url for shared access signature authorization
pub fn with_sas_authorization(
mut self,
@@ -769,8 +847,8 @@ impl MicrosoftAzureBuilder {
/// Sets an alternative authority host for OAuth based authorization
/// common hosts for azure clouds are defined in [authority_hosts].
/// Defaults to <https://login.microsoftonline.com>
- pub fn with_authority_host(mut self, authority_host: String) -> Self {
- self.authority_host = Some(authority_host);
+ pub fn with_authority_host(mut self, authority_host: impl Into<String>) -> Self {
+ self.authority_host = Some(authority_host.into());
self
}
@@ -792,6 +870,23 @@ impl MicrosoftAzureBuilder {
self
}
+ /// Sets the endpoint for acquiring managed identity token
+ pub fn with_msi_endpoint(mut self, msi_endpoint: impl Into<String>) -> Self {
+ self.msi_endpoint = Some(msi_endpoint.into());
+ self
+ }
+
+ /// Sets a file path for acquiring azure federated identity token in k8s
+ ///
+ /// requires `client_id` and `tenant_id` to be set
+ pub fn with_federated_token_file(
+ mut self,
+ federated_token_file: impl Into<String>,
+ ) -> Self {
+ self.federated_token_file = Some(federated_token_file.into());
+ self
+ }
+
/// Configure a connection to container with given name on Microsoft Azure
/// Blob store.
pub fn build(mut self) -> Result<MicrosoftAzure> {
@@ -821,28 +916,54 @@ impl MicrosoftAzureBuilder {
let url = Url::parse(&account_url)
.context(UnableToParseUrlSnafu { url: account_url })?;
let credential = if let Some(bearer_token) = self.bearer_token {
- Ok(credential::CredentialProvider::AccessKey(bearer_token))
+ credential::CredentialProvider::AccessKey(bearer_token)
} else if let Some(access_key) = self.access_key {
- Ok(credential::CredentialProvider::AccessKey(access_key))
+ credential::CredentialProvider::AccessKey(access_key)
+ } else if let (Some(client_id), Some(tenant_id), Some(federated_token_file)) =
+ (&self.client_id, &self.tenant_id, self.federated_token_file)
+ {
+ let client_credential = credential::WorkloadIdentityOAuthProvider::new(
+ client_id,
+ federated_token_file,
+ tenant_id,
+ self.authority_host,
+ );
+ credential::CredentialProvider::TokenCredential(
+ TokenCache::default(),
+ Box::new(client_credential),
+ )
} else if let (Some(client_id), Some(client_secret), Some(tenant_id)) =
- (self.client_id, self.client_secret, self.tenant_id)
+ (&self.client_id, self.client_secret, &self.tenant_id)
{
let client_credential = credential::ClientSecretOAuthProvider::new(
- client_id,
+ client_id.clone(),
client_secret,
tenant_id,
self.authority_host,
);
- Ok(credential::CredentialProvider::ClientSecret(
- client_credential,
- ))
+ credential::CredentialProvider::TokenCredential(
+ TokenCache::default(),
+ Box::new(client_credential),
+ )
} else if let Some(query_pairs) = self.sas_query_pairs {
- Ok(credential::CredentialProvider::SASToken(query_pairs))
+ credential::CredentialProvider::SASToken(query_pairs)
} else if let Some(sas) = self.sas_key {
- Ok(credential::CredentialProvider::SASToken(split_sas(&sas)?))
+ credential::CredentialProvider::SASToken(split_sas(&sas)?)
} else {
- Err(Error::MissingCredentials {})
- }?;
+ let client =
+ self.client_options.clone().with_allow_http(true).client()?;
+ let msi_credential = credential::ImdsManagedIdentityOAuthProvider::new(
+ self.client_id,
+ self.object_id,
+ self.msi_resource_id,
+ self.msi_endpoint,
+ client,
+ );
+ credential::CredentialProvider::TokenCredential(
+ TokenCache::default(),
+ Box::new(msi_credential),
+ )
+ };
(false, url, credential, account_name)
};