Revert "Pull beta-only changes out of identity for the August GA release (#5863)" (#5889)

This reverts commit 60dff1dac1.
This commit is contained in:
Ahson Khan 2024-08-09 11:35:24 -07:00 committed by GitHub
parent 59aa20e3fa
commit 4f77d817b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 627 additions and 11 deletions

1
.vscode/cspell.json vendored
View File

@ -132,6 +132,7 @@
"hlocal",
"HLOCAL",
"HRESULT",
"imds",
"Imds",
"IMDS",
"immutability",

View File

@ -19,6 +19,29 @@ namespace Azure { namespace Identity {
class ManagedIdentitySource;
}
// This will move to Azure::Core.
/**
* @brief An Azure Resource Manager resource identifier.
*/
class ResourceIdentifier final {
std::string m_resourceId;
public:
/**
* @brief Constructs a resource identifier.
*
* @param resourceId The id string to create the ResourceIdentifier from.
*/
explicit ResourceIdentifier(std::string const& resourceId) : m_resourceId(resourceId){};
/**
* @brief The string representation of this resource identifier.
*
* @return The resource identifier string.
*/
std::string ToString() const { return m_resourceId; }
};
/**
* @brief 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
@ -48,6 +71,17 @@ namespace Azure { namespace Identity {
Azure::Core::Credentials::TokenCredentialOptions const& options
= Azure::Core::Credentials::TokenCredentialOptions());
/**
* @brief Constructs an instance of ManagedIdentityCredential capable of authenticating a
* resource with a user-assigned managed identity.
*
* @param resourceId The resource ID to authenticate for a user-assigned managed identity.
* @param options Options for token retrieval.
*/
explicit ManagedIdentityCredential(
ResourceIdentifier const& resourceId,
Azure::Core::Credentials::TokenCredentialOptions const& options = {});
/**
* @brief Constructs a Managed Identity Credential.
*

View File

@ -11,6 +11,7 @@ namespace {
std::unique_ptr<_detail::ManagedIdentitySource> CreateManagedIdentitySource(
std::string const& credentialName,
std::string const& clientId,
std::string const& resourceId,
Azure::Core::Credentials::TokenCredentialOptions const& options)
{
using namespace Azure::Core::Credentials;
@ -18,6 +19,7 @@ std::unique_ptr<_detail::ManagedIdentitySource> CreateManagedIdentitySource(
static std::unique_ptr<ManagedIdentitySource> (*managedIdentitySourceCreate[])(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
TokenCredentialOptions const& options)
= {AppServiceV2019ManagedIdentitySource::Create,
AppServiceV2017ManagedIdentitySource::Create,
@ -29,7 +31,7 @@ std::unique_ptr<_detail::ManagedIdentitySource> CreateManagedIdentitySource(
// For that reason, it is not possible to cover that execution branch in tests.
for (auto create : managedIdentitySourceCreate)
{
if (auto source = create(credentialName, clientId, options))
if (auto source = create(credentialName, clientId, resourceId, options))
{
return source;
}
@ -47,7 +49,16 @@ ManagedIdentityCredential::ManagedIdentityCredential(
Azure::Core::Credentials::TokenCredentialOptions const& options)
: TokenCredential("ManagedIdentityCredential")
{
m_managedIdentitySource = CreateManagedIdentitySource(GetCredentialName(), clientId, options);
m_managedIdentitySource = CreateManagedIdentitySource(GetCredentialName(), clientId, {}, options);
}
ManagedIdentityCredential::ManagedIdentityCredential(
ResourceIdentifier const& resourceId,
Azure::Core::Credentials::TokenCredentialOptions const& options)
: TokenCredential("ManagedIdentityCredential")
{
m_managedIdentitySource
= CreateManagedIdentitySource(GetCredentialName(), {}, resourceId.ToString(), options);
}
ManagedIdentityCredential::ManagedIdentityCredential(

View File

@ -140,6 +140,7 @@ template <typename T>
std::unique_ptr<ManagedIdentitySource> AppServiceManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Azure::Core::Credentials::TokenCredentialOptions const& options,
char const* endpointVarName,
char const* secretVarName,
@ -154,6 +155,7 @@ std::unique_ptr<ManagedIdentitySource> AppServiceManagedIdentitySource::Create(
{
return std::unique_ptr<ManagedIdentitySource>(new T(
clientId,
resourceId,
options,
ParseEndpointUrl(credName, msiEndpoint, endpointVarName, credSource),
msiSecret));
@ -165,6 +167,7 @@ std::unique_ptr<ManagedIdentitySource> AppServiceManagedIdentitySource::Create(
AppServiceManagedIdentitySource::AppServiceManagedIdentitySource(
std::string const& clientId,
std::string const& resourceId,
Azure::Core::Credentials::TokenCredentialOptions const& options,
Azure::Core::Url endpointUrl,
std::string const& secret,
@ -180,10 +183,17 @@ AppServiceManagedIdentitySource::AppServiceManagedIdentitySource(
url.AppendQueryParameter("api-version", apiVersion);
// Only one of clientId or resourceId will be set to a non-empty value.
// AppService uses mi_res_id, and not msi_res_id:
// https://learn.microsoft.com/azure/app-service/overview-managed-identity?tabs=portal%2Chttp#rest-endpoint-reference
if (!clientId.empty())
{
url.AppendQueryParameter(clientIdHeaderName, clientId);
}
else if (!resourceId.empty())
{
url.AppendQueryParameter("mi_res_id", resourceId);
}
}
m_request.SetHeader(secretHeaderName, secret);
@ -223,24 +233,28 @@ Azure::Core::Credentials::AccessToken AppServiceManagedIdentitySource::GetToken(
std::unique_ptr<ManagedIdentitySource> AppServiceV2017ManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options)
{
return AppServiceManagedIdentitySource::Create<AppServiceV2017ManagedIdentitySource>(
credName, clientId, options, "MSI_ENDPOINT", "MSI_SECRET", "2017");
credName, clientId, resourceId, options, "MSI_ENDPOINT", "MSI_SECRET", "2017");
}
std::unique_ptr<ManagedIdentitySource> AppServiceV2019ManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options)
{
return AppServiceManagedIdentitySource::Create<AppServiceV2019ManagedIdentitySource>(
credName, clientId, options, "IDENTITY_ENDPOINT", "IDENTITY_HEADER", "2019");
credName, clientId, resourceId, options, "IDENTITY_ENDPOINT", "IDENTITY_HEADER", "2019");
}
// Cloud Shell doesn't support user-assigned managed identities
std::unique_ptr<ManagedIdentitySource> CloudShellManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
std::string const&,
Azure::Core::Credentials::TokenCredentialOptions const& options)
{
constexpr auto EndpointVarName = "MSI_ENDPOINT";
@ -315,6 +329,7 @@ Azure::Core::Credentials::AccessToken CloudShellManagedIdentitySource::GetToken(
std::unique_ptr<ManagedIdentitySource> AzureArcManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Azure::Core::Credentials::TokenCredentialOptions const& options)
{
using Azure::Core::Credentials::AuthenticationException;
@ -330,11 +345,11 @@ std::unique_ptr<ManagedIdentitySource> AzureArcManagedIdentitySource::Create(
return nullptr;
}
if (!clientId.empty())
if (!clientId.empty() || !resourceId.empty())
{
throw AuthenticationException(
"User assigned identity is not supported by the Azure Arc Managed Identity Endpoint. "
"To authenticate with the system assigned identity, omit the client ID "
"To authenticate with the system assigned identity, omit the client or resource ID "
"when constructing the ManagedIdentityCredential.");
}
@ -348,7 +363,6 @@ AzureArcManagedIdentitySource::AzureArcManagedIdentitySource(
: ManagedIdentitySource(std::string(), endpointUrl.GetHost(), options),
m_url(std::move(endpointUrl))
{
m_url.AppendQueryParameter("api-version", "2019-11-01");
}
@ -442,6 +456,7 @@ Azure::Core::Credentials::AccessToken AzureArcManagedIdentitySource::GetToken(
std::unique_ptr<ManagedIdentitySource> ImdsManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Azure::Core::Credentials::TokenCredentialOptions const& options)
{
IdentityLog::Write(
@ -449,16 +464,28 @@ std::unique_ptr<ManagedIdentitySource> ImdsManagedIdentitySource::Create(
credName + " will be created" + WithSourceMessage("Azure Instance Metadata Service")
+ ".\nSuccessful creation does not guarantee further successful token retrieval.");
return std::unique_ptr<ManagedIdentitySource>(new ImdsManagedIdentitySource(clientId, options));
std::string imdsHost = "http://169.254.169.254";
std::string customImdsHost = Environment::GetVariable("AZURE_IMDS_CUSTOM_AUTHORITY_HOST");
if (!customImdsHost.empty())
{
IdentityLog::Write(
IdentityLog::Level::Informational, "Custom IMDS host is set to: " + customImdsHost);
imdsHost = customImdsHost;
}
Azure::Core::Url imdsUrl(imdsHost);
imdsUrl.AppendPath("/metadata/identity/oauth2/token");
return std::unique_ptr<ManagedIdentitySource>(
new ImdsManagedIdentitySource(clientId, resourceId, imdsUrl, options));
}
ImdsManagedIdentitySource::ImdsManagedIdentitySource(
std::string const& clientId,
std::string const& resourceId,
Azure::Core::Url const& imdsUrl,
Azure::Core::Credentials::TokenCredentialOptions const& options)
: ManagedIdentitySource(clientId, std::string(), options),
m_request(
Azure::Core::Http::HttpMethod::Get,
Azure::Core::Url("http://169.254.169.254/metadata/identity/oauth2/token"))
m_request(Azure::Core::Http::HttpMethod::Get, imdsUrl)
{
{
using Azure::Core::Url;
@ -466,10 +493,17 @@ ImdsManagedIdentitySource::ImdsManagedIdentitySource(
url.AppendQueryParameter("api-version", "2018-02-01");
// Only one of clientId or resourceId will be set to a non-empty value.
// IMDS uses msi_res_id, and not mi_res_id:
// https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
if (!clientId.empty())
{
url.AppendQueryParameter("client_id", clientId);
}
else if (!resourceId.empty())
{
url.AppendQueryParameter("msi_res_id", resourceId);
}
}
m_request.SetHeader("Metadata", "true");

View File

@ -54,6 +54,7 @@ namespace Azure { namespace Identity { namespace _detail {
protected:
explicit AppServiceManagedIdentitySource(
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options,
Core::Url endpointUrl,
std::string const& secret,
@ -65,6 +66,7 @@ namespace Azure { namespace Identity { namespace _detail {
static std::unique_ptr<ManagedIdentitySource> Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options,
char const* endpointVarName,
char const* secretVarName,
@ -82,11 +84,13 @@ namespace Azure { namespace Identity { namespace _detail {
private:
explicit AppServiceV2017ManagedIdentitySource(
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options,
Core::Url endpointUrl,
std::string const& secret)
: AppServiceManagedIdentitySource(
clientId,
resourceId,
options,
endpointUrl,
secret,
@ -100,6 +104,7 @@ namespace Azure { namespace Identity { namespace _detail {
static std::unique_ptr<ManagedIdentitySource> Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options);
};
@ -109,11 +114,13 @@ namespace Azure { namespace Identity { namespace _detail {
private:
explicit AppServiceV2019ManagedIdentitySource(
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options,
Core::Url endpointUrl,
std::string const& secret)
: AppServiceManagedIdentitySource(
clientId,
resourceId,
options,
endpointUrl,
secret,
@ -127,6 +134,7 @@ namespace Azure { namespace Identity { namespace _detail {
static std::unique_ptr<ManagedIdentitySource> Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options);
};
@ -144,6 +152,7 @@ namespace Azure { namespace Identity { namespace _detail {
static std::unique_ptr<ManagedIdentitySource> Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options);
Core::Credentials::AccessToken GetToken(
@ -163,6 +172,7 @@ namespace Azure { namespace Identity { namespace _detail {
static std::unique_ptr<ManagedIdentitySource> Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options);
Core::Credentials::AccessToken GetToken(
@ -176,12 +186,15 @@ namespace Azure { namespace Identity { namespace _detail {
explicit ImdsManagedIdentitySource(
std::string const& clientId,
std::string const& resourceId,
Core::Url const& imdsUrl,
Core::Credentials::TokenCredentialOptions const& options);
public:
static std::unique_ptr<ManagedIdentitySource> Create(
std::string const& credName,
std::string const& clientId,
std::string const& resourceId,
Core::Credentials::TokenCredentialOptions const& options);
Core::Credentials::AccessToken GetToken(

View File

@ -28,6 +28,7 @@ add_executable (
environment_credential_test.cpp
macro_guard_test.cpp
managed_identity_credential_test.cpp
resource_identifier_test.cpp
simplified_header_test.cpp
tenant_id_resolver_test.cpp
token_cache_test.cpp

View File

@ -30,6 +30,7 @@ using Azure::Core::Credentials::TokenCredentialOptions;
using Azure::Core::Http::HttpMethod;
using Azure::Core::Http::HttpStatusCode;
using Azure::Identity::ManagedIdentityCredential;
using Azure::Identity::ResourceIdentifier;
using Azure::Identity::Test::_detail::CredentialTestHelper;
TEST(ManagedIdentityCredential, GetCredentialName)
@ -240,6 +241,96 @@ TEST(ManagedIdentityCredential, AppServiceV2019ClientId)
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s);
}
TEST(ManagedIdentityCredential, AppServiceV2019ResourceId)
{
auto const actual = CredentialTestHelper::SimulateTokenRequest(
[](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
CredentialTestHelper::EnvironmentOverride const env({
{"MSI_ENDPOINT", "https://microsoft.com/"},
{"MSI_SECRET", "CLIENTSECRET1"},
{"IDENTITY_ENDPOINT", "https://visualstudio.com/"},
{"IMDS_ENDPOINT", "https://xbox.com/"},
{"IDENTITY_HEADER", "CLIENTSECRET2"},
{"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"},
});
return std::make_unique<ManagedIdentityCredential>(
ResourceIdentifier("abcdef01-2345-6789-9876-543210fedcba"), options);
},
{{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}},
std::vector<std::string>{
"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}",
"{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN2\"}",
"{\"expires_in\":9999, \"access_token\":\"ACCESSTOKEN3\"}"});
EXPECT_EQ(actual.Requests.size(), 3U);
EXPECT_EQ(actual.Responses.size(), 3U);
auto const& request0 = actual.Requests.at(0);
auto const& request1 = actual.Requests.at(1);
auto const& request2 = actual.Requests.at(2);
auto const& response0 = actual.Responses.at(0);
auto const& response1 = actual.Responses.at(1);
auto const& response2 = actual.Responses.at(2);
EXPECT_EQ(request0.HttpMethod, HttpMethod::Get);
EXPECT_EQ(request1.HttpMethod, HttpMethod::Get);
EXPECT_EQ(request2.HttpMethod, HttpMethod::Get);
EXPECT_EQ(
request0.AbsoluteUrl,
"https://visualstudio.com"
"?api-version=2019-08-01"
"&mi_res_id=abcdef01-2345-6789-9876-543210fedcba"
"&resource=https%3A%2F%2Fazure.com"); // cspell:disable-line
EXPECT_EQ(
request1.AbsoluteUrl,
"https://visualstudio.com"
"?api-version=2019-08-01"
"&mi_res_id=abcdef01-2345-6789-9876-543210fedcba"
"&resource=https%3A%2F%2Foutlook.com"); // cspell:disable-line
EXPECT_EQ(
request2.AbsoluteUrl,
"https://visualstudio.com"
"?api-version=2019-08-01"
"&mi_res_id=abcdef01-2345-6789-9876-543210fedcba");
EXPECT_TRUE(request0.Body.empty());
EXPECT_TRUE(request1.Body.empty());
EXPECT_TRUE(request2.Body.empty());
{
EXPECT_NE(request0.Headers.find("X-IDENTITY-HEADER"), request0.Headers.end());
EXPECT_EQ(request0.Headers.at("X-IDENTITY-HEADER"), "CLIENTSECRET2");
EXPECT_NE(request1.Headers.find("X-IDENTITY-HEADER"), request1.Headers.end());
EXPECT_EQ(request1.Headers.at("X-IDENTITY-HEADER"), "CLIENTSECRET2");
EXPECT_NE(request2.Headers.find("X-IDENTITY-HEADER"), request2.Headers.end());
EXPECT_EQ(request2.Headers.at("X-IDENTITY-HEADER"), "CLIENTSECRET2");
}
EXPECT_EQ(response0.AccessToken.Token, "ACCESSTOKEN1");
EXPECT_EQ(response1.AccessToken.Token, "ACCESSTOKEN2");
EXPECT_EQ(response2.AccessToken.Token, "ACCESSTOKEN3");
using namespace std::chrono_literals;
EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s);
EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s);
EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s);
EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s);
EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s);
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s);
}
TEST(ManagedIdentityCredential, AppServiceV2019InvalidUrl)
{
using Azure::Core::Diagnostics::Logger;
@ -522,6 +613,96 @@ TEST(ManagedIdentityCredential, AppServiceV2017ClientId)
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s);
}
TEST(ManagedIdentityCredential, AppServiceV2017ResourceId)
{
auto const actual = CredentialTestHelper::SimulateTokenRequest(
[](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
CredentialTestHelper::EnvironmentOverride const env({
{"MSI_ENDPOINT", "https://microsoft.com/"},
{"MSI_SECRET", "CLIENTSECRET1"},
{"IDENTITY_ENDPOINT", ""},
{"IMDS_ENDPOINT", "https://xbox.com/"},
{"IDENTITY_HEADER", "CLIENTSECRET2"},
{"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"},
});
return std::make_unique<ManagedIdentityCredential>(
ResourceIdentifier("abcdef01-2345-6789-9876-543210fedcba"), options);
},
{{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}},
std::vector<std::string>{
"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}",
"{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN2\"}",
"{\"expires_in\":9999, \"access_token\":\"ACCESSTOKEN3\"}"});
EXPECT_EQ(actual.Requests.size(), 3U);
EXPECT_EQ(actual.Responses.size(), 3U);
auto const& request0 = actual.Requests.at(0);
auto const& request1 = actual.Requests.at(1);
auto const& request2 = actual.Requests.at(2);
auto const& response0 = actual.Responses.at(0);
auto const& response1 = actual.Responses.at(1);
auto const& response2 = actual.Responses.at(2);
EXPECT_EQ(request0.HttpMethod, HttpMethod::Get);
EXPECT_EQ(request1.HttpMethod, HttpMethod::Get);
EXPECT_EQ(request2.HttpMethod, HttpMethod::Get);
EXPECT_EQ(
request0.AbsoluteUrl,
"https://microsoft.com"
"?api-version=2017-09-01"
"&mi_res_id=abcdef01-2345-6789-9876-543210fedcba"
"&resource=https%3A%2F%2Fazure.com"); // cspell:disable-line
EXPECT_EQ(
request1.AbsoluteUrl,
"https://microsoft.com"
"?api-version=2017-09-01"
"&mi_res_id=abcdef01-2345-6789-9876-543210fedcba"
"&resource=https%3A%2F%2Foutlook.com"); // cspell:disable-line
EXPECT_EQ(
request2.AbsoluteUrl,
"https://microsoft.com"
"?api-version=2017-09-01"
"&mi_res_id=abcdef01-2345-6789-9876-543210fedcba");
EXPECT_TRUE(request0.Body.empty());
EXPECT_TRUE(request1.Body.empty());
EXPECT_TRUE(request2.Body.empty());
{
EXPECT_NE(request0.Headers.find("secret"), request0.Headers.end());
EXPECT_EQ(request0.Headers.at("secret"), "CLIENTSECRET1");
EXPECT_NE(request1.Headers.find("secret"), request1.Headers.end());
EXPECT_EQ(request1.Headers.at("secret"), "CLIENTSECRET1");
EXPECT_NE(request2.Headers.find("secret"), request2.Headers.end());
EXPECT_EQ(request2.Headers.at("secret"), "CLIENTSECRET1");
}
EXPECT_EQ(response0.AccessToken.Token, "ACCESSTOKEN1");
EXPECT_EQ(response1.AccessToken.Token, "ACCESSTOKEN2");
EXPECT_EQ(response2.AccessToken.Token, "ACCESSTOKEN3");
using namespace std::chrono_literals;
EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s);
EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s);
EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s);
EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s);
EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s);
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s);
}
TEST(ManagedIdentityCredential, AppServiceV2017InvalidUrl)
{
using Azure::Core::Credentials::AccessToken;
@ -770,6 +951,84 @@ TEST(ManagedIdentityCredential, CloudShellClientId)
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s);
}
TEST(ManagedIdentityCredential, CloudShellResourceId)
{
auto const actual = CredentialTestHelper::SimulateTokenRequest(
[](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
CredentialTestHelper::EnvironmentOverride const env({
{"MSI_ENDPOINT", "https://microsoft.com/"},
{"MSI_SECRET", ""},
{"IDENTITY_ENDPOINT", "https://visualstudio.com/"},
{"IMDS_ENDPOINT", "https://xbox.com/"},
{"IDENTITY_HEADER", ""},
{"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"},
});
return std::make_unique<ManagedIdentityCredential>(
ResourceIdentifier("abcdef01-2345-6789-9876-543210fedcba"), options);
},
{{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}},
std::vector<std::string>{
"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}",
"{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN2\"}",
"{\"expires_in\":9999, \"access_token\":\"ACCESSTOKEN3\"}"});
EXPECT_EQ(actual.Requests.size(), 3U);
EXPECT_EQ(actual.Responses.size(), 3U);
auto const& request0 = actual.Requests.at(0);
auto const& request1 = actual.Requests.at(1);
auto const& request2 = actual.Requests.at(2);
auto const& response0 = actual.Responses.at(0);
auto const& response1 = actual.Responses.at(1);
auto const& response2 = actual.Responses.at(2);
EXPECT_EQ(request0.HttpMethod, HttpMethod::Post);
EXPECT_EQ(request1.HttpMethod, HttpMethod::Post);
EXPECT_EQ(request2.HttpMethod, HttpMethod::Post);
EXPECT_EQ(request0.AbsoluteUrl, "https://microsoft.com");
EXPECT_EQ(request1.AbsoluteUrl, "https://microsoft.com");
EXPECT_EQ(request2.AbsoluteUrl, "https://microsoft.com");
EXPECT_EQ(request0.Body,
"resource=https%3A%2F%2Fazure.com"); // cspell:disable-line
EXPECT_EQ(request1.Body,
"resource=https%3A%2F%2Foutlook.com"); // cspell:disable-line
EXPECT_EQ(request2.Body, "");
{
EXPECT_NE(request0.Headers.find("Metadata"), request0.Headers.end());
EXPECT_EQ(request0.Headers.at("Metadata"), "true");
EXPECT_NE(request1.Headers.find("Metadata"), request1.Headers.end());
EXPECT_EQ(request1.Headers.at("Metadata"), "true");
EXPECT_NE(request2.Headers.find("Metadata"), request2.Headers.end());
EXPECT_EQ(request2.Headers.at("Metadata"), "true");
}
EXPECT_EQ(response0.AccessToken.Token, "ACCESSTOKEN1");
EXPECT_EQ(response1.AccessToken.Token, "ACCESSTOKEN2");
EXPECT_EQ(response2.AccessToken.Token, "ACCESSTOKEN3");
using namespace std::chrono_literals;
EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s);
EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s);
EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s);
EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s);
EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s);
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s);
}
TEST(ManagedIdentityCredential, CloudShellInvalidUrl)
{
using Azure::Core::Credentials::AccessToken;
@ -1082,6 +1341,37 @@ TEST(ManagedIdentityCredential, AzureArcClientId)
{"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}));
}
TEST(ManagedIdentityCredential, AzureArcResourceId)
{
using Azure::Core::Credentials::AccessToken;
using Azure::Core::Credentials::AuthenticationException;
static_cast<void>(CredentialTestHelper::SimulateTokenRequest(
[](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
CredentialTestHelper::EnvironmentOverride const env({
{"MSI_ENDPOINT", ""},
{"MSI_SECRET", ""},
{"IDENTITY_ENDPOINT", "https://visualstudio.com/"},
{"IMDS_ENDPOINT", "https://xbox.com/"},
{"IDENTITY_HEADER", ""},
{"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"},
});
std::unique_ptr<ManagedIdentityCredential const> azureArcManagedIdentityCredential;
EXPECT_THROW(
azureArcManagedIdentityCredential = std::make_unique<ManagedIdentityCredential>(
ResourceIdentifier("abcdef01-2345-6789-9876-543210fedcba"), options),
AuthenticationException);
return azureArcManagedIdentityCredential;
},
{},
{"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}));
}
TEST(ManagedIdentityCredential, AzureArcAuthHeaderMissing)
{
using Azure::Core::Credentials::AccessToken;
@ -1755,6 +2045,96 @@ TEST(ManagedIdentityCredential, ImdsClientId)
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s);
}
TEST(ManagedIdentityCredential, ImdsResourceId)
{
auto const actual = CredentialTestHelper::SimulateTokenRequest(
[](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
CredentialTestHelper::EnvironmentOverride const env({
{"MSI_ENDPOINT", ""},
{"MSI_SECRET", ""},
{"IDENTITY_ENDPOINT", ""},
{"IMDS_ENDPOINT", ""},
{"IDENTITY_HEADER", ""},
{"IDENTITY_SERVER_THUMBPRINT", ""},
});
return std::make_unique<ManagedIdentityCredential>(
ResourceIdentifier("abcdef01-2345-6789-9876-543210fedcba"), options);
},
{{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}},
std::vector<std::string>{
"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}",
"{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN2\"}",
"{\"expires_in\":9999, \"access_token\":\"ACCESSTOKEN3\"}"});
EXPECT_EQ(actual.Requests.size(), 3U);
EXPECT_EQ(actual.Responses.size(), 3U);
auto const& request0 = actual.Requests.at(0);
auto const& request1 = actual.Requests.at(1);
auto const& request2 = actual.Requests.at(2);
auto const& response0 = actual.Responses.at(0);
auto const& response1 = actual.Responses.at(1);
auto const& response2 = actual.Responses.at(2);
EXPECT_EQ(request0.HttpMethod, HttpMethod::Get);
EXPECT_EQ(request1.HttpMethod, HttpMethod::Get);
EXPECT_EQ(request2.HttpMethod, HttpMethod::Get);
EXPECT_EQ(
request0.AbsoluteUrl,
"http://169.254.169.254/metadata/identity/oauth2/token"
"?api-version=2018-02-01"
"&msi_res_id=abcdef01-2345-6789-9876-543210fedcba"
"&resource=https%3A%2F%2Fazure.com"); // cspell:disable-line
EXPECT_EQ(
request1.AbsoluteUrl,
"http://169.254.169.254/metadata/identity/oauth2/token"
"?api-version=2018-02-01"
"&msi_res_id=abcdef01-2345-6789-9876-543210fedcba"
"&resource=https%3A%2F%2Foutlook.com"); // cspell:disable-line
EXPECT_EQ(
request2.AbsoluteUrl,
"http://169.254.169.254/metadata/identity/oauth2/token"
"?api-version=2018-02-01"
"&msi_res_id=abcdef01-2345-6789-9876-543210fedcba");
EXPECT_TRUE(request0.Body.empty());
EXPECT_TRUE(request1.Body.empty());
EXPECT_TRUE(request2.Body.empty());
{
EXPECT_NE(request0.Headers.find("Metadata"), request0.Headers.end());
EXPECT_EQ(request0.Headers.at("Metadata"), "true");
EXPECT_NE(request1.Headers.find("Metadata"), request1.Headers.end());
EXPECT_EQ(request1.Headers.at("Metadata"), "true");
EXPECT_NE(request2.Headers.find("Metadata"), request2.Headers.end());
EXPECT_EQ(request2.Headers.at("Metadata"), "true");
}
EXPECT_EQ(response0.AccessToken.Token, "ACCESSTOKEN1");
EXPECT_EQ(response1.AccessToken.Token, "ACCESSTOKEN2");
EXPECT_EQ(response2.AccessToken.Token, "ACCESSTOKEN3");
using namespace std::chrono_literals;
EXPECT_GE(response0.AccessToken.ExpiresOn, response0.EarliestExpiration + 3600s);
EXPECT_LE(response0.AccessToken.ExpiresOn, response0.LatestExpiration + 3600s);
EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s);
EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s);
EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s);
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s);
}
TEST(ManagedIdentityCredential, ImdsCreation)
{
auto const actual1 = CredentialTestHelper::SimulateTokenRequest(
@ -1847,3 +2227,127 @@ TEST(ManagedIdentityCredential, ImdsCreation)
EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 3600s);
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 3600s);
}
TEST(ManagedIdentityCredential, ImdsCustomHost)
{
auto const actual1 = CredentialTestHelper::SimulateTokenRequest(
[](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
CredentialTestHelper::EnvironmentOverride const env({
{"MSI_ENDPOINT", ""},
{"MSI_SECRET", ""},
{"IDENTITY_ENDPOINT", "https://visualstudio.com/"},
{"IMDS_ENDPOINT", ""},
{"IDENTITY_HEADER", ""},
{"IDENTITY_SERVER_THUMBPRINT", ""},
{"AZURE_IMDS_CUSTOM_AUTHORITY_HOST", ""},
});
return std::make_unique<ManagedIdentityCredential>(
"fedcba98-7654-3210-0123-456789abcdef", options);
},
{{"https://azure.com/.default"}},
{"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"});
auto const actual2 = CredentialTestHelper::SimulateTokenRequest(
[](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
CredentialTestHelper::EnvironmentOverride const env({
{"MSI_ENDPOINT", ""},
{"MSI_SECRET", ""},
{"IDENTITY_ENDPOINT", ""},
{"IMDS_ENDPOINT", "https://xbox.com/"},
{"IDENTITY_HEADER", ""},
{"IDENTITY_SERVER_THUMBPRINT", ""},
{"AZURE_IMDS_CUSTOM_AUTHORITY_HOST", "https://custom.imds.endpoint/"},
});
return std::make_unique<ManagedIdentityCredential>(
"01234567-89ab-cdef-fedc-ba9876543210", options);
},
{{"https://outlook.com/.default"}},
{"{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN2\"}"});
auto const actual3 = CredentialTestHelper::SimulateTokenRequest(
[](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
CredentialTestHelper::EnvironmentOverride const env({
{"MSI_ENDPOINT", ""},
{"MSI_SECRET", ""},
{"IDENTITY_ENDPOINT", ""},
{"IMDS_ENDPOINT", "https://xbox.com/"},
{"IDENTITY_HEADER", ""},
{"IDENTITY_SERVER_THUMBPRINT", ""},
{"AZURE_IMDS_CUSTOM_AUTHORITY_HOST", "http://localhost:59202"},
});
return std::make_unique<ManagedIdentityCredential>(
"01234567-89ab-cdef-fedc-ba9876543210", options);
},
{{"https://outlook.com/.default"}},
{"{\"expires_in\":7200, \"access_token\":\"ACCESSTOKEN2\"}"});
EXPECT_EQ(actual1.Requests.size(), 1U);
EXPECT_EQ(actual1.Responses.size(), 1U);
EXPECT_EQ(actual2.Requests.size(), 1U);
EXPECT_EQ(actual2.Responses.size(), 1U);
auto const& request1 = actual1.Requests.at(0);
auto const& response1 = actual1.Responses.at(0);
auto const& request2 = actual2.Requests.at(0);
auto const& response2 = actual2.Responses.at(0);
EXPECT_EQ(request1.HttpMethod, HttpMethod::Get);
EXPECT_EQ(request2.HttpMethod, HttpMethod::Get);
EXPECT_EQ(
request1.AbsoluteUrl,
"http://169.254.169.254/metadata/identity/oauth2/token"
"?api-version=2018-02-01"
"&client_id=fedcba98-7654-3210-0123-456789abcdef"
"&resource=https%3A%2F%2Fazure.com"); // cspell:disable-line
EXPECT_EQ(
request2.AbsoluteUrl,
"https://custom.imds.endpoint/metadata/identity/oauth2/token"
"?api-version=2018-02-01"
"&client_id=01234567-89ab-cdef-fedc-ba9876543210"
"&resource=https%3A%2F%2Foutlook.com"); // cspell:disable-line
auto const& request3 = actual3.Requests.at(0);
EXPECT_EQ(
request3.AbsoluteUrl,
"http://localhost:59202/metadata/identity/oauth2/token"
"?api-version=2018-02-01"
"&client_id=01234567-89ab-cdef-fedc-ba9876543210"
"&resource=https%3A%2F%2Foutlook.com"); // cspell:disable-line
EXPECT_TRUE(request1.Body.empty());
EXPECT_TRUE(request2.Body.empty());
{
EXPECT_NE(request1.Headers.find("Metadata"), request1.Headers.end());
EXPECT_EQ(request1.Headers.at("Metadata"), "true");
EXPECT_NE(request2.Headers.find("Metadata"), request2.Headers.end());
EXPECT_EQ(request2.Headers.at("Metadata"), "true");
}
EXPECT_EQ(response1.AccessToken.Token, "ACCESSTOKEN1");
EXPECT_EQ(response2.AccessToken.Token, "ACCESSTOKEN2");
using namespace std::chrono_literals;
EXPECT_GE(response1.AccessToken.ExpiresOn, response1.EarliestExpiration + 3600s);
EXPECT_LE(response1.AccessToken.ExpiresOn, response1.LatestExpiration + 3600s);
EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 3600s);
EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 3600s);
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "azure/identity/managed_identity_credential.hpp"
#include <string>
#include <gtest/gtest.h>
using namespace Azure::Identity;
TEST(ResourceIdentifier, Basic)
{
std::string resourceId = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/"
"providers/Compute/virtualMachines/vm-name";
ResourceIdentifier resourceIdentifier(resourceId);
EXPECT_EQ(resourceIdentifier.ToString(), resourceId);
}