Use client assertion credential within AzurePipelinesCredential and WorkloadIdentityCredential (#5802)
* Use ClientAssertionCredential within AzurePipelinesCredential. * Use ClientAssertionCredential in WorkloadIdentityCredential. * Fix DefaultAzureCredentia.LogMessages test since an extra log got added. * Disable tests that dont correctly simulate the token request and return the test response. * Address PR feedback and make sure base options are passed in to underlying client assertion credential. * Address PR feedback - move credential ctor into validation checks. * Address PR feedback, add const. * Add a ClientAssertionCredentialImpl to make sure logs use the calling credential name.
This commit is contained in:
parent
f9ea69c0e8
commit
5fd26a6832
@ -77,6 +77,7 @@ set(
|
||||
src/managed_identity_credential.cpp
|
||||
src/managed_identity_source.cpp
|
||||
src/private/chained_token_credential_impl.hpp
|
||||
src/private/client_assertion_credential_impl.hpp
|
||||
src/private/identity_log.hpp
|
||||
src/private/managed_identity_source.hpp
|
||||
src/private/package_version.hpp
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "azure/identity/client_assertion_credential.hpp"
|
||||
#include "azure/identity/detail/client_credential_core.hpp"
|
||||
#include "azure/identity/detail/token_cache.hpp"
|
||||
|
||||
@ -20,7 +21,7 @@
|
||||
|
||||
namespace Azure { namespace Identity {
|
||||
namespace _detail {
|
||||
class TokenCredentialImpl;
|
||||
class ClientAssertionCredentialImpl;
|
||||
} // namespace _detail
|
||||
|
||||
/**
|
||||
@ -57,12 +58,9 @@ namespace Azure { namespace Identity {
|
||||
private:
|
||||
std::string m_serviceConnectionId;
|
||||
std::string m_systemAccessToken;
|
||||
_detail::ClientCredentialCore m_clientCredentialCore;
|
||||
Azure::Core::Http::_internal::HttpPipeline m_httpPipeline;
|
||||
std::string m_oidcRequestUrl;
|
||||
std::unique_ptr<_detail::TokenCredentialImpl> m_tokenCredentialImpl;
|
||||
std::string m_requestBody;
|
||||
_detail::TokenCache m_tokenCache;
|
||||
std::unique_ptr<_detail::ClientAssertionCredentialImpl> m_clientAssertionCredentialImpl;
|
||||
|
||||
std::string GetAssertion(Core::Context const& context) const;
|
||||
Azure::Core::Http::Request CreateOidcRequestMessage() const;
|
||||
|
||||
@ -9,17 +9,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "azure/identity/detail/client_credential_core.hpp"
|
||||
#include "azure/identity/detail/token_cache.hpp"
|
||||
|
||||
#include <azure/core/credentials/token_credential_options.hpp>
|
||||
#include <azure/core/http/http.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Azure { namespace Identity {
|
||||
namespace _detail {
|
||||
class TokenCredentialImpl;
|
||||
class ClientAssertionCredentialImpl;
|
||||
} // namespace _detail
|
||||
|
||||
/**
|
||||
@ -55,11 +53,7 @@ namespace Azure { namespace Identity {
|
||||
*/
|
||||
class ClientAssertionCredential final : public Core::Credentials::TokenCredential {
|
||||
private:
|
||||
std::function<std::string(Core::Context const&)> m_assertionCallback;
|
||||
_detail::ClientCredentialCore m_clientCredentialCore;
|
||||
std::unique_ptr<_detail::TokenCredentialImpl> m_tokenCredentialImpl;
|
||||
std::string m_requestBody;
|
||||
_detail::TokenCache m_tokenCache;
|
||||
std::unique_ptr<_detail::ClientAssertionCredentialImpl> m_impl;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "azure/identity/client_assertion_credential.hpp"
|
||||
#include "azure/identity/detail/client_credential_core.hpp"
|
||||
#include "azure/identity/detail/token_cache.hpp"
|
||||
|
||||
@ -18,7 +19,7 @@
|
||||
|
||||
namespace Azure { namespace Identity {
|
||||
namespace _detail {
|
||||
class TokenCredentialImpl;
|
||||
class ClientAssertionCredentialImpl;
|
||||
} // namespace _detail
|
||||
|
||||
/**
|
||||
@ -74,12 +75,11 @@ namespace Azure { namespace Identity {
|
||||
*/
|
||||
class WorkloadIdentityCredential final : public Core::Credentials::TokenCredential {
|
||||
private:
|
||||
_detail::TokenCache m_tokenCache;
|
||||
_detail::ClientCredentialCore m_clientCredentialCore;
|
||||
std::unique_ptr<_detail::TokenCredentialImpl> m_tokenCredentialImpl;
|
||||
std::string m_requestBody;
|
||||
std::unique_ptr<_detail::ClientAssertionCredentialImpl> m_clientAssertionCredentialImpl;
|
||||
std::string m_tokenFilePath;
|
||||
|
||||
std::string GetAssertion(Core::Context const& context) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a Workload Identity Credential.
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
|
||||
#include "azure/identity/azure_pipelines_credential.hpp"
|
||||
|
||||
#include "private/client_assertion_credential_impl.hpp"
|
||||
#include "private/identity_log.hpp"
|
||||
#include "private/package_version.hpp"
|
||||
#include "private/tenant_id_resolver.hpp"
|
||||
#include "private/token_credential_impl.hpp"
|
||||
|
||||
#include <azure/core/internal/json/json.hpp>
|
||||
|
||||
@ -28,30 +28,6 @@ using Azure::Core::Json::_internal::json;
|
||||
using Azure::Identity::_detail::IdentityLog;
|
||||
using Azure::Identity::_detail::PackageVersion;
|
||||
using Azure::Identity::_detail::TenantIdResolver;
|
||||
using Azure::Identity::_detail::TokenCredentialImpl;
|
||||
|
||||
namespace {
|
||||
bool IsValidTenantId(std::string const& tenantId)
|
||||
{
|
||||
const std::string allowedChars = ".-";
|
||||
if (tenantId.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (auto const c : tenantId)
|
||||
{
|
||||
if (allowedChars.find(c) != std::string::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!StringExtensions::IsAlphaNumeric(c))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
AzurePipelinesCredential::AzurePipelinesCredential(
|
||||
std::string tenantId,
|
||||
@ -61,26 +37,10 @@ AzurePipelinesCredential::AzurePipelinesCredential(
|
||||
AzurePipelinesCredentialOptions const& options)
|
||||
: TokenCredential("AzurePipelinesCredential"), m_serviceConnectionId(serviceConnectionId),
|
||||
m_systemAccessToken(systemAccessToken),
|
||||
m_clientCredentialCore(tenantId, options.AuthorityHost, options.AdditionallyAllowedTenants),
|
||||
m_httpPipeline(HttpPipeline(options, "identity", PackageVersion::ToString(), {}, {}))
|
||||
{
|
||||
m_oidcRequestUrl = _detail::DefaultOptionValues::GetOidcRequestUrl();
|
||||
|
||||
bool isTenantIdValid = IsValidTenantId(tenantId);
|
||||
if (!isTenantIdValid)
|
||||
{
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Warning,
|
||||
"Invalid tenant ID provided for " + GetCredentialName()
|
||||
+ ". The tenant ID must be a non-empty string containing only alphanumeric characters, "
|
||||
"periods, or hyphens. You can locate your tenant ID by following the instructions "
|
||||
"listed here: https://learn.microsoft.com/partner-center/find-ids-and-domain-names");
|
||||
}
|
||||
if (clientId.empty())
|
||||
{
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Warning, "No client ID specified for " + GetCredentialName() + ".");
|
||||
}
|
||||
if (serviceConnectionId.empty())
|
||||
{
|
||||
IdentityLog::Write(
|
||||
@ -101,20 +61,24 @@ AzurePipelinesCredential::AzurePipelinesCredential(
|
||||
+ "' needed by " + GetCredentialName() + ". This should be set by Azure Pipelines.");
|
||||
}
|
||||
|
||||
if (isTenantIdValid && !clientId.empty() && !serviceConnectionId.empty()
|
||||
&& !systemAccessToken.empty() && !m_oidcRequestUrl.empty())
|
||||
if (TenantIdResolver::IsValidTenantId(tenantId) && !clientId.empty()
|
||||
&& !serviceConnectionId.empty() && !systemAccessToken.empty() && !m_oidcRequestUrl.empty())
|
||||
{
|
||||
m_tokenCredentialImpl = std::make_unique<TokenCredentialImpl>(options);
|
||||
m_requestBody
|
||||
= std::string(
|
||||
"grant_type=client_credentials"
|
||||
"&client_assertion_type="
|
||||
"urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" // cspell:disable-line
|
||||
"&client_id=")
|
||||
+ Url::Encode(clientId);
|
||||
ClientAssertionCredentialOptions clientAssertionCredentialOptions{};
|
||||
// Get the options from the base class (including ClientOptions).
|
||||
static_cast<Core::Credentials::TokenCredentialOptions&>(clientAssertionCredentialOptions)
|
||||
= options;
|
||||
clientAssertionCredentialOptions.AuthorityHost = options.AuthorityHost;
|
||||
clientAssertionCredentialOptions.AdditionallyAllowedTenants
|
||||
= options.AdditionallyAllowedTenants;
|
||||
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Informational, GetCredentialName() + " was created successfully.");
|
||||
std::function<std::string(Context const&)> callback
|
||||
= [this](Context const& context) { return GetAssertion(context); };
|
||||
|
||||
// ClientAssertionCredential validates the tenant ID, client ID, and assertion callback and logs
|
||||
// warning messages otherwise.
|
||||
m_clientAssertionCredentialImpl = std::make_unique<_detail::ClientAssertionCredentialImpl>(
|
||||
GetCredentialName(), tenantId, clientId, callback, clientAssertionCredentialOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -214,7 +178,7 @@ AccessToken AzurePipelinesCredential::GetToken(
|
||||
TokenRequestContext const& tokenRequestContext,
|
||||
Context const& context) const
|
||||
{
|
||||
if (!m_tokenCredentialImpl)
|
||||
if (!m_clientAssertionCredentialImpl)
|
||||
{
|
||||
auto const AuthUnavailable = GetCredentialName() + " authentication unavailable. ";
|
||||
|
||||
@ -226,41 +190,6 @@ AccessToken AzurePipelinesCredential::GetToken(
|
||||
AuthUnavailable + "Azure Pipelines environment is not set up correctly.");
|
||||
}
|
||||
|
||||
auto const tenantId = TenantIdResolver::Resolve(
|
||||
m_clientCredentialCore.GetTenantId(),
|
||||
tokenRequestContext,
|
||||
m_clientCredentialCore.GetAdditionallyAllowedTenants());
|
||||
|
||||
auto const scopesStr
|
||||
= m_clientCredentialCore.GetScopesString(tenantId, tokenRequestContext.Scopes);
|
||||
|
||||
// TokenCache::GetToken() and m_tokenCredentialImpl->GetToken() can only use the lambda
|
||||
// argument when they are being executed. They are not supposed to keep a reference to lambda
|
||||
// argument to call it later. Therefore, any capture made here will outlive the possible time
|
||||
// frame when the lambda might get called.
|
||||
return m_tokenCache.GetToken(scopesStr, tenantId, tokenRequestContext.MinimumExpiration, [&]() {
|
||||
return m_tokenCredentialImpl->GetToken(context, false, [&]() {
|
||||
auto body = m_requestBody;
|
||||
if (!scopesStr.empty())
|
||||
{
|
||||
body += "&scope=" + scopesStr;
|
||||
}
|
||||
|
||||
// Get the request url before calling GetAssertion to validate the authority host scheme.
|
||||
// This is to avoid making a request to the OIDC endpoint if the authority host scheme is
|
||||
// invalid.
|
||||
auto const requestUrl = m_clientCredentialCore.GetRequestUrl(tenantId);
|
||||
|
||||
const std::string assertion = GetAssertion(context);
|
||||
|
||||
body += "&client_assertion=" + Azure::Core::Url::Encode(assertion);
|
||||
|
||||
auto request
|
||||
= std::make_unique<TokenCredentialImpl::TokenRequest>(HttpMethod::Post, requestUrl, body);
|
||||
|
||||
request->HttpRequest.SetHeader("Host", requestUrl.GetHost());
|
||||
|
||||
return request;
|
||||
});
|
||||
});
|
||||
return m_clientAssertionCredentialImpl->GetToken(
|
||||
GetCredentialName(), tokenRequestContext, context);
|
||||
}
|
||||
|
||||
@ -3,15 +3,16 @@
|
||||
|
||||
#include "azure/identity/client_assertion_credential.hpp"
|
||||
|
||||
#include "private/client_assertion_credential_impl.hpp"
|
||||
#include "private/identity_log.hpp"
|
||||
#include "private/package_version.hpp"
|
||||
#include "private/tenant_id_resolver.hpp"
|
||||
#include "private/token_credential_impl.hpp"
|
||||
|
||||
#include <azure/core/internal/json/json.hpp>
|
||||
|
||||
using Azure::Identity::ClientAssertionCredential;
|
||||
using Azure::Identity::ClientAssertionCredentialOptions;
|
||||
using Azure::Identity::_detail::ClientAssertionCredentialImpl;
|
||||
|
||||
using Azure::Core::Context;
|
||||
using Azure::Core::Url;
|
||||
@ -24,44 +25,21 @@ using Azure::Identity::_detail::IdentityLog;
|
||||
using Azure::Identity::_detail::TenantIdResolver;
|
||||
using Azure::Identity::_detail::TokenCredentialImpl;
|
||||
|
||||
namespace {
|
||||
bool IsValidTenantId(std::string const& tenantId)
|
||||
{
|
||||
const std::string allowedChars = ".-";
|
||||
if (tenantId.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (auto const c : tenantId)
|
||||
{
|
||||
if (allowedChars.find(c) != std::string::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!StringExtensions::IsAlphaNumeric(c))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ClientAssertionCredential::ClientAssertionCredential(
|
||||
ClientAssertionCredentialImpl::ClientAssertionCredentialImpl(
|
||||
std::string const& credentialName,
|
||||
std::string tenantId,
|
||||
std::string clientId,
|
||||
std::function<std::string(Context const&)> assertionCallback,
|
||||
ClientAssertionCredentialOptions const& options)
|
||||
: TokenCredential("ClientAssertionCredential"),
|
||||
m_assertionCallback(std::move(assertionCallback)),
|
||||
: m_assertionCallback(std::move(assertionCallback)),
|
||||
m_clientCredentialCore(tenantId, options.AuthorityHost, options.AdditionallyAllowedTenants)
|
||||
{
|
||||
bool isTenantIdValid = IsValidTenantId(tenantId);
|
||||
bool isTenantIdValid = TenantIdResolver::IsValidTenantId(tenantId);
|
||||
if (!isTenantIdValid)
|
||||
{
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Warning,
|
||||
GetCredentialName()
|
||||
credentialName
|
||||
+ ": Invalid tenant ID provided. The tenant ID must be a non-empty string containing "
|
||||
"only alphanumeric characters, periods, or hyphens. You can locate your tenant ID by "
|
||||
"following the instructions listed here: "
|
||||
@ -69,14 +47,13 @@ ClientAssertionCredential::ClientAssertionCredential(
|
||||
}
|
||||
if (clientId.empty())
|
||||
{
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Warning, GetCredentialName() + ": No client ID specified.");
|
||||
IdentityLog::Write(IdentityLog::Level::Warning, credentialName + ": No client ID specified.");
|
||||
}
|
||||
if (!m_assertionCallback)
|
||||
{
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Warning,
|
||||
GetCredentialName()
|
||||
credentialName
|
||||
+ ": The assertionCallback must be a valid function that returns assertions.");
|
||||
}
|
||||
|
||||
@ -92,7 +69,7 @@ ClientAssertionCredential::ClientAssertionCredential(
|
||||
+ Url::Encode(clientId);
|
||||
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Informational, GetCredentialName() + " was created successfully.");
|
||||
IdentityLog::Level::Informational, credentialName + " was created successfully.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -101,23 +78,22 @@ ClientAssertionCredential::ClientAssertionCredential(
|
||||
// primarily needed for credentials that are part of the DefaultAzureCredential, which this
|
||||
// credential is not intended for.
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Warning, GetCredentialName() + " was not initialized correctly.");
|
||||
IdentityLog::Level::Warning, credentialName + " was not initialized correctly.");
|
||||
}
|
||||
}
|
||||
|
||||
ClientAssertionCredential::~ClientAssertionCredential() = default;
|
||||
|
||||
AccessToken ClientAssertionCredential::GetToken(
|
||||
AccessToken ClientAssertionCredentialImpl::GetToken(
|
||||
std::string const& credentialName,
|
||||
TokenRequestContext const& tokenRequestContext,
|
||||
Context const& context) const
|
||||
{
|
||||
if (!m_tokenCredentialImpl)
|
||||
{
|
||||
auto const AuthUnavailable = GetCredentialName() + " authentication unavailable. ";
|
||||
auto const AuthUnavailable = credentialName + " authentication unavailable. ";
|
||||
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Warning,
|
||||
AuthUnavailable + "See earlier " + GetCredentialName() + " log messages for details.");
|
||||
AuthUnavailable + "See earlier " + credentialName + " log messages for details.");
|
||||
|
||||
throw AuthenticationException(AuthUnavailable);
|
||||
}
|
||||
@ -160,3 +136,27 @@ AccessToken ClientAssertionCredential::GetToken(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ClientAssertionCredential::ClientAssertionCredential(
|
||||
std::string tenantId,
|
||||
std::string clientId,
|
||||
std::function<std::string(Context const&)> assertionCallback,
|
||||
ClientAssertionCredentialOptions const& options)
|
||||
: TokenCredential("ClientAssertionCredential"),
|
||||
m_impl(std::make_unique<ClientAssertionCredentialImpl>(
|
||||
GetCredentialName(),
|
||||
tenantId,
|
||||
clientId,
|
||||
assertionCallback,
|
||||
options))
|
||||
{
|
||||
}
|
||||
|
||||
ClientAssertionCredential::~ClientAssertionCredential() = default;
|
||||
|
||||
AccessToken ClientAssertionCredential::GetToken(
|
||||
TokenRequestContext const& tokenRequestContext,
|
||||
Context const& context) const
|
||||
{
|
||||
return m_impl->GetToken(GetCredentialName(), tokenRequestContext, context);
|
||||
}
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Client Assertion Credential and options.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "azure/identity/client_assertion_credential.hpp"
|
||||
#include "azure/identity/detail/client_credential_core.hpp"
|
||||
#include "azure/identity/detail/token_cache.hpp"
|
||||
#include "token_credential_impl.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Azure { namespace Identity { namespace _detail {
|
||||
class TokenCredentialImpl;
|
||||
|
||||
class ClientAssertionCredentialImpl final {
|
||||
private:
|
||||
std::function<std::string(Core::Context const&)> m_assertionCallback;
|
||||
_detail::ClientCredentialCore m_clientCredentialCore;
|
||||
std::unique_ptr<TokenCredentialImpl> m_tokenCredentialImpl;
|
||||
std::string m_requestBody;
|
||||
_detail::TokenCache m_tokenCache;
|
||||
|
||||
public:
|
||||
ClientAssertionCredentialImpl(
|
||||
std::string const& credentialName,
|
||||
std::string tenantId,
|
||||
std::string clientId,
|
||||
std::function<std::string(Core::Context const&)> assertionCallback,
|
||||
ClientAssertionCredentialOptions const& options = {});
|
||||
|
||||
Core::Credentials::AccessToken GetToken(
|
||||
std::string const& credentialName,
|
||||
Core::Credentials::TokenRequestContext const& tokenRequestContext,
|
||||
Core::Context const& context) const;
|
||||
};
|
||||
}}} // namespace Azure::Identity::_detail
|
||||
@ -25,5 +25,7 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
|
||||
// ADFS is the Active Directory Federation Service, a tenant ID that is used in Azure Stack.
|
||||
static bool IsAdfs(std::string const& tenantId);
|
||||
|
||||
static bool IsValidTenantId(std::string const& tenantId);
|
||||
};
|
||||
}}} // namespace Azure::Identity::_detail
|
||||
|
||||
@ -56,3 +56,24 @@ bool TenantIdResolver::IsAdfs(std::string const& tenantId)
|
||||
{
|
||||
return StringExtensions::LocaleInvariantCaseInsensitiveEqual(tenantId, "adfs");
|
||||
}
|
||||
|
||||
bool TenantIdResolver::IsValidTenantId(std::string const& tenantId)
|
||||
{
|
||||
const std::string allowedChars = ".-";
|
||||
if (tenantId.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (auto const c : tenantId)
|
||||
{
|
||||
if (allowedChars.find(c) != std::string::npos)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!StringExtensions::IsAlphaNumeric(c))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
#include "azure/identity/workload_identity_credential.hpp"
|
||||
|
||||
#include "private/client_assertion_credential_impl.hpp"
|
||||
#include "private/identity_log.hpp"
|
||||
#include "private/tenant_id_resolver.hpp"
|
||||
#include "private/token_credential_impl.hpp"
|
||||
|
||||
#include <azure/core/internal/environment.hpp>
|
||||
|
||||
@ -23,35 +23,32 @@ using Azure::Core::Credentials::TokenRequestContext;
|
||||
using Azure::Core::Http::HttpMethod;
|
||||
using Azure::Identity::_detail::IdentityLog;
|
||||
using Azure::Identity::_detail::TenantIdResolver;
|
||||
using Azure::Identity::_detail::TokenCredentialImpl;
|
||||
|
||||
WorkloadIdentityCredential::WorkloadIdentityCredential(
|
||||
WorkloadIdentityCredentialOptions const& options)
|
||||
: TokenCredential("WorkloadIdentityCredential"), m_clientCredentialCore(
|
||||
options.TenantId,
|
||||
options.AuthorityHost,
|
||||
options.AdditionallyAllowedTenants)
|
||||
: TokenCredential("WorkloadIdentityCredential")
|
||||
{
|
||||
std::string tenantId = options.TenantId;
|
||||
std::string clientId = options.ClientId;
|
||||
std::string authorityHost = options.AuthorityHost;
|
||||
m_tokenFilePath = options.TokenFilePath;
|
||||
|
||||
if (!tenantId.empty() && !clientId.empty() && !m_tokenFilePath.empty())
|
||||
if (TenantIdResolver::IsValidTenantId(tenantId) && !clientId.empty() && !m_tokenFilePath.empty())
|
||||
{
|
||||
m_clientCredentialCore = Azure::Identity::_detail::ClientCredentialCore(
|
||||
tenantId, authorityHost, options.AdditionallyAllowedTenants);
|
||||
m_tokenCredentialImpl = std::make_unique<TokenCredentialImpl>(options);
|
||||
m_requestBody
|
||||
= std::string(
|
||||
"grant_type=client_credentials"
|
||||
"&client_assertion_type="
|
||||
"urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" // cspell:disable-line
|
||||
"&client_id=")
|
||||
+ Url::Encode(clientId);
|
||||
ClientAssertionCredentialOptions clientAssertionCredentialOptions{};
|
||||
// Get the options from the base class (including ClientOptions).
|
||||
static_cast<Core::Credentials::TokenCredentialOptions&>(clientAssertionCredentialOptions)
|
||||
= options;
|
||||
clientAssertionCredentialOptions.AuthorityHost = options.AuthorityHost;
|
||||
clientAssertionCredentialOptions.AdditionallyAllowedTenants
|
||||
= options.AdditionallyAllowedTenants;
|
||||
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Informational, GetCredentialName() + " was created successfully.");
|
||||
std::function<std::string(Context const&)> callback
|
||||
= [this](Context const& context) { return GetAssertion(context); };
|
||||
|
||||
// ClientAssertionCredential validates the tenant ID, client ID, and assertion callback and logs
|
||||
// warning messages otherwise.
|
||||
m_clientAssertionCredentialImpl = std::make_unique<_detail::ClientAssertionCredentialImpl>(
|
||||
GetCredentialName(), tenantId, clientId, callback, clientAssertionCredentialOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -64,30 +61,26 @@ WorkloadIdentityCredential::WorkloadIdentityCredential(
|
||||
|
||||
WorkloadIdentityCredential::WorkloadIdentityCredential(
|
||||
Core::Credentials::TokenCredentialOptions const& options)
|
||||
: TokenCredential("WorkloadIdentityCredential"),
|
||||
m_clientCredentialCore("", "", std::vector<std::string>())
|
||||
: TokenCredential("WorkloadIdentityCredential")
|
||||
{
|
||||
std::string const tenantId = _detail::DefaultOptionValues::GetTenantId();
|
||||
std::string const clientId = _detail::DefaultOptionValues::GetClientId();
|
||||
m_tokenFilePath = _detail::DefaultOptionValues::GetFederatedTokenFile();
|
||||
|
||||
if (!tenantId.empty() && !clientId.empty() && !m_tokenFilePath.empty())
|
||||
if (TenantIdResolver::IsValidTenantId(tenantId) && !clientId.empty() && !m_tokenFilePath.empty())
|
||||
{
|
||||
std::string const authorityHost = _detail::DefaultOptionValues::GetAuthorityHost();
|
||||
ClientAssertionCredentialOptions clientAssertionCredentialOptions{};
|
||||
// Get the options from the base class (including ClientOptions).
|
||||
static_cast<Core::Credentials::TokenCredentialOptions&>(clientAssertionCredentialOptions)
|
||||
= options;
|
||||
|
||||
m_clientCredentialCore = Azure::Identity::_detail::ClientCredentialCore(
|
||||
tenantId, authorityHost, std::vector<std::string>());
|
||||
m_tokenCredentialImpl = std::make_unique<TokenCredentialImpl>(options);
|
||||
m_requestBody
|
||||
= std::string(
|
||||
"grant_type=client_credentials"
|
||||
"&client_assertion_type="
|
||||
"urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" // cspell:disable-line
|
||||
"&client_id=")
|
||||
+ Url::Encode(clientId);
|
||||
std::function<std::string(Context const&)> callback
|
||||
= [this](Context const& context) { return GetAssertion(context); };
|
||||
|
||||
IdentityLog::Write(
|
||||
IdentityLog::Level::Informational, GetCredentialName() + " was created successfully.");
|
||||
// ClientAssertionCredential validates the tenant ID, client ID, and assertion callback and logs
|
||||
// warning messages otherwise.
|
||||
m_clientAssertionCredentialImpl = std::make_unique<_detail::ClientAssertionCredentialImpl>(
|
||||
GetCredentialName(), tenantId, clientId, callback, clientAssertionCredentialOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -100,11 +93,21 @@ WorkloadIdentityCredential::WorkloadIdentityCredential(
|
||||
|
||||
WorkloadIdentityCredential::~WorkloadIdentityCredential() = default;
|
||||
|
||||
std::string WorkloadIdentityCredential::GetAssertion(Context const&) const
|
||||
{
|
||||
// Read the specified file's content, which is expected to be a Kubernetes service account
|
||||
// token. Kubernetes is responsible for updating the file as service account tokens expire.
|
||||
std::ifstream azureFederatedTokenFile(m_tokenFilePath);
|
||||
std::string assertion(
|
||||
(std::istreambuf_iterator<char>(azureFederatedTokenFile)), std::istreambuf_iterator<char>());
|
||||
return assertion;
|
||||
}
|
||||
|
||||
AccessToken WorkloadIdentityCredential::GetToken(
|
||||
TokenRequestContext const& tokenRequestContext,
|
||||
Context const& context) const
|
||||
{
|
||||
if (!m_tokenCredentialImpl)
|
||||
if (!m_clientAssertionCredentialImpl)
|
||||
{
|
||||
auto const AuthUnavailable = GetCredentialName() + " authentication unavailable. ";
|
||||
|
||||
@ -116,43 +119,6 @@ AccessToken WorkloadIdentityCredential::GetToken(
|
||||
AuthUnavailable + "Azure Kubernetes environment is not set up correctly.");
|
||||
}
|
||||
|
||||
auto const tenantId = TenantIdResolver::Resolve(
|
||||
m_clientCredentialCore.GetTenantId(),
|
||||
tokenRequestContext,
|
||||
m_clientCredentialCore.GetAdditionallyAllowedTenants());
|
||||
|
||||
auto const scopesStr
|
||||
= m_clientCredentialCore.GetScopesString(tenantId, tokenRequestContext.Scopes);
|
||||
|
||||
// TokenCache::GetToken() and m_tokenCredentialImpl->GetToken() can only use the lambda argument
|
||||
// when they are being executed. They are not supposed to keep a reference to lambda argument to
|
||||
// call it later. Therefore, any capture made here will outlive the possible time frame when the
|
||||
// lambda might get called.
|
||||
return m_tokenCache.GetToken(scopesStr, tenantId, tokenRequestContext.MinimumExpiration, [&]() {
|
||||
return m_tokenCredentialImpl->GetToken(context, false, [&]() {
|
||||
auto body = m_requestBody;
|
||||
if (!scopesStr.empty())
|
||||
{
|
||||
body += "&scope=" + scopesStr;
|
||||
}
|
||||
|
||||
auto const requestUrl = m_clientCredentialCore.GetRequestUrl(tenantId);
|
||||
|
||||
// Read the specified file's content, which is expected to be a Kubernetes service account
|
||||
// token. Kubernetes is responsible for updating the file as service account tokens expire.
|
||||
std::ifstream azureFederatedTokenFile(m_tokenFilePath);
|
||||
std::string assertion(
|
||||
(std::istreambuf_iterator<char>(azureFederatedTokenFile)),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
body += "&client_assertion=" + Azure::Core::Url::Encode(assertion);
|
||||
|
||||
auto request
|
||||
= std::make_unique<TokenCredentialImpl::TokenRequest>(HttpMethod::Post, requestUrl, body);
|
||||
|
||||
request->HttpRequest.SetHeader("Host", requestUrl.GetHost());
|
||||
|
||||
return request;
|
||||
});
|
||||
});
|
||||
return m_clientAssertionCredentialImpl->GetToken(
|
||||
GetCredentialName(), tokenRequestContext, context);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user