diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index 3c0432643..d67c7fe21 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features Added - Added `Subscription` to `AzureCliCredentialOptions` which allows the caller to specify an Azure subscription that does not match the current Azure CLI subscription. +- [[#6321]](https://github.com/Azure/azure-sdk-for-cpp/issues/6321) Log Client ID used in `ManagedIdentityCredential`. ### Breaking Changes diff --git a/sdk/identity/azure-identity/src/managed_identity_source.cpp b/sdk/identity/azure-identity/src/managed_identity_source.cpp index cd299038d..20202800a 100644 --- a/sdk/identity/azure-identity/src/managed_identity_source.cpp +++ b/sdk/identity/azure-identity/src/managed_identity_source.cpp @@ -28,9 +28,15 @@ namespace { // host. std::string const ImdsEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token"; -std::string WithSourceMessage(std::string const& credSource) +std::string WithSourceAndClientIdMessage(std::string const& credSource, std::string const& clientId) { - return " with " + credSource + " source"; + std::string result = " with " + credSource + " source"; + if (!clientId.empty()) + { + result += " and Client ID '" + clientId + '\''; + } + + return result; } void PrintEnvNotSetUpMessage(std::string const& credName, std::string const& credSource) @@ -38,7 +44,7 @@ void PrintEnvNotSetUpMessage(std::string const& credName, std::string const& cre IdentityLog::Write( IdentityLog::Level::Verbose, credName + ": Environment is not set up for the credential to be created" - + WithSourceMessage(credSource) + '.'); + + WithSourceAndClientIdMessage(credSource, {}) + '.'); } // ExpectedArcKeyDirectory returns the directory expected to contain Azure Arc keys. @@ -113,7 +119,8 @@ Azure::Core::Url ManagedIdentitySource::ParseEndpointUrl( std::string const& credName, std::string const& url, char const* envVarName, - std::string const& credSource) + std::string const& credSource, + std::string const& clientId) { using Azure::Core::Url; using Azure::Core::Credentials::AuthenticationException; @@ -124,7 +131,7 @@ Azure::Core::Url ManagedIdentitySource::ParseEndpointUrl( IdentityLog::Write( IdentityLog::Level::Informational, - credName + " will be created" + WithSourceMessage(credSource) + '.'); + credName + " will be created" + WithSourceAndClientIdMessage(credSource, clientId) + '.'); return endpointUrl; } @@ -135,7 +142,7 @@ Azure::Core::Url ManagedIdentitySource::ParseEndpointUrl( { } - auto const errorMessage = credName + WithSourceMessage(credSource) + auto const errorMessage = credName + WithSourceAndClientIdMessage(credSource, {}) + ": Failed to create: The environment variable \'" + envVarName + "\' contains an invalid URL."; @@ -166,7 +173,7 @@ std::unique_ptr AppServiceManagedIdentitySource::Create( objectId, resourceId, options, - ParseEndpointUrl(credName, msiEndpoint, endpointVarName, credSource), + ParseEndpointUrl(credName, msiEndpoint, endpointVarName, credSource, clientId), msiSecret)); } @@ -287,7 +294,7 @@ std::unique_ptr CloudShellManagedIdentitySource::Create( constexpr auto EndpointVarName = "MSI_ENDPOINT"; auto msiEndpoint = Environment::GetVariable(EndpointVarName); - std::string const CredSource = "Cloud Shell"; + std::string const credSource = "Cloud Shell"; if (!msiEndpoint.empty()) { @@ -299,10 +306,12 @@ std::unique_ptr CloudShellManagedIdentitySource::Create( } return std::unique_ptr(new CloudShellManagedIdentitySource( - clientId, options, ParseEndpointUrl(credName, msiEndpoint, EndpointVarName, CredSource))); + clientId, + options, + ParseEndpointUrl(credName, msiEndpoint, EndpointVarName, credSource, clientId))); } - PrintEnvNotSetUpMessage(credName, CredSource); + PrintEnvNotSetUpMessage(credName, credSource); return nullptr; } @@ -380,7 +389,8 @@ std::unique_ptr AzureArcManagedIdentitySource::Create( } return std::unique_ptr(new AzureArcManagedIdentitySource( - options, ParseEndpointUrl(credName, identityEndpoint, EndpointVarName, credSource))); + options, + ParseEndpointUrl(credName, identityEndpoint, EndpointVarName, credSource, clientId))); } AzureArcManagedIdentitySource::AzureArcManagedIdentitySource( @@ -488,7 +498,8 @@ std::unique_ptr ImdsManagedIdentitySource::Create( { IdentityLog::Write( IdentityLog::Level::Informational, - credName + " will be created" + WithSourceMessage("Azure Instance Metadata Service") + credName + " will be created" + + WithSourceAndClientIdMessage("Azure Instance Metadata Service", clientId) + ".\nSuccessful creation does not guarantee further successful token retrieval."); return std::unique_ptr( diff --git a/sdk/identity/azure-identity/src/private/managed_identity_source.hpp b/sdk/identity/azure-identity/src/private/managed_identity_source.hpp index 61544675d..bdd005941 100644 --- a/sdk/identity/azure-identity/src/private/managed_identity_source.hpp +++ b/sdk/identity/azure-identity/src/private/managed_identity_source.hpp @@ -32,7 +32,8 @@ namespace Azure { namespace Identity { namespace _detail { std::string const& credName, std::string const& url, char const* envVarName, - std::string const& credSource); + std::string const& credSource, + std::string const& clientId); explicit ManagedIdentitySource( std::string clientId, diff --git a/sdk/identity/azure-identity/test/ut/managed_identity_credential_test.cpp b/sdk/identity/azure-identity/test/ut/managed_identity_credential_test.cpp index a90ad8e10..ba82eb0d1 100644 --- a/sdk/identity/azure-identity/test/ut/managed_identity_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/managed_identity_credential_test.cpp @@ -238,8 +238,14 @@ namespace Azure { namespace Identity { namespace Test { TEST(ManagedIdentityCredential, AppServiceV2019ClientId) { + using Azure::Core::Diagnostics::Logger; + using LogMsgVec = std::vector>; + LogMsgVec log; + Logger::SetLevel(Logger::Level::Verbose); + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + auto const actual = CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { TokenCredentialOptions options; options.Transport.Transport = transport; @@ -252,8 +258,19 @@ namespace Azure { namespace Identity { namespace Test { {"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"}, }); - return std::make_unique( + auto credential = std::make_unique( "fedcba98-7654-3210-0123-456789abcdef", options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Informational); + EXPECT_EQ( + log[0].second, + "Identity: ManagedIdentityCredential will be created with App Service 2019 source" + " and Client ID 'fedcba98-7654-3210-0123-456789abcdef'."); + + log.clear(); + + return credential; }, {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, std::vector{ @@ -324,6 +341,8 @@ namespace Azure { namespace Identity { namespace Test { EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); + + Logger::SetListener(nullptr); } TEST(ManagedIdentityCredential, AppServiceV2019ResourceId) @@ -699,8 +718,14 @@ namespace Azure { namespace Identity { namespace Test { TEST(ManagedIdentityCredential, AppServiceV2017ClientId) { + using Azure::Core::Diagnostics::Logger; + using LogMsgVec = std::vector>; + LogMsgVec log; + Logger::SetLevel(Logger::Level::Verbose); + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + auto const actual = CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { TokenCredentialOptions options; options.Transport.Transport = transport; @@ -713,8 +738,26 @@ namespace Azure { namespace Identity { namespace Test { {"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"}, }); - return std::make_unique( + auto credential = std::make_unique( "fedcba98-7654-3210-0123-456789abcdef", options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(2)); + + EXPECT_EQ(log[0].first, Logger::Level::Verbose); + EXPECT_EQ( + log[0].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with App Service 2019 source."); + + EXPECT_EQ(log[1].first, Logger::Level::Informational); + EXPECT_EQ( + log[1].second, + "Identity: ManagedIdentityCredential will be created with App Service 2017 source" + " and Client ID 'fedcba98-7654-3210-0123-456789abcdef'."); + + log.clear(); + + return credential; }, {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, std::vector{ @@ -785,6 +828,8 @@ namespace Azure { namespace Identity { namespace Test { EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); + + Logger::SetListener(nullptr); } TEST(ManagedIdentityCredential, AppServiceV2017ResourceId) @@ -2455,9 +2500,15 @@ namespace Azure { namespace Identity { namespace Test { TEST(ManagedIdentityCredential, ImdsClientId) { + using Azure::Core::Diagnostics::Logger; + using LogMsgVec = std::vector>; + LogMsgVec log; + Logger::SetLevel(Logger::Level::Verbose); + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + { auto const actual = CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { TokenCredentialOptions options; options.Transport.Transport = transport; @@ -2470,8 +2521,46 @@ namespace Azure { namespace Identity { namespace Test { {"IDENTITY_SERVER_THUMBPRINT", ""}, }); - return std::make_unique( + auto credential = std::make_unique( "fedcba98-7654-3210-0123-456789abcdef", options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(5)); + + EXPECT_EQ(log[0].first, Logger::Level::Verbose); + EXPECT_EQ( + log[0].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with App Service 2019 source."); + + EXPECT_EQ(log[1].first, Logger::Level::Verbose); + EXPECT_EQ( + log[1].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with App Service 2017 source."); + + EXPECT_EQ(log[2].first, Logger::Level::Verbose); + EXPECT_EQ( + log[2].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with Cloud Shell source."); + + EXPECT_EQ(log[3].first, Logger::Level::Verbose); + EXPECT_EQ( + log[3].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with Azure Arc source."); + + EXPECT_EQ(log[4].first, Logger::Level::Informational); + EXPECT_EQ( + log[4].second, + "Identity: ManagedIdentityCredential will be created " + "with Azure Instance Metadata Service source" + " and Client ID 'fedcba98-7654-3210-0123-456789abcdef'." + "\nSuccessful creation does not guarantee further successful token retrieval."); + + log.clear(); + + return credential; }, {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, std::vector{ @@ -2545,7 +2634,7 @@ namespace Azure { namespace Identity { namespace Test { } { auto const actual = CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { ManagedIdentityCredentialOptions options; options.Transport.Transport = transport; options.IdentityId = ManagedIdentityId::FromUserAssignedClientId( @@ -2560,7 +2649,47 @@ namespace Azure { namespace Identity { namespace Test { {"IDENTITY_SERVER_THUMBPRINT", ""}, }); - return std::make_unique(options); + log.clear(); + + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(5)); + + EXPECT_EQ(log[0].first, Logger::Level::Verbose); + EXPECT_EQ( + log[0].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with App Service 2019 source."); + + EXPECT_EQ(log[1].first, Logger::Level::Verbose); + EXPECT_EQ( + log[1].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with App Service 2017 source."); + + EXPECT_EQ(log[2].first, Logger::Level::Verbose); + EXPECT_EQ( + log[2].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with Cloud Shell source."); + + EXPECT_EQ(log[3].first, Logger::Level::Verbose); + EXPECT_EQ( + log[3].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with Azure Arc source."); + + EXPECT_EQ(log[4].first, Logger::Level::Informational); + EXPECT_EQ( + log[4].second, + "Identity: ManagedIdentityCredential will be created " + "with Azure Instance Metadata Service source" + " and Client ID 'fedcba98-7654-3210-0123-456789abcdef'." + "\nSuccessful creation does not guarantee further successful token retrieval."); + + log.clear(); + + return credential; }, {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, std::vector{ @@ -2632,6 +2761,8 @@ namespace Azure { namespace Identity { namespace Test { EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 4999s); EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 4999s); } + + Logger::SetListener(nullptr); } TEST(ManagedIdentityCredential, ImdsResourceId)