From 7cafd17537de1b6a7cdf0f105b37364c2c7773da Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Mon, 6 Mar 2023 15:22:19 -0800 Subject: [PATCH 1/9] Added DefaultAzureCredential --- sdk/identity/azure-identity/CHANGELOG.md | 8 +- sdk/identity/azure-identity/CMakeLists.txt | 2 + sdk/identity/azure-identity/README.md | 175 +++++++------- .../azure-identity/inc/azure/identity.hpp | 1 + .../identity/chained_token_credential.hpp | 12 + .../identity/default_azure_credential.hpp | 73 ++++++ .../azure-identity/samples/CMakeLists.txt | 5 + .../samples/default_azure_credential.cpp | 39 +++ .../src/azure_cli_credential.cpp | 28 ++- .../src/chained_token_credential.cpp | 141 ++++++++--- .../src/default_azure_credential.cpp | 72 ++++++ .../src/environment_credential.cpp | 175 ++++++++++++-- .../src/managed_identity_source.cpp | 111 +++++++-- .../src/private/managed_identity_source.hpp | 11 +- .../azure-identity/test/ut/CMakeLists.txt | 1 + .../test/ut/azure_cli_credential_test.cpp | 27 +++ .../test/ut/chained_token_credential_test.cpp | 107 ++++++--- .../test/ut/default_azure_credential_test.cpp | 134 +++++++++++ .../test/ut/environment_credential_test.cpp | 226 ++++++++++++++++-- .../ut/managed_identity_credential_test.cpp | 195 ++++++++++++++- .../test/ut/simplified_header_test.cpp | 43 +++- 21 files changed, 1356 insertions(+), 230 deletions(-) create mode 100644 sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp create mode 100644 sdk/identity/azure-identity/samples/default_azure_credential.cpp create mode 100644 sdk/identity/azure-identity/src/default_azure_credential.cpp create mode 100644 sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp diff --git a/sdk/identity/azure-identity/CHANGELOG.md b/sdk/identity/azure-identity/CHANGELOG.md index 73547d2624..e111fc1a0f 100644 --- a/sdk/identity/azure-identity/CHANGELOG.md +++ b/sdk/identity/azure-identity/CHANGELOG.md @@ -1,15 +1,15 @@ # Release History -## 1.5.0-beta.1 (Unreleased) +## 1.5.0-beta.1 (2023-03-07) ### Features Added -### Breaking Changes - -### Bugs Fixed +- Added `DefaultAzureCredential`. ### Other Changes +- Improved log messages. + ## 1.4.0 (2023-02-07) ### Features Added diff --git a/sdk/identity/azure-identity/CMakeLists.txt b/sdk/identity/azure-identity/CMakeLists.txt index 0ea5fe69e1..ab2cd1b6b5 100644 --- a/sdk/identity/azure-identity/CMakeLists.txt +++ b/sdk/identity/azure-identity/CMakeLists.txt @@ -52,6 +52,7 @@ set( inc/azure/identity/chained_token_credential.hpp inc/azure/identity/client_certificate_credential.hpp inc/azure/identity/client_secret_credential.hpp + inc/azure/identity/default_azure_credential.hpp inc/azure/identity/dll_import_export.hpp inc/azure/identity/environment_credential.hpp inc/azure/identity/managed_identity_credential.hpp @@ -69,6 +70,7 @@ set( src/client_certificate_credential.cpp src/client_credential_core.cpp src/client_secret_credential.cpp + src/default_azure_credential.cpp src/environment_credential.cpp src/managed_identity_credential.cpp src/managed_identity_source.cpp diff --git a/sdk/identity/azure-identity/README.md b/sdk/identity/azure-identity/README.md index 02c391d782..29963391d6 100644 --- a/sdk/identity/azure-identity/README.md +++ b/sdk/identity/azure-identity/README.md @@ -22,6 +22,21 @@ find_package(azure-identity-cpp CONFIG REQUIRED) target_link_libraries( PRIVATE Azure::azure-identity) ``` +### Prerequisites + +* An [Azure subscription][azure_sub]. +* The [Azure CLI][azure_cli] can also be useful for authenticating in a development environment, creating accounts, and managing account roles. + +### Authenticate the client + +When debugging and executing code locally it is typical for a developer to use their own account for authenticating calls to Azure services. There are several developer tools which can be used to perform this authentication in your development environment. + +#### Authenticate via the Azure CLI + +Developers can use the [Azure CLI][azure_cli] to authenticate. Applications using the `DefaultAzureCredential` or the `AzureCliCredential` can then use this account to authenticate calls in their application when running locally. + +To authenticate with the [Azure CLI][azure_cli], users can run the command `az login`. For users running on a system with a default web browser, the Azure CLI will launch the browser to authenticate the user. + ## Key concepts ### Credentials @@ -29,106 +44,103 @@ A credential is a class which contains or can obtain the data needed for a servi The Azure Identity library focuses on OAuth authentication with Azure Active directory, and it offers a variety of credential classes capable of acquiring an AAD token to authenticate service requests. All of the credential classes in this library are implementations of the `TokenCredential` abstract class in [azure-core][azure_core_library], and any of them can be used by to construct service clients capable of authenticating with a `TokenCredential`. -### Authenticating Service Principals - - - - - - - - - - - - - - - - - - - - - -
credential classusageconfiguration
ClientSecretCredentialauthenticates a service principal using a secretService principal authentication
ClientCertificateCredentialauthenticates a service principal using a certificateService principal authentication
+See [Credential Classes](#credential-classes) for a complete listing of available credential types. -## Environment Variables -`EnvironmentCredential` can be configured with environment variables. Each type of authentication requires values for specific variables: +### DefaultAzureCredential -#### Service principal with secret - - - - - - - - - - - - - - - - - - - - - -
variable namevalue
AZURE_CLIENT_IDid of an Azure Active Directory application
AZURE_TENANT_IDid of the application's Azure Active Directory tenant
AZURE_CLIENT_SECRETone of the application's client secrets
+`DefaultAzureCredential` combines credentials commonly used to authenticate when deployed, with credentials used to authenticate in a development environment. -#### Service principal with certificate - - - - - - - - - - - - - - - - - - - - - -
variable namevalue
AZURE_CLIENT_IDid of an Azure Active Directory application
AZURE_TENANT_IDid of the application's Azure Active Directory tenant
AZURE_CLIENT_CERTIFICATE_PATHpath to a PEM-encoded certificate file including private key (without password protection)
+> Note: `DefaultAzureCredential` is intended to simplify getting started with the SDK by handling common scenarios with reasonable default behaviors. It is not recommended to use it in production. Developers who want more control or whose scenario isn't served by the default settings should use other credential types. -Configuration is attempted in the above order. For example, if values for a client secret and certificate are both present, the client secret will be used. +The `DefaultAzureCredential` attempts to authenticate via the following mechanisms, in this order, stopping when one succeeds: -## Managed Identity Support -The [Managed identity authentication](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) is supported via the `ManagedIdentityCredential` for the following Azure Services: -* [Azure Virtual Machines](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token) -* [Azure Cloud Shell](https://docs.microsoft.com/azure/cloud-shell/msi-authorization) -* [Azure Arc](https://docs.microsoft.com/azure/azure-arc/servers/managed-identity-authentication) +1. **Environment** - The `DefaultAzureCredential` will read account information specified via [environment variables](#environment-variables) and use it to authenticate. +1. **Azure CLI** - If the developer has authenticated an account via the Azure CLI `az login` command, the `DefaultAzureCredential` will authenticate with that account. +1. **Managed Identity** - If the application is deployed to an Azure host with Managed Identity enabled, the `DefaultAzureCredential` will authenticate with that account. + +`DefaultAzureCredential` uses [`ChainedTokenCredential`](#chained-token-credential) that consists of a chain of `EnvironmentCredential`, `AzureCliCredential`, and `ManagedIdentityCredential`. Implementation, including the order in which credentials are applied is documented, but it may change from release to release. + +`DefaultAzureCrdential` intends to provide a credential that "just works out of the box and without requiring any information", if only the environment is set up sufficiently for the credential to work. +Therefore, it could be simple to use, but since it uses a chain of credentials, it could be a bit complicated to diagnose if the environment setup is not sufficient. +TO help with this, `DefaultAzureCredential` code paths are instrumented with [log messages](#troubleshooting). + +## Examples + +See the [code samples](https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/identity/azure-identity/samples). ## Chained Token Credential -`ChainedTokenCredential` allows users to customize the credentials considered when authenticating. + +`ChainedTokenCredential` allows users to set up custom authentication flow consisting of multiple creentials. An example below demonstrates using `ChainedTokenCredential` which will attempt to authenticate using `EnvironmentCredential`, and fall back to authenticate using `ManagedIdentityCredential`. ```cpp -// Authenticate using environment credential if it is available; otherwise use the managed identity credential to authenticate. +// A configuration demonstrated below would authenticate using EnvironmentCredential if it is +// available, and if it is not available, would fall back to use AzureCliCredential, and then to +// ManagedIdentityCredential. auto chainedTokenCredential = std::make_shared( Azure::Identity::ChainedTokenCredential::Sources{ std::make_shared(), + std::make_shared(), std::make_shared()}); Azure::Service::Client azureServiceClient("serviceUrl", chainedTokenCredential); ``` +## Managed Identity Support + +The [Managed identity authentication](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview) is supported via the `ManagedIdentityCredential` for the following Azure Services: +* [Azure App Service and Azure Functions](https://docs.microsoft.com/azure/app-service/overview-managed-identity) +* [Azure Cloud Shell](https://docs.microsoft.com/azure/cloud-shell/msi-authorization) +* [Azure Arc](https://docs.microsoft.com/azure/azure-arc/servers/managed-identity-authentication) +* [Azure Virtual Machines](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token) + +## Environment Variables + +`DefaultAzureCredential` and `EnvironmentCredential` can be configured with environment variables. Each type of authentication requires values for specific variables: + +#### Service principal with secret +|Variable name|Value +|-|- +|`AZURE_TENANT_ID`|ID of the application's Azure AD tenant +|`AZURE_CLIENT_ID`|ID of an Azure AD application +|`AZURE_CLIENT_SECRET`|one of the application's client secrets +|`AZURE_AUTHORITY_HOST`|(optional) [authentication authority URL](https://docs.microsoft.com/azure/active-directory/develop/authentication-national-cloud) + +#### Service principal with certificate +|variable name|Value +|-|- +|`AZURE_CLIENT_ID`|ID of an Azure AD application +|`AZURE_TENANT_ID`|ID of the application's Azure AD tenant +|`AZURE_CLIENT_CERTIFICATE_PATH`|path to a PFX or PEM-encoded certificate file including private key +|`AZURE_AUTHORITY_HOST`|(optional) [authentication authority URL](https://docs.microsoft.com/azure/active-directory/develop/authentication-national-cloud) + +Configuration is attempted in the above order. For example, if values for a client secret and certificate are both present, the client secret will be used. + +## Credential classes + +### Authenticate Azure-hosted applications +|Credential | Usage +|-|- +|`DefaultAzureCredential`|Provides a simplified authentication experience to quickly start developing applications run in Azure. +|`ChainedTokenCredential`|Allows users to define custom authentication flows composing multiple credentials. +|`ManagedIdentityCredential`|Authenticates the managed identity of an Azure resource. +|`EnvironmentCredential`|Authenticates a service principal or user via credential information specified in environment variables. + +### Authenticate service principals +|Credential | Usage +|-|- +|`ClientSecretCredential`|Authenticates a service principal [using a secret](https://learn.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals). +|`ClientCertificateCredential`|Authenticates a service principal [using a certificate](https://learn.microsoft.com/azure/active-directory/develop/app-objects-and-service-principals). + +### Authenticate via development tools +|Credential | Usage +|-|- +|`AzureCliCredential`|Authenticates in a development environment [with the Azure CLI](https://learn.microsoft.com/cli/azure/authenticate-azure-cli). + ## Troubleshooting -Credentials raise exceptions either when they fail to authenticate or cannot execute authentication. -When credentials fail to authenticate, the `AuthenticationException` is thrown and it has the `what()` functions returning the description why authentication failed. + +1. Azure Identity [SDK log messages](https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/core/azure-core#sdk-log-messages) contain disgnostic infrmation, and start with "`Identity: `". +1. Credentials raise exceptions either when they fail to authenticate or cannot execute authentication. When a credential fails to authenticate, an `AuthenticationException` is thrown. The exception has the `what()` function that provides more information about the failure. ## Contributing For details on contributing to this repository, see the [contributing guide][azure_sdk_for_cpp_contributing]. @@ -163,6 +175,7 @@ Security issues and bugs should be reported privately, via email, to the Microso Azure SDK for C++ is licensed under the [MIT](https://github.com/Azure/azure-sdk-for-cpp/blob/main/LICENSE.txt) license. +[azure_cli]: https://learn.microsoft.com/cli/azure [azsdk_vcpkg_install]: https://github.com/Azure/azure-sdk-for-cpp#download--install-the-sdk [azure_sdk_for_cpp_contributing]: https://github.com/Azure/azure-sdk-for-cpp/blob/main/CONTRIBUTING.md [azure_sdk_for_cpp_contributing_developer_guide]: https://github.com/Azure/azure-sdk-for-cpp/blob/main/CONTRIBUTING.md#developer-guide diff --git a/sdk/identity/azure-identity/inc/azure/identity.hpp b/sdk/identity/azure-identity/inc/azure/identity.hpp index 6afce616a9..5970cb7ae7 100644 --- a/sdk/identity/azure-identity/inc/azure/identity.hpp +++ b/sdk/identity/azure-identity/inc/azure/identity.hpp @@ -12,6 +12,7 @@ #include "azure/identity/chained_token_credential.hpp" #include "azure/identity/client_certificate_credential.hpp" #include "azure/identity/client_secret_credential.hpp" +#include "azure/identity/default_azure_credential.hpp" #include "azure/identity/dll_import_export.hpp" #include "azure/identity/environment_credential.hpp" #include "azure/identity/managed_identity_credential.hpp" diff --git a/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp b/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp index e4de47d850..a8bd474134 100644 --- a/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp +++ b/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp @@ -11,9 +11,12 @@ #include #include +#include #include namespace Azure { namespace Identity { + class DefaultAzureCredential; + /** * @brief Chained Token Credential provides a token credential implementation which chains * multiple Azure::Core::Credentials::TokenCredential implementations to be tried in order until @@ -21,6 +24,8 @@ namespace Azure { namespace Identity { * */ class ChainedTokenCredential final : public Core::Credentials::TokenCredential { + friend class DefaultAzureCredential; + public: /** * @brief A container type to store the ordered chain of credentials. @@ -55,6 +60,13 @@ namespace Azure { namespace Identity { Core::Context const& context) const override; private: + explicit ChainedTokenCredential( + Sources sources, + std::string const& enclosingCredential, + std::vector sourceDescriptions); + + std::string m_logPrefix; + std::vector m_sourceDescriptions; Sources m_sources; }; diff --git a/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp b/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp new file mode 100644 index 0000000000..d03d2fd3b1 --- /dev/null +++ b/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * @brief Default Azure Credential. + */ + +#pragma once + +#include + +#include + +#include + +namespace Azure { namespace Identity { + + /** + * @brief Default Azure Credential combines multiple credentials that depend on the setup + * environment and require no parameters into a single chain. If the environment is set up + * sufficiently for at least one of such credentials to work, `DefaultAzureCredential` will work + * as well. + * + * @details This credential is using the #ChainedTokenCredential of 3 credentials in the order: + * #EnvironmentCredential, #AzureCliCredential, and #ManagedIdentityCredential. Even though the + * credentials being used and their order is documented, it may be changed in the future versions + * of the SDK, potentially bringing breaking changes in its behavior. + * + * @note This credential is intended to be used at the early stages of development, to allow the + * developer some time to work with the other aspects of the SDK, and later to replace this + * credential with the exact credential that is the best fit for the application. It is not + * intended to be used in a production environment. + * + */ + class DefaultAzureCredential final : public Core::Credentials::TokenCredential { + public: + /** + * @brief Constructs `%DefaultAzureCredential`. + * + */ + explicit DefaultAzureCredential() : DefaultAzureCredential({}){}; + + /** + * @brief Constructs `%DefaultAzureCredential`. + * + * @param options Generic Token Credential Options. + */ + explicit DefaultAzureCredential(Core::Credentials::TokenCredentialOptions const& options); + + /** + * @brief Destructs `%DefaultAzureCredential`. + * + */ + ~DefaultAzureCredential() override; + + /** + * @brief Gets an authentication token. + * + * @param tokenRequestContext A context to get the token in. + * @param context A context to control the request lifetime. + * + * @throw Azure::Core::Credentials::AuthenticationException Authentication error occurred. + */ + Core::Credentials::AccessToken GetToken( + Core::Credentials::TokenRequestContext const& tokenRequestContext, + Core::Context const& context) const override; + + private: + std::shared_ptr m_credentials; + }; + +}} // namespace Azure::Identity diff --git a/sdk/identity/azure-identity/samples/CMakeLists.txt b/sdk/identity/azure-identity/samples/CMakeLists.txt index 1e56cd640e..c9ceca8774 100644 --- a/sdk/identity/azure-identity/samples/CMakeLists.txt +++ b/sdk/identity/azure-identity/samples/CMakeLists.txt @@ -27,6 +27,11 @@ target_link_libraries(client_secret_credential_sample PRIVATE azure-identity ser target_include_directories(client_secret_credential_sample PRIVATE .) create_per_service_target_build_for_sample(identity client_secret_credential_sample) +add_executable(default_azure_credential_sample default_azure_credential.cpp) +target_link_libraries(default_azure_credential_sample PRIVATE azure-identity service) +target_include_directories(default_azure_credential_sample PRIVATE .) +create_per_service_target_build_for_sample(identity default_azure_credential_sample) + add_executable(environment_credential_sample environment_credential.cpp) target_link_libraries(environment_credential_sample PRIVATE azure-identity service) target_include_directories(environment_credential_sample PRIVATE .) diff --git a/sdk/identity/azure-identity/samples/default_azure_credential.cpp b/sdk/identity/azure-identity/samples/default_azure_credential.cpp new file mode 100644 index 0000000000..238d5c3f39 --- /dev/null +++ b/sdk/identity/azure-identity/samples/default_azure_credential.cpp @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include + +#include + +#include + +int main() +{ + try + { + // Step 1: Initialize Default Azure Credential. + // Default Azure Credential is good for samples and initial development stages only. + // It is not recommended used it in a production environment. + // To diagnose, see + // https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/identity/azure-identity#troubleshooting + + auto defaultAzureCredential = std::make_shared(); + + // Step 2: Pass the credential to an Azure Service Client. + Azure::Service::Client azureServiceClient("serviceUrl", defaultAzureCredential); + + // Step 3: Start using the Azure Service Client. + azureServiceClient.DoSomething(Azure::Core::Context::ApplicationContext); + + std::cout << "Success!" << std::endl; + } + catch (const Azure::Core::Credentials::AuthenticationException& exception) + { + // Step 4: Handle authentication errors, if needed + // (invalid credential parameters, insufficient permissions). + std::cout << "Authentication error: " << exception.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/sdk/identity/azure-identity/src/azure_cli_credential.cpp b/sdk/identity/azure-identity/src/azure_cli_credential.cpp index fc1f1329f9..47a11a94f7 100644 --- a/sdk/identity/azure-identity/src/azure_cli_credential.cpp +++ b/sdk/identity/azure-identity/src/azure_cli_credential.cpp @@ -5,6 +5,7 @@ #include "private/token_credential_impl.hpp" +#include #include #include #include @@ -43,11 +44,15 @@ using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; using Azure::Core::Credentials::TokenCredentialOptions; using Azure::Core::Credentials::TokenRequestContext; +using Azure::Core::Diagnostics::Logger; +using Azure::Core::Diagnostics::_internal::Log; using Azure::Identity::AzureCliCredentialOptions; using Azure::Identity::_detail::TokenCache; using Azure::Identity::_detail::TokenCredentialImpl; namespace { +std::string const MsgPrefix = "Identity: AzureCliCredential"; + void ThrowIfNotSafeCmdLineInput(std::string const& input, std::string const& description) { for (auto const c : input) @@ -66,8 +71,7 @@ void ThrowIfNotSafeCmdLineInput(std::string const& input, std::string const& des if (!std::isalnum(c, std::locale::classic())) { throw AuthenticationException( - "AzureCliCredential: Unsafe command line input found in " + description + ": " - + input); + MsgPrefix + ": Unsafe command line input found in " + description + ": " + input); } } } @@ -82,6 +86,16 @@ AzureCliCredential::AzureCliCredential( { static_cast(options); ThrowIfNotSafeCmdLineInput(m_tenantId, "TenantID"); + + auto const logLevel = Logger::Level::Informational; + if (Log::ShouldWrite(logLevel)) + { + Log::Write( + logLevel, + MsgPrefix + + " created.\n" + "Successful creation does not guarantee further succesful token retrieval."); + } } AzureCliCredential::AzureCliCredential(AzureCliCredentialOptions const& options) @@ -147,7 +161,15 @@ AccessToken AzureCliCredential::GetToken( } catch (std::exception const& e) { - throw AuthenticationException(std::string("AzureCliCredential::GetToken(): ") + e.what()); + auto const errorMsg = MsgPrefix + " didn't get the token: \"" + e.what() + '\"'; + + auto const logLevel = Logger::Level::Warning; + if (Log::ShouldWrite(logLevel)) + { + Log::Write(logLevel, errorMsg); + } + + throw AuthenticationException(errorMsg); } }); } diff --git a/sdk/identity/azure-identity/src/chained_token_credential.cpp b/sdk/identity/azure-identity/src/chained_token_credential.cpp index 6db48d282b..68a32a6218 100644 --- a/sdk/identity/azure-identity/src/chained_token_credential.cpp +++ b/sdk/identity/azure-identity/src/chained_token_credential.cpp @@ -4,69 +4,138 @@ #include "azure/identity/chained_token_credential.hpp" #include "azure/core/internal/diagnostics/log.hpp" +#include #include using namespace Azure::Identity; using namespace Azure::Core::Credentials; using Azure::Core::Context; +using Azure::Core::Diagnostics::Logger; +using Azure::Core::Diagnostics::_internal::Log; + +namespace { +std::string const IdentityPrefix = "Identity: "; +} ChainedTokenCredential::ChainedTokenCredential(ChainedTokenCredential::Sources sources) - : m_sources(std::move(sources)) + : ChainedTokenCredential(sources, {}, {}) { } -ChainedTokenCredential::~ChainedTokenCredential() = default; - -AccessToken ChainedTokenCredential::GetToken( - TokenRequestContext const& tokenRequestContext, - Context const& context) const +ChainedTokenCredential::ChainedTokenCredential( + ChainedTokenCredential::Sources sources, + std::string const& enclosingCredential, + std::vector sourceDescriptions) + : m_sources(std::move(sources)), m_sourceDescriptions(std::move(sourceDescriptions)) { - using Azure::Core::Diagnostics::Logger; - using Azure::Core::Diagnostics::_internal::Log; + // LCOV_EXCL_START + AZURE_ASSERT(m_sourceDescriptions.empty() || m_sourceDescriptions.size() == m_sources.size()); + // LCOV_EXCL_STOP - auto n = 0; - for (const auto& source : m_sources) + auto const logLevel = m_sources.empty() ? Logger::Level::Warning : Logger::Level::Informational; + if (Log::ShouldWrite(logLevel)) { - try + std::string credentialsList; + if (!m_sourceDescriptions.empty()) { - ++n; - auto token = source->GetToken(tokenRequestContext, context); - + auto const sourceDescriptionsSize = m_sourceDescriptions.size(); + for (auto i = 0; i < (sourceDescriptionsSize - 1); ++i) { - const auto logLevel = Logger::Level::Informational; - if (Log::ShouldWrite(logLevel)) - { - Log::Write( - logLevel, - std::string("ChainedTokenCredential authentication attempt with credential #") - + std::to_string(n) + " did succeed."); - } + credentialsList += m_sourceDescriptions[i] + ", "; } - return token; + credentialsList += m_sourceDescriptions.back(); } - catch (const AuthenticationException& e) + + Log::Write( + logLevel, + IdentityPrefix + + (enclosingCredential.empty() + ? "ChainedTokenCredential: Created" + : (enclosingCredential + ": Created ChainedTokenCredential")) + + " with " + + (m_sourceDescriptions.empty() + ? (std::to_string(m_sources.size()) + " credentials.") + : (std::string("the following credentials: ") + credentialsList + '.'))); + } + + m_logPrefix = IdentityPrefix + + (enclosingCredential.empty() ? "ChainedTokenCredential" + : (enclosingCredential + " -> ChainedTokenCredential")) + + ": "; + + if (m_sourceDescriptions.empty()) + { + auto const sourcesSize = m_sources.size(); + for (auto i = 1; i <= sourcesSize; ++i) { - const auto logLevel = Logger::Level::Verbose; - if (Log::ShouldWrite(logLevel)) - { - Log::Write( - logLevel, - std::string("ChainedTokenCredential authentication attempt with credential #") - + std::to_string(n) + " did not succeed: " + e.what()); - } + m_sourceDescriptions.push_back(std::string("credential #") + std::to_string(i)); } } +} + +ChainedTokenCredential::~ChainedTokenCredential() = default; + +AccessToken ChainedTokenCredential::GetToken( + TokenRequestContext const& tokenRequestContext, + Context const& context) const +{ + auto const sourcesSize = m_sources.size(); - if (n == 0) + if (sourcesSize == 0) { - const auto logLevel = Logger::Level::Verbose; + const auto logLevel = Logger::Level::Warning; if (Log::ShouldWrite(logLevel)) { Log::Write( - logLevel, - "ChainedTokenCredential authentication did not succeed: list of sources is empty."); + logLevel, m_logPrefix + "Authentication did not succeed: List of sources is empty."); + } + } + else + { + for (auto i = 0; i < sourcesSize; ++i) + { + try + { + auto token = m_sources[i]->GetToken(tokenRequestContext, context); + + { + auto const logLevel = Logger::Level::Informational; + if (Log::ShouldWrite(logLevel)) + { + Log::Write( + logLevel, + m_logPrefix + "Successfully got token from " + m_sourceDescriptions[i] + '.'); + } + } + + return token; + } + catch (AuthenticationException const& e) + { + { + auto const logLevel = Logger::Level::Verbose; + if (Log::ShouldWrite(logLevel)) + { + Log::Write( + logLevel, + m_logPrefix + "Failed to get token from " + m_sourceDescriptions[i] + ": " + + e.what()); + } + } + + if ((sourcesSize - 1) == i) // On the last credential + { + auto const logLevel = Logger::Level::Warning; + if (Log::ShouldWrite(logLevel)) + { + Log::Write( + logLevel, + m_logPrefix + "Didn't succeed to get a token from any credential in the chain."); + } + } + } } } diff --git a/sdk/identity/azure-identity/src/default_azure_credential.cpp b/sdk/identity/azure-identity/src/default_azure_credential.cpp new file mode 100644 index 0000000000..85d0eead91 --- /dev/null +++ b/sdk/identity/azure-identity/src/default_azure_credential.cpp @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "azure/identity/default_azure_credential.hpp" + +#include "azure/identity/azure_cli_credential.hpp" +#include "azure/identity/environment_credential.hpp" +#include "azure/identity/managed_identity_credential.hpp" + +#include "azure/core/internal/diagnostics/log.hpp" + +using namespace Azure::Identity; +using namespace Azure::Core::Credentials; + +using Azure::Core::Context; +using Azure::Core::Diagnostics::Logger; +using Azure::Core::Diagnostics::_internal::Log; + +namespace { +std::string const IdentityPrefix = "Identity: "; +} + +DefaultAzureCredential::DefaultAzureCredential(TokenCredentialOptions const& options) +{ + // Initializing m_credential below and not in the member initializer list to have a specific order + // of log messages. + auto const logLevel = Logger::Level::Verbose; + if (Log::ShouldWrite(logLevel)) + { + Log::Write( + logLevel, + IdentityPrefix + + "Creating DefaultAzureCredential which combines mutiple parameterless credentials " + "into a single one (by using ChainedTokenCredential)." + "\nDefaultAzureCredential is only recommended for the early stages of development, " + "and not for usage in production environment." + "\nOnce the developer focuses on the Credentials and Authentication aspects of their " + "application, DefaultAzureCredential needs to be replaced with the credential that " + "is the better fit for the application."); + } + + // Creating credentials in order to ensure the order of log messages. + auto const envCred = std::make_shared(options); + auto const azCliCred = std::make_shared(options); + auto const managedIdentityCred = std::make_shared(options); + + // Using the ChainedTokenCredential's private constructor for more detailed log messages. + m_credentials.reset(new ChainedTokenCredential( + ChainedTokenCredential::Sources{envCred, azCliCred, managedIdentityCred}, + "DefaultAzureCredential", // extra args for the ChainedTokenCredential's private constructor. + std::vector{ + "EnvironmentCredential", "AzureCliCredential", "ManagedIdentityCredential"})); +} + +DefaultAzureCredential::~DefaultAzureCredential() = default; + +AccessToken DefaultAzureCredential::GetToken( + TokenRequestContext const& tokenRequestContext, + Context const& context) const +{ + try + { + return m_credentials->GetToken(tokenRequestContext, context); + } + catch (AuthenticationException const&) + { + throw AuthenticationException("Failed to get token from DefaultAzureCredential." + "\nSee Azure::Core::Diagnostics::Logger for details " + "(https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/" + "identity/azure-identity#troubleshooting)."); + } +} diff --git a/sdk/identity/azure-identity/src/environment_credential.cpp b/sdk/identity/azure-identity/src/environment_credential.cpp index 8ace56ca22..557268819f 100644 --- a/sdk/identity/azure-identity/src/environment_credential.cpp +++ b/sdk/identity/azure-identity/src/environment_credential.cpp @@ -5,8 +5,13 @@ #include "azure/identity/client_certificate_credential.hpp" #include "azure/identity/client_secret_credential.hpp" +#include +#include #include +#include +#include + using Azure::Identity::EnvironmentCredential; using Azure::Core::Context; @@ -15,19 +20,32 @@ using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; using Azure::Core::Credentials::TokenCredentialOptions; using Azure::Core::Credentials::TokenRequestContext; +using Azure::Core::Diagnostics::Logger; +using Azure::Core::Diagnostics::_internal::Log; + +namespace { +auto const AzureTenantIdEnvVarName = "AZURE_TENANT_ID"; +auto const AzureClientIdEnvVarName = "AZURE_CLIENT_ID"; +auto const AzureClientSecretEnvVarName = "AZURE_CLIENT_SECRET"; +auto const AzureAuthorityHostEnvVarName = "AZURE_AUTHORITY_HOST"; +auto const AzureClientCertificatePathEnvVarName = "AZURE_CLIENT_CERTIFICATE_PATH"; + +std::string const LogMsgPrefix = "Identity: EnvironmentCredential"; + +void PrintCredentialCreationLogMessage( + std::vector> const& envVarsToParams, + char const* credThatGetsCreated); +} // namespace EnvironmentCredential::EnvironmentCredential(TokenCredentialOptions options) { - auto tenantId = Environment::GetVariable("AZURE_TENANT_ID"); - auto clientId = Environment::GetVariable("AZURE_CLIENT_ID"); - - auto clientSecret = Environment::GetVariable("AZURE_CLIENT_SECRET"); - auto authority = Environment::GetVariable("AZURE_AUTHORITY_HOST"); + auto tenantId = Environment::GetVariable(AzureTenantIdEnvVarName); + auto clientId = Environment::GetVariable(AzureClientIdEnvVarName); - // auto username = Environment::GetVariable("AZURE_USERNAME"); - // auto password = Environment::GetVariable("AZURE_PASSWORD"); + auto clientSecret = Environment::GetVariable(AzureClientSecretEnvVarName); + auto authority = Environment::GetVariable(AzureAuthorityHostEnvVarName); - auto clientCertificatePath = Environment::GetVariable("AZURE_CLIENT_CERTIFICATE_PATH"); + auto clientCertificatePath = Environment::GetVariable(AzureClientCertificatePathEnvVarName); if (!tenantId.empty() && !clientId.empty()) { @@ -35,6 +53,15 @@ EnvironmentCredential::EnvironmentCredential(TokenCredentialOptions options) { if (!authority.empty()) { + PrintCredentialCreationLogMessage( + { + {AzureTenantIdEnvVarName, "tenantId"}, + {AzureClientIdEnvVarName, "clientId"}, + {AzureClientSecretEnvVarName, "clientSecret"}, + {AzureAuthorityHostEnvVarName, "authorityHost"}, + }, + "ClientSecretCredential"); + ClientSecretCredentialOptions clientSecretCredentialOptions; static_cast(clientSecretCredentialOptions) = options; clientSecretCredentialOptions.AuthorityHost = authority; @@ -44,20 +71,31 @@ EnvironmentCredential::EnvironmentCredential(TokenCredentialOptions options) } else { + PrintCredentialCreationLogMessage( + { + {AzureTenantIdEnvVarName, "tenantId"}, + {AzureClientIdEnvVarName, "clientId"}, + {AzureClientSecretEnvVarName, "clientSecret"}, + }, + "ClientSecretCredential"); + m_credentialImpl.reset( new ClientSecretCredential(tenantId, clientId, clientSecret, options)); } } - // TODO: UsernamePasswordCredential is not implemented. Uncomment when implemented. - // else if (!username.empty() && !password.empty()) - // { - // m_credentialImpl.reset( - // new UsernamePasswordCredential(tenantId, clientId, username, password, options)); - // } else if (!clientCertificatePath.empty()) { if (!authority.empty()) { + PrintCredentialCreationLogMessage( + { + {AzureTenantIdEnvVarName, "tenantId"}, + {AzureClientIdEnvVarName, "clientId"}, + {AzureClientCertificatePathEnvVarName, "clientCertificatePath"}, + {AzureAuthorityHostEnvVarName, "authorityHost"}, + }, + "ClientCertificateCredential"); + ClientCertificateCredentialOptions clientCertificateCredentialOptions; static_cast(clientCertificateCredentialOptions) = options; clientCertificateCredentialOptions.AuthorityHost = authority; @@ -67,11 +105,57 @@ EnvironmentCredential::EnvironmentCredential(TokenCredentialOptions options) } else { + PrintCredentialCreationLogMessage( + { + {AzureTenantIdEnvVarName, "tenantId"}, + {AzureClientIdEnvVarName, "clientId"}, + {AzureClientCertificatePathEnvVarName, "clientCertificatePath"}, + }, + "ClientCertificateCredential"); + m_credentialImpl.reset( new ClientCertificateCredential(tenantId, clientId, clientCertificatePath, options)); } } } + + if (!m_credentialImpl) + { + auto const logLevel = Logger::Level::Warning; + if (Log::ShouldWrite(logLevel)) + { + std::string const basicMessage + = LogMsgPrefix + " was not initialized with underlying credential"; + + if (!Log::ShouldWrite(Logger::Level::Verbose)) + { + Log::Write(logLevel, basicMessage + '.'); + } + else + { + std::string logMsg = basicMessage + ": Both '" + AzureTenantIdEnvVarName + "' and '" + + AzureClientIdEnvVarName + "', and at least one of '" + AzureClientSecretEnvVarName + + "', '" + AzureClientCertificatePathEnvVarName + "' needs to be set. Additionally, '" + + AzureAuthorityHostEnvVarName + + "' could be set to override the default authority host. Currently:\n"; + + std::pair envVarStatus[] = { + {AzureTenantIdEnvVarName, !tenantId.empty()}, + {AzureClientIdEnvVarName, !clientId.empty()}, + {AzureClientSecretEnvVarName, !clientSecret.empty()}, + {AzureClientCertificatePathEnvVarName, !clientCertificatePath.empty()}, + {AzureAuthorityHostEnvVarName, !authority.empty()}, + }; + for (auto const status : envVarStatus) + { + logMsg += std::string(" * '") + status.first + "' " + "is" + + (status.second ? " " : " NOT ") + "set\n"; + } + + Log::Write(logLevel, logMsg); + } + } + } } AccessToken EnvironmentCredential::GetToken( @@ -80,9 +164,68 @@ AccessToken EnvironmentCredential::GetToken( { if (!m_credentialImpl) { - throw AuthenticationException("EnvironmentCredential authentication unavailable. " - "Environment variables are not fully configured."); + std::string const AuthUnavailable = LogMsgPrefix + " authentication unavailable. "; + + { + auto const logLevel = Logger::Level::Warning; + if (Log::ShouldWrite(logLevel)) + { + Log::Write( + logLevel, + AuthUnavailable + "See earlier EnvironmentCredential log messages for details."); + } + } + + throw AuthenticationException( + AuthUnavailable + "Environment variables are not fully configured."); } return m_credentialImpl->GetToken(tokenRequestContext, context); } + +namespace { +void PrintCredentialCreationLogMessage( + std::vector> const& envVarsToParams, + char const* credThatGetsCreated) +{ + if (!Log::ShouldWrite(Logger::Level::Verbose)) + { + if (Log::ShouldWrite(Logger::Level::Informational)) + { + Log::Write( + Logger::Level::Informational, + LogMsgPrefix + " gets created with " + credThatGetsCreated + '.'); + } + + return; + } + + auto const envVarsToParamsSize = envVarsToParams.size(); + + // LCOV_EXCL_START + AZURE_ASSERT(envVarsToParamsSize > 1); + // LCOV_EXCL_STOP + + std::string const Tick = "'"; + std::string const Comma = ", "; + std::string const TickComma = Tick + Comma; + std::string const And = "and "; + + std::string envVars; + std::string credParams; + for (auto i = 0; i < envVarsToParamsSize - 1; + ++i) // not iterating over the last element for ", and". + { + envVars += Tick + envVarsToParams[i].first + TickComma; + credParams += envVarsToParams[i].second + Comma; + } + + envVars += And + Tick + envVarsToParams.back().first + Tick; + credParams += And + envVarsToParams.back().second; + + Log::Write( + Logger::Level::Verbose, + LogMsgPrefix + ": " + envVars + " environment variables are set, so " + credThatGetsCreated + + " with corresponding " + credParams + " gets created."); +} +} // namespace diff --git a/sdk/identity/azure-identity/src/managed_identity_source.cpp b/sdk/identity/azure-identity/src/managed_identity_source.cpp index dc4af9d046..43b749e10b 100644 --- a/sdk/identity/azure-identity/src/managed_identity_source.cpp +++ b/sdk/identity/azure-identity/src/managed_identity_source.cpp @@ -5,24 +5,62 @@ #include +#include + #include #include #include #include using namespace Azure::Identity::_detail; + using Azure::Core::_internal::Environment; +using Azure::Core::Diagnostics::Logger; +using Azure::Core::Diagnostics::_internal::Log; + +namespace { +std::string const IdentityPrefix = "Identity: "; +std::string const CredPrefix = "ManagedIdentityCredential"; + +std::string WithSourceMessage(std::string const& credSource) +{ + return std::string(" with ") + credSource + " source"; +} + +void PrintEnvNotSetUpMessage(std::string const& credSource) +{ + auto const logLevel = Logger::Level::Verbose; + if (Log::ShouldWrite(logLevel)) + { + Log::Write( + logLevel, + IdentityPrefix + CredPrefix + ": Environment is not set up for the credential to be created" + + WithSourceMessage(credSource) + '.'); + } +} +} // namespace Azure::Core::Url ManagedIdentitySource::ParseEndpointUrl( std::string const& url, - char const* envVarName) + char const* envVarName, + std::string const credSource) { using Azure::Core::Url; using Azure::Core::Credentials::AuthenticationException; try { - return Url(url); + auto const endpointUrl = Url(url); + + auto const logLevel = Logger::Level::Informational; + if (Log::ShouldWrite(logLevel)) + { + Log::Write( + logLevel, + IdentityPrefix + CredPrefix + " will be created" + WithSourceMessage(credSource) + '.'); + } + + return endpointUrl; } catch (std::invalid_argument const&) { @@ -31,24 +69,40 @@ Azure::Core::Url ManagedIdentitySource::ParseEndpointUrl( { } - throw AuthenticationException( - std::string("The environment variable ") + envVarName + " contains an invalid URL."); + auto const errorMessage = CredPrefix + WithSourceMessage(credSource) + + ": Failed to create: The environment variable \'" + envVarName + + "\' contains an invalid URL."; + + auto const logLevel = Logger::Level::Warning; + if (Log::ShouldWrite(logLevel)) + { + Log::Write(logLevel, IdentityPrefix + errorMessage); + } + + throw AuthenticationException(errorMessage); } template std::unique_ptr AppServiceManagedIdentitySource::Create( std::string const& clientId, Azure::Core::Credentials::TokenCredentialOptions const& options, - const char* endpointVarName, - const char* secretVarName) + char const* endpointVarName, + char const* secretVarName, + char const* appServiceVersion) { - auto msiEndpoint = Environment::GetVariable(endpointVarName); - auto msiSecret = Environment::GetVariable(secretVarName); + auto const msiEndpoint = Environment::GetVariable(endpointVarName); + auto const msiSecret = Environment::GetVariable(secretVarName); + + auto const credSource = std::string("App Service ") + appServiceVersion; - return (msiEndpoint.empty() || msiSecret.empty()) - ? nullptr - : std::unique_ptr( - new T(clientId, options, ParseEndpointUrl(msiEndpoint, endpointVarName), msiSecret)); + if (!msiEndpoint.empty() && !msiSecret.empty()) + { + return std::unique_ptr(new T( + clientId, options, ParseEndpointUrl(msiEndpoint, endpointVarName, credSource), msiSecret)); + } + + PrintEnvNotSetUpMessage(credSource); + return nullptr; } AppServiceManagedIdentitySource::AppServiceManagedIdentitySource( @@ -113,7 +167,7 @@ std::unique_ptr AppServiceV2017ManagedIdentitySource::Cre Core::Credentials::TokenCredentialOptions const& options) { return AppServiceManagedIdentitySource::Create( - clientId, options, "MSI_ENDPOINT", "MSI_SECRET"); + clientId, options, "MSI_ENDPOINT", "MSI_SECRET", "2017"); } std::unique_ptr AppServiceV2019ManagedIdentitySource::Create( @@ -121,7 +175,7 @@ std::unique_ptr AppServiceV2019ManagedIdentitySource::Cre Core::Credentials::TokenCredentialOptions const& options) { return AppServiceManagedIdentitySource::Create( - clientId, options, "IDENTITY_ENDPOINT", "IDENTITY_HEADER"); + clientId, options, "IDENTITY_ENDPOINT", "IDENTITY_HEADER", "2019"); } std::unique_ptr CloudShellManagedIdentitySource::Create( @@ -131,10 +185,16 @@ std::unique_ptr CloudShellManagedIdentitySource::Create( constexpr auto EndpointVarName = "MSI_ENDPOINT"; auto msiEndpoint = Environment::GetVariable(EndpointVarName); - return (msiEndpoint.empty()) - ? nullptr - : std::unique_ptr(new CloudShellManagedIdentitySource( - clientId, options, ParseEndpointUrl(msiEndpoint, EndpointVarName))); + std::string const credSource = "Cloud Shell"; + + if (!msiEndpoint.empty()) + { + return std::unique_ptr(new CloudShellManagedIdentitySource( + clientId, options, ParseEndpointUrl(msiEndpoint, EndpointVarName, credSource))); + } + + PrintEnvNotSetUpMessage(credSource); + return nullptr; } CloudShellManagedIdentitySource::CloudShellManagedIdentitySource( @@ -200,8 +260,11 @@ std::unique_ptr AzureArcManagedIdentitySource::Create( constexpr auto EndpointVarName = "IDENTITY_ENDPOINT"; auto identityEndpoint = Environment::GetVariable(EndpointVarName); + std::string const credSource = "Azure Arc"; + if (identityEndpoint.empty() || Environment::GetVariable("IMDS_ENDPOINT").empty()) { + PrintEnvNotSetUpMessage(credSource); return nullptr; } @@ -214,7 +277,7 @@ std::unique_ptr AzureArcManagedIdentitySource::Create( } return std::unique_ptr(new AzureArcManagedIdentitySource( - options, ParseEndpointUrl(identityEndpoint, EndpointVarName))); + options, ParseEndpointUrl(identityEndpoint, EndpointVarName, credSource))); } AzureArcManagedIdentitySource::AzureArcManagedIdentitySource( @@ -313,6 +376,16 @@ std::unique_ptr ImdsManagedIdentitySource::Create( std::string const& clientId, Azure::Core::Credentials::TokenCredentialOptions const& options) { + auto const logLevel = Logger::Level::Informational; + if (Log::ShouldWrite(logLevel)) + { + Log::Write( + logLevel, + IdentityPrefix + CredPrefix + " will be created" + + WithSourceMessage("Azure Instance Metadata Service") + + ".\nSuccessful creation does not guarantee further successful token retrieval."); + } + return std::unique_ptr(new ImdsManagedIdentitySource(clientId, options)); } 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 cf80baf9e0..f7d4606b4b 100644 --- a/sdk/identity/azure-identity/src/private/managed_identity_source.hpp +++ b/sdk/identity/azure-identity/src/private/managed_identity_source.hpp @@ -28,7 +28,11 @@ namespace Azure { namespace Identity { namespace _detail { protected: _detail::TokenCache m_tokenCache; - static Core::Url ParseEndpointUrl(std::string const& url, char const* envVarName); + + static Core::Url ParseEndpointUrl( + std::string const& url, + char const* envVarName, + std::string const credSource); explicit ManagedIdentitySource( std::string clientId, @@ -61,8 +65,9 @@ namespace Azure { namespace Identity { namespace _detail { static std::unique_ptr Create( std::string const& clientId, Core::Credentials::TokenCredentialOptions const& options, - const char* endpointVarName, - const char* secretVarName); + char const* endpointVarName, + char const* secretVarName, + char const* appServiceVersion); public: Core::Credentials::AccessToken GetToken( diff --git a/sdk/identity/azure-identity/test/ut/CMakeLists.txt b/sdk/identity/azure-identity/test/ut/CMakeLists.txt index a87255464d..2c560e245f 100644 --- a/sdk/identity/azure-identity/test/ut/CMakeLists.txt +++ b/sdk/identity/azure-identity/test/ut/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable ( client_secret_credential_test.cpp credential_test_helper.cpp credential_test_helper.hpp + default_azure_credential_test.cpp environment_credential_test.cpp macro_guard_test.cpp managed_identity_credential_test.cpp diff --git a/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp b/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp index 44f5ad4cc4..4fb17964f6 100644 --- a/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp @@ -3,6 +3,7 @@ #include "azure/identity/azure_cli_credential.hpp" +#include #include #include @@ -122,13 +123,39 @@ TEST(AzureCliCredential, NotAvailable) || (!defined(WINAPI_PARTITION_DESKTOP) || WINAPI_PARTITION_DESKTOP) // not UWP TEST(AzureCliCredential, Error) { + using Azure::Core::Diagnostics::Logger; + using LogMsgVec = std::vector>; + LogMsgVec log; + Logger::SetLevel(Logger::Level::Informational); + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + AzureCliTestCredential const azCliCred( EchoCommand("ERROR: Please run 'az login' to setup account.")); + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Informational); + EXPECT_EQ( + log[0].second, + "Identity: AzureCliCredential created." + "\nSuccessful creation does not guarantee further succesful token retrieval."); + TokenRequestContext trc; trc.Scopes.push_back("https://storage.azure.com/.default"); + log.clear(); + auto const errorMsg = "Identity: AzureCliCredential didn't get the token:" + " \"ERROR: Please run 'az login' to setup account." +#if defined(AZ_PLATFORM_WINDOWS) + "\r" +#endif + "\n\""; + EXPECT_THROW(static_cast(azCliCred.GetToken(trc, {})), AuthenticationException); + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ(log[0].second, errorMsg); + + Logger::SetListener(nullptr); } TEST(AzureCliCredential, EmptyOutput) diff --git a/sdk/identity/azure-identity/test/ut/chained_token_credential_test.cpp b/sdk/identity/azure-identity/test/ut/chained_token_credential_test.cpp index f2cb6267a8..fa8297e24a 100644 --- a/sdk/identity/azure-identity/test/ut/chained_token_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/chained_token_credential_test.cpp @@ -106,40 +106,83 @@ TEST(ChainedTokenCredential, Logging) Logger::SetLevel(Logger::Level::Verbose); Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); - ChainedTokenCredential c0({}); - EXPECT_THROW(c0.GetToken({}, {}), AuthenticationException); - EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); - EXPECT_EQ(log[0].first, Logger::Level::Verbose); - EXPECT_EQ( - log[0].second, - "ChainedTokenCredential authentication did not succeed: list of sources is empty."); - - log.clear(); - auto c1 = std::make_shared(); - auto c2 = std::make_shared("Token2"); - ChainedTokenCredential cred({c1, c2}); - - EXPECT_FALSE(c1->WasInvoked); - EXPECT_FALSE(c2->WasInvoked); - - auto token = cred.GetToken({}, {}); - EXPECT_EQ(token.Token, "Token2"); - - EXPECT_TRUE(c1->WasInvoked); - EXPECT_TRUE(c2->WasInvoked); - - EXPECT_EQ(log.size(), LogMsgVec::size_type(2)); + { + ChainedTokenCredential cred({}); + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ(log[0].second, "Identity: ChainedTokenCredential: Created with 0 credentials."); + + log.clear(); + EXPECT_THROW(static_cast(cred.GetToken({}, {})), AuthenticationException); + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: ChainedTokenCredential: " + "Authentication did not succeed: List of sources is empty."); + } - EXPECT_EQ(log[0].first, Logger::Level::Verbose); - EXPECT_EQ( - log[0].second, - "ChainedTokenCredential authentication attempt with credential #1 did not succeed: " - "Test Error"); + { + log.clear(); + auto c = std::make_shared(); + ChainedTokenCredential cred({c}); + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Informational); + EXPECT_EQ(log[0].second, "Identity: ChainedTokenCredential: Created with 1 credentials."); + + log.clear(); + EXPECT_FALSE(c->WasInvoked); + + EXPECT_THROW(static_cast(cred.GetToken({}, {})), AuthenticationException); + EXPECT_TRUE(c->WasInvoked); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(2)); + + EXPECT_EQ(log[0].first, Logger::Level::Verbose); + EXPECT_EQ( + log[0].second, + "Identity: ChainedTokenCredential: Failed to get token from credential #1: " + "Test Error"); + + EXPECT_EQ(log[1].first, Logger::Level::Warning); + EXPECT_EQ( + log[1].second, + "Identity: ChainedTokenCredential: " + "Didn't succeed to get a token from any credential in the chain."); + } - EXPECT_EQ(log[1].first, Logger::Level::Informational); - EXPECT_EQ( - log[1].second, - "ChainedTokenCredential authentication attempt with credential #2 did succeed."); + { + log.clear(); + auto c1 = std::make_shared(); + auto c2 = std::make_shared("Token2"); + ChainedTokenCredential cred({c1, c2}); + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Informational); + EXPECT_EQ(log[0].second, "Identity: ChainedTokenCredential: Created with 2 credentials."); + + log.clear(); + EXPECT_FALSE(c1->WasInvoked); + EXPECT_FALSE(c2->WasInvoked); + + auto token = cred.GetToken({}, {}); + EXPECT_EQ(token.Token, "Token2"); + + EXPECT_TRUE(c1->WasInvoked); + EXPECT_TRUE(c2->WasInvoked); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(2)); + + EXPECT_EQ(log[0].first, Logger::Level::Verbose); + EXPECT_EQ( + log[0].second, + "Identity: ChainedTokenCredential: Failed to get token from credential #1: " + "Test Error"); + + EXPECT_EQ(log[1].first, Logger::Level::Informational); + EXPECT_EQ( + log[1].second, + "Identity: ChainedTokenCredential: Successfully got token from credential #2."); + } Logger::SetListener(nullptr); } diff --git a/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp b/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp new file mode 100644 index 0000000000..67e5c4aa47 --- /dev/null +++ b/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "azure/identity/default_azure_credential.hpp" + +#include "credential_test_helper.hpp" +#include + +#include + +using Azure::Identity::DefaultAzureCredential; + +using Azure::Core::Credentials::TokenCredentialOptions; +using Azure::Core::Diagnostics::Logger; +using Azure::Identity::Test::_detail::CredentialTestHelper; + +namespace { +} // namespace + +TEST(DefaultAzureCredential, LogMessages) +{ + 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)); }); + + CredentialTestHelper::SimulateTokenRequest( + [&](auto transport) { + TokenCredentialOptions options; + options.Transport.Transport = transport; + + CredentialTestHelper::EnvironmentOverride const env({ + {"AZURE_TENANT_ID", "01234567-89ab-cdef-fedc-ba8976543210"}, + {"AZURE_CLIENT_ID", "fedcba98-7654-3210-0123-456789abcdef"}, + {"AZURE_CLIENT_SECRET", "CLIENTSECRET"}, + {"AZURE_AUTHORITY_HOST", "https://microsoft.com/"}, + {"AZURE_USERNAME", ""}, + {"AZURE_PASSWORD", ""}, + {"AZURE_CLIENT_CERTIFICATE_PATH", ""}, + {"MSI_ENDPOINT", ""}, + {"MSI_SECRET", ""}, + {"IDENTITY_ENDPOINT", ""}, + {"IMDS_ENDPOINT", ""}, + {"IDENTITY_HEADER", ""}, + {"IDENTITY_SERVER_THUMBPRINT", ""}, + }); + + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(9)); + + EXPECT_EQ(log[0].first, Logger::Level::Verbose); + EXPECT_EQ( + log[0].second, + "Identity: Creating DefaultAzureCredential which combines mutiple parameterless " + "credentials " + "into a single one (by using ChainedTokenCredential)." + "\nDefaultAzureCredential is only recommended for the early stages of development, " + "and not for usage in production environment." + "\nOnce the developer focuses on the Credentials and Authentication aspects of their " + "application, DefaultAzureCredential needs to be replaced with the credential that " + "is the better fit for the application."); + + EXPECT_EQ(log[1].first, Logger::Level::Verbose); + EXPECT_EQ( + log[1].second, + "Identity: EnvironmentCredential: 'AZURE_TENANT_ID', 'AZURE_CLIENT_ID', " + "'AZURE_CLIENT_SECRET', and 'AZURE_AUTHORITY_HOST' environment variables are set, so " + "ClientSecretCredential with corresponding tenantId, clientId, clientSecret, and " + "authorityHost gets created."); + + EXPECT_EQ(log[2].first, Logger::Level::Informational); + EXPECT_EQ( + log[2].second, + "Identity: AzureCliCredential created." + "\nSuccessful creation does not guarantee further succesful token retrieval."); + + 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 App Service 2019 source."); + + EXPECT_EQ(log[4].first, Logger::Level::Verbose); + EXPECT_EQ( + log[4].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with App Service 2017 source."); + + EXPECT_EQ(log[5].first, Logger::Level::Verbose); + EXPECT_EQ( + log[5].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with Cloud Shell source."); + + EXPECT_EQ(log[6].first, Logger::Level::Verbose); + EXPECT_EQ( + log[6].second, + "Identity: ManagedIdentityCredential: Environment is not set up for the credential " + "to be created with Azure Arc source."); + + EXPECT_EQ(log[7].first, Logger::Level::Informational); + EXPECT_EQ( + log[7].second, + "Identity: ManagedIdentityCredential will be created " + "with Azure Instance Metadata Service source." + "\nSuccessful creation does not guarantee further successful token retrieval."); + + EXPECT_EQ(log[8].first, Logger::Level::Informational); + EXPECT_EQ( + log[8].second, + "Identity: DefaultAzureCredential: Created ChainedTokenCredential " + "with the following credentials: " + "EnvironmentCredential, AzureCliCredential, ManagedIdentityCredential."); + + log.clear(); + + return credential; + }, + {{"https://azure.com/.default"}}, + {"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}); + + EXPECT_EQ( + log.size(), + LogMsgVec::size_type(4)); // Request and retry policies will get their messages here as well. + + EXPECT_EQ(log[3].first, Logger::Level::Informational); + EXPECT_EQ( + log[3].second, + "Identity: DefaultAzureCredential -> ChainedTokenCredential: " + "Successfully got token from EnvironmentCredential."); + + Logger::SetListener(nullptr); +} diff --git a/sdk/identity/azure-identity/test/ut/environment_credential_test.cpp b/sdk/identity/azure-identity/test/ut/environment_credential_test.cpp index d7ebfacf18..5388b21892 100644 --- a/sdk/identity/azure-identity/test/ut/environment_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/environment_credential_test.cpp @@ -4,6 +4,7 @@ #include "azure/identity/environment_credential.hpp" #include "credential_test_helper.hpp" +#include #include @@ -14,8 +15,14 @@ using Azure::Identity::Test::_detail::CredentialTestHelper; TEST(EnvironmentCredential, RegularClientSecretCredential) { + 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; @@ -28,7 +35,19 @@ TEST(EnvironmentCredential, RegularClientSecretCredential) {"AZURE_PASSWORD", ""}, {"AZURE_CLIENT_CERTIFICATE_PATH", ""}}); - return std::make_unique(options); + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Verbose); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential: 'AZURE_TENANT_ID', 'AZURE_CLIENT_ID', " + "'AZURE_CLIENT_SECRET', and 'AZURE_AUTHORITY_HOST' environment variables are set, so " + "ClientSecretCredential with corresponding tenantId, clientId, clientSecret, and " + "authorityHost gets created."); + log.clear(); + + return credential; }, {{"https://azure.com/.default"}}, {"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}); @@ -65,12 +84,21 @@ TEST(EnvironmentCredential, RegularClientSecretCredential) using namespace std::chrono_literals; EXPECT_GE(response.AccessToken.ExpiresOn, response.EarliestExpiration + 3600s); EXPECT_LE(response.AccessToken.ExpiresOn, response.LatestExpiration + 3600s); + + Logger::SetListener(nullptr); } TEST(EnvironmentCredential, AzureStackClientSecretCredential) { + using Azure::Core::Diagnostics::Logger; + using LogMsgVec = std::vector>; + LogMsgVec log; + Logger::SetLevel(Logger::Level::Informational); // Setting it to Informational instead of Verbose + // will result in less detailed log message. + 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; @@ -83,7 +111,16 @@ TEST(EnvironmentCredential, AzureStackClientSecretCredential) {"AZURE_PASSWORD", ""}, {"AZURE_CLIENT_CERTIFICATE_PATH", ""}}); - return std::make_unique(options); + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Informational); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential gets created with ClientSecretCredential."); + log.clear(); + + return credential; }, {{"https://azure.com/.default"}}, {"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}); @@ -118,6 +155,8 @@ TEST(EnvironmentCredential, AzureStackClientSecretCredential) using namespace std::chrono_literals; EXPECT_GE(response.AccessToken.ExpiresOn, response.EarliestExpiration + 3600s); EXPECT_LE(response.AccessToken.ExpiresOn, response.LatestExpiration + 3600s); + + Logger::SetListener(nullptr); } TEST(EnvironmentCredential, Unavailable) @@ -125,8 +164,14 @@ TEST(EnvironmentCredential, Unavailable) using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; + 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)); }); + static_cast(CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { TokenCredentialOptions options; options.Transport.Transport = transport; @@ -139,22 +184,57 @@ TEST(EnvironmentCredential, Unavailable) {"AZURE_PASSWORD", ""}, {"AZURE_CLIENT_CERTIFICATE_PATH", ""}}); - return std::make_unique(options); + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential was not initialized with underlying credential: Both " + "'AZURE_TENANT_ID' and 'AZURE_CLIENT_ID', and at least one of 'AZURE_CLIENT_SECRET', " + "'AZURE_CLIENT_CERTIFICATE_PATH' needs to be set. Additionally, " + "'AZURE_AUTHORITY_HOST' could be set to override the default authority host." + " Currently:\n" + " * 'AZURE_TENANT_ID' is NOT set\n" + " * 'AZURE_CLIENT_ID' is NOT set\n" + " * 'AZURE_CLIENT_SECRET' is NOT set\n" + " * 'AZURE_CLIENT_CERTIFICATE_PATH' is NOT set\n" + " * 'AZURE_AUTHORITY_HOST' is NOT set\n"); + log.clear(); + + return credential; }, {{"https://azure.com/.default"}}, {"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}, - [](auto& credential, auto& tokenRequestContext, auto& context) { + [&](auto& credential, auto& tokenRequestContext, auto& context) { AccessToken token; EXPECT_THROW( token = credential.GetToken(tokenRequestContext, context), AuthenticationException); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential authentication unavailable. " + "See earlier EnvironmentCredential log messages for details."); + log.clear(); + return token; })); + + Logger::SetListener(nullptr); } TEST(EnvironmentCredential, ClientSecretDefaultAuthority) { + 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; @@ -167,7 +247,18 @@ TEST(EnvironmentCredential, ClientSecretDefaultAuthority) {"AZURE_PASSWORD", ""}, {"AZURE_CLIENT_CERTIFICATE_PATH", ""}}); - return std::make_unique(options); + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Verbose); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential: 'AZURE_TENANT_ID', 'AZURE_CLIENT_ID', and " + "'AZURE_CLIENT_SECRET' environment variables are set, so ClientSecretCredential with " + "corresponding tenantId, clientId, and clientSecret gets created."); + log.clear(); + + return credential; }, {{"https://azure.com/.default"}}, {"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}); @@ -204,6 +295,8 @@ TEST(EnvironmentCredential, ClientSecretDefaultAuthority) using namespace std::chrono_literals; EXPECT_GE(response.AccessToken.ExpiresOn, response.EarliestExpiration + 3600s); EXPECT_LE(response.AccessToken.ExpiresOn, response.LatestExpiration + 3600s); + + Logger::SetListener(nullptr); } TEST(EnvironmentCredential, ClientSecretNoTenantId) @@ -211,8 +304,14 @@ TEST(EnvironmentCredential, ClientSecretNoTenantId) using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; + 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)); }); + static_cast(CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { TokenCredentialOptions options; options.Transport.Transport = transport; @@ -225,16 +324,45 @@ TEST(EnvironmentCredential, ClientSecretNoTenantId) {"AZURE_PASSWORD", ""}, {"AZURE_CLIENT_CERTIFICATE_PATH", ""}}); - return std::make_unique(options); + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential was not initialized with underlying credential: Both " + "'AZURE_TENANT_ID' and 'AZURE_CLIENT_ID', and at least one of 'AZURE_CLIENT_SECRET', " + "'AZURE_CLIENT_CERTIFICATE_PATH' needs to be set. Additionally, " + "'AZURE_AUTHORITY_HOST' could be set to override the default authority host." + " Currently:\n" + " * 'AZURE_TENANT_ID' is NOT set\n" + " * 'AZURE_CLIENT_ID' is set\n" + " * 'AZURE_CLIENT_SECRET' is set\n" + " * 'AZURE_CLIENT_CERTIFICATE_PATH' is NOT set\n" + " * 'AZURE_AUTHORITY_HOST' is set\n"); + log.clear(); + + return credential; }, {{"https://azure.com/.default"}}, {"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}, - [](auto& credential, auto& tokenRequestContext, auto& context) { + [&](auto& credential, auto& tokenRequestContext, auto& context) { AccessToken token; EXPECT_THROW( token = credential.GetToken(tokenRequestContext, context), AuthenticationException); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential authentication unavailable. " + "See earlier EnvironmentCredential log messages for details."); + log.clear(); + return token; })); + + Logger::SetListener(nullptr); } TEST(EnvironmentCredential, ClientSecretNoClientId) @@ -242,8 +370,15 @@ TEST(EnvironmentCredential, ClientSecretNoClientId) using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; + using Azure::Core::Diagnostics::Logger; + using LogMsgVec = std::vector>; + LogMsgVec log; + Logger::SetLevel(Logger::Level::Warning); // Setting it to Warning instead of Verbose will result + // in less detailed Warning message. + Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); + static_cast(CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { TokenCredentialOptions options; options.Transport.Transport = transport; @@ -256,16 +391,36 @@ TEST(EnvironmentCredential, ClientSecretNoClientId) {"AZURE_PASSWORD", ""}, {"AZURE_CLIENT_CERTIFICATE_PATH", ""}}); - return std::make_unique(options); + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential was not initialized with underlying credential."); + log.clear(); + + return credential; }, {{"https://azure.com/.default"}}, {"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}, - [](auto& credential, auto& tokenRequestContext, auto& context) { + [&](auto& credential, auto& tokenRequestContext, auto& context) { AccessToken token; EXPECT_THROW( token = credential.GetToken(tokenRequestContext, context), AuthenticationException); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential authentication unavailable. " + "See earlier EnvironmentCredential log messages for details."); + log.clear(); + return token; })); + + Logger::SetListener(nullptr); } TEST(EnvironmentCredential, ClientSecretNoClientSecret) @@ -273,8 +428,14 @@ TEST(EnvironmentCredential, ClientSecretNoClientSecret) using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; + 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)); }); + static_cast(CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { TokenCredentialOptions options; options.Transport.Transport = transport; @@ -287,14 +448,43 @@ TEST(EnvironmentCredential, ClientSecretNoClientSecret) {"AZURE_PASSWORD", ""}, {"AZURE_CLIENT_CERTIFICATE_PATH", ""}}); - return std::make_unique(options); + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential was not initialized with underlying credential: Both " + "'AZURE_TENANT_ID' and 'AZURE_CLIENT_ID', and at least one of 'AZURE_CLIENT_SECRET', " + "'AZURE_CLIENT_CERTIFICATE_PATH' needs to be set. Additionally, " + "'AZURE_AUTHORITY_HOST' could be set to override the default authority host." + " Currently:\n" + " * 'AZURE_TENANT_ID' is set\n" + " * 'AZURE_CLIENT_ID' is set\n" + " * 'AZURE_CLIENT_SECRET' is NOT set\n" + " * 'AZURE_CLIENT_CERTIFICATE_PATH' is NOT set\n" + " * 'AZURE_AUTHORITY_HOST' is set\n"); + log.clear(); + + return credential; }, {{"https://azure.com/.default"}}, {"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"}, - [](auto& credential, auto& tokenRequestContext, auto& context) { + [&](auto& credential, auto& tokenRequestContext, auto& context) { AccessToken token; EXPECT_THROW( token = credential.GetToken(tokenRequestContext, context), AuthenticationException); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: EnvironmentCredential authentication unavailable. " + "See earlier EnvironmentCredential log messages for details."); + log.clear(); + return token; })); + + Logger::SetListener(nullptr); } 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 8ae5e8eb38..2923f91346 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 @@ -4,6 +4,7 @@ #include "azure/identity/managed_identity_credential.hpp" #include "credential_test_helper.hpp" +#include #include @@ -17,8 +18,14 @@ using Azure::Identity::Test::_detail::CredentialTestHelper; TEST(ManagedIdentityCredential, AppServiceV2019) { + 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; @@ -31,7 +38,16 @@ TEST(ManagedIdentityCredential, AppServiceV2019) {"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"}, }); - return std::make_unique(options); + auto credential = std::make_unique(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."); + log.clear(); + + return credential; }, {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, std::vector{ @@ -99,6 +115,8 @@ TEST(ManagedIdentityCredential, AppServiceV2019) EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + + Logger::SetListener(nullptr); } TEST(ManagedIdentityCredential, AppServiceV2019ClientId) @@ -193,12 +211,18 @@ TEST(ManagedIdentityCredential, AppServiceV2019ClientId) TEST(ManagedIdentityCredential, AppServiceV2019InvalidUrl) { + 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)); }); + using Azure::Core::Credentials::AccessToken; using Azure::Core::Credentials::AuthenticationException; using Azure::Core::Credentials::AuthenticationException; static_cast(CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { TokenCredentialOptions options; options.Transport.Transport = transport; @@ -217,10 +241,21 @@ TEST(ManagedIdentityCredential, AppServiceV2019InvalidUrl) = std::make_unique(options), AuthenticationException); + EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); + EXPECT_EQ(log[0].first, Logger::Level::Warning); + EXPECT_EQ( + log[0].second, + "Identity: ManagedIdentityCredential with App Service 2019 source: " + "Failed to create: The environment variable \'IDENTITY_ENDPOINT\' " + "contains an invalid URL."); + log.clear(); + return appServiceV2019ManagedIdentityCredential; }, {}, {"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN1\"}"})); + + Logger::SetListener(nullptr); } TEST(ManagedIdentityCredential, AppServiceV2019UnsupportedUrl) @@ -257,8 +292,14 @@ TEST(ManagedIdentityCredential, AppServiceV2019UnsupportedUrl) TEST(ManagedIdentityCredential, AppServiceV2017) { + 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; @@ -271,7 +312,24 @@ TEST(ManagedIdentityCredential, AppServiceV2017) {"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"}, }); - return std::make_unique(options); + auto credential = std::make_unique(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."); + + log.clear(); + + return credential; }, {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, std::vector{ @@ -339,6 +397,8 @@ TEST(ManagedIdentityCredential, AppServiceV2017) EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + + Logger::SetListener(nullptr); } TEST(ManagedIdentityCredential, AppServiceV2017ClientId) @@ -497,8 +557,14 @@ TEST(ManagedIdentityCredential, AppServiceV2017UnsupportedUrl) TEST(ManagedIdentityCredential, CloudShell) { + 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; @@ -511,7 +577,30 @@ TEST(ManagedIdentityCredential, CloudShell) {"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"}, }); - return std::make_unique(options); + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(3)); + + 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::Informational); + EXPECT_EQ( + log[2].second, + "Identity: ManagedIdentityCredential will be created with Cloud Shell source."); + + log.clear(); + + return credential; }, {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, std::vector{ @@ -566,6 +655,8 @@ TEST(ManagedIdentityCredential, CloudShell) EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + + Logger::SetListener(nullptr); } TEST(ManagedIdentityCredential, CloudShellClientId) @@ -681,6 +772,12 @@ TEST(ManagedIdentityCredential, CloudShellInvalidUrl) TEST(ManagedIdentityCredential, AzureArc) { + 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)); }); + { std::ofstream secretFile( "managed_identity_credential_test1.txt", std::ios_base::out | std::ios_base::trunc); @@ -703,7 +800,7 @@ TEST(ManagedIdentityCredential, AzureArc) } auto const actual = CredentialTestHelper::SimulateTokenRequest( - [](auto transport) { + [&](auto transport) { TokenCredentialOptions options; options.Transport.Transport = transport; @@ -716,7 +813,36 @@ TEST(ManagedIdentityCredential, AzureArc) {"IDENTITY_SERVER_THUMBPRINT", "0123456789abcdef0123456789abcdef01234567"}, }); - return std::make_unique(options); + auto credential = std::make_unique(options); + + EXPECT_EQ(log.size(), LogMsgVec::size_type(4)); + + 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::Informational); + EXPECT_EQ( + log[3].second, + "Identity: ManagedIdentityCredential will be created with Azure Arc source."); + + log.clear(); + + return credential; }, {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, {{HttpStatusCode::Unauthorized, @@ -829,6 +955,8 @@ TEST(ManagedIdentityCredential, AzureArc) EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + + Logger::SetListener(nullptr); } TEST(ManagedIdentityCredential, AzureArcClientId) @@ -1032,8 +1160,14 @@ TEST(ManagedIdentityCredential, AzureArcInvalidUrl) TEST(ManagedIdentityCredential, Imds) { + 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; @@ -1046,7 +1180,44 @@ TEST(ManagedIdentityCredential, Imds) {"IDENTITY_SERVER_THUMBPRINT", ""}, }); - return std::make_unique(options); + 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." + "\nSuccessful creation does not guarantee further successful token retrieval."); + + log.clear(); + + return credential; }, {{"https://azure.com/.default"}, {"https://outlook.com/.default"}, {}}, std::vector{ @@ -1114,6 +1285,8 @@ TEST(ManagedIdentityCredential, Imds) EXPECT_GE(response2.AccessToken.ExpiresOn, response2.EarliestExpiration + 9999s); EXPECT_LE(response2.AccessToken.ExpiresOn, response2.LatestExpiration + 9999s); + + Logger::SetListener(nullptr); } TEST(ManagedIdentityCredential, ImdsClientId) diff --git a/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp b/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp index 313509db46..9addc9b048 100644 --- a/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp +++ b/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp @@ -9,17 +9,46 @@ */ #include -#include -class DllExportTest final { - AZ_IDENTITY_DLLEXPORT static const bool DllExportHIncluded; -}; +#include TEST(SimplifiedHeader, identity) { using namespace Azure::Identity; - EXPECT_NO_THROW(ClientSecretCredential clientSecretCredential("", "", "")); - EXPECT_NO_THROW(EnvironmentCredential environmentCredential); - EXPECT_NO_THROW(static_cast(static_cast(nullptr))); + static_assert( + sizeof(AzureCliCredential) != 0, + "azure/identity.hpp does not include azure_cli_credential.hpp"); + + static_assert( + sizeof(ChainedTokenCredential) != 0, + "azure/identity.hpp does not include chained_token_credential.hpp"); + + static_assert( + sizeof(ClientCertificateCredential) != 0, + "azure/identity.hpp does not include client_certificate_credential.hpp"); + + static_assert( + sizeof(ClientSecretCredential) != 0, + "azure/identity.hpp does not include client_secret_credential.hpp"); + + static_assert( + sizeof(DefaultAzureCredential) != 0, + "azure/identity.hpp does not include default_azure_credential.hpp"); + +#if !defined(AZ_IDENTITY_DLLEXPORT) + static_assert(false, "azure/identity.hpp does not include dll_import_export.hpp"); +#endif + + static_assert( + sizeof(EnvironmentCredential) != 0, + "azure/identity.hpp does not include environment_credential.hpp"); + + static_assert( + sizeof(ManagedIdentityCredential) != 0, + "azure/identity.hpp does not include managed_identity_credential.hpp"); + +#if !defined(AZ_IDENTITY_RTTI) + static_assert(false, "azure/identity.hpp does not include rtti.hpp"); +#endif } From 80177c0388b9126be52d46b9a556f143f0c8cdfa Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Mon, 6 Mar 2023 15:34:32 -0800 Subject: [PATCH 2/9] Mac warning fix --- sdk/identity/azure-identity/src/chained_token_credential.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/azure-identity/src/chained_token_credential.cpp b/sdk/identity/azure-identity/src/chained_token_credential.cpp index 68a32a6218..07da4c995d 100644 --- a/sdk/identity/azure-identity/src/chained_token_credential.cpp +++ b/sdk/identity/azure-identity/src/chained_token_credential.cpp @@ -68,7 +68,7 @@ ChainedTokenCredential::ChainedTokenCredential( if (m_sourceDescriptions.empty()) { auto const sourcesSize = m_sources.size(); - for (auto i = 1; i <= sourcesSize; ++i) + for (size_t i = 1; i <= sourcesSize; ++i) { m_sourceDescriptions.push_back(std::string("credential #") + std::to_string(i)); } From 06915bba530c963755fba5e28176d22633ea6969 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Mon, 6 Mar 2023 15:37:02 -0800 Subject: [PATCH 3/9] Linux fixes --- sdk/identity/azure-identity/src/chained_token_credential.cpp | 4 ++-- sdk/identity/azure-identity/src/environment_credential.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sdk/identity/azure-identity/src/chained_token_credential.cpp b/sdk/identity/azure-identity/src/chained_token_credential.cpp index 07da4c995d..baf35a3afc 100644 --- a/sdk/identity/azure-identity/src/chained_token_credential.cpp +++ b/sdk/identity/azure-identity/src/chained_token_credential.cpp @@ -40,7 +40,7 @@ ChainedTokenCredential::ChainedTokenCredential( if (!m_sourceDescriptions.empty()) { auto const sourceDescriptionsSize = m_sourceDescriptions.size(); - for (auto i = 0; i < (sourceDescriptionsSize - 1); ++i) + for (size_t i = 0; i < (sourceDescriptionsSize - 1); ++i) { credentialsList += m_sourceDescriptions[i] + ", "; } @@ -94,7 +94,7 @@ AccessToken ChainedTokenCredential::GetToken( } else { - for (auto i = 0; i < sourcesSize; ++i) + for (size_t i = 0; i < sourcesSize; ++i) { try { diff --git a/sdk/identity/azure-identity/src/environment_credential.cpp b/sdk/identity/azure-identity/src/environment_credential.cpp index 557268819f..4ee984c4a2 100644 --- a/sdk/identity/azure-identity/src/environment_credential.cpp +++ b/sdk/identity/azure-identity/src/environment_credential.cpp @@ -146,7 +146,7 @@ EnvironmentCredential::EnvironmentCredential(TokenCredentialOptions options) {AzureClientCertificatePathEnvVarName, !clientCertificatePath.empty()}, {AzureAuthorityHostEnvVarName, !authority.empty()}, }; - for (auto const status : envVarStatus) + for (auto const& status : envVarStatus) { logMsg += std::string(" * '") + status.first + "' " + "is" + (status.second ? " " : " NOT ") + "set\n"; @@ -213,7 +213,7 @@ void PrintCredentialCreationLogMessage( std::string envVars; std::string credParams; - for (auto i = 0; i < envVarsToParamsSize - 1; + for (size_t i = 0; i < envVarsToParamsSize - 1; ++i) // not iterating over the last element for ", and". { envVars += Tick + envVarsToParams[i].first + TickComma; From 27d8da220672477b875fd55a5e453465160f85c4 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Mon, 6 Mar 2023 15:44:15 -0800 Subject: [PATCH 4/9] Mac warning --- .../inc/azure/identity/chained_token_credential.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp b/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp index a8bd474134..dfb181b024 100644 --- a/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp +++ b/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp @@ -65,9 +65,9 @@ namespace Azure { namespace Identity { std::string const& enclosingCredential, std::vector sourceDescriptions); - std::string m_logPrefix; - std::vector m_sourceDescriptions; Sources m_sources; + std::vector m_sourceDescriptions; + std::string m_logPrefix; }; }} // namespace Azure::Identity From 9f23aeacbad2d4d014dcaf63460cc05fea2f9fb5 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Mon, 6 Mar 2023 15:58:42 -0800 Subject: [PATCH 5/9] FIx spelling and platform-specific warnings --- sdk/identity/azure-identity/README.md | 6 +++--- .../inc/azure/identity/default_azure_credential.hpp | 3 ++- sdk/identity/azure-identity/src/azure_cli_credential.cpp | 2 +- .../azure-identity/test/ut/azure_cli_credential_test.cpp | 6 +++--- .../test/ut/default_azure_credential_test.cpp | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/sdk/identity/azure-identity/README.md b/sdk/identity/azure-identity/README.md index 29963391d6..9d581e7547 100644 --- a/sdk/identity/azure-identity/README.md +++ b/sdk/identity/azure-identity/README.md @@ -60,7 +60,7 @@ The `DefaultAzureCredential` attempts to authenticate via the following mechanis `DefaultAzureCredential` uses [`ChainedTokenCredential`](#chained-token-credential) that consists of a chain of `EnvironmentCredential`, `AzureCliCredential`, and `ManagedIdentityCredential`. Implementation, including the order in which credentials are applied is documented, but it may change from release to release. -`DefaultAzureCrdential` intends to provide a credential that "just works out of the box and without requiring any information", if only the environment is set up sufficiently for the credential to work. +`DefaultAzureCredential` intends to provide a credential that "just works out of the box and without requiring any information", if only the environment is set up sufficiently for the credential to work. Therefore, it could be simple to use, but since it uses a chain of credentials, it could be a bit complicated to diagnose if the environment setup is not sufficient. TO help with this, `DefaultAzureCredential` code paths are instrumented with [log messages](#troubleshooting). @@ -70,7 +70,7 @@ See the [code samples](https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/ ## Chained Token Credential -`ChainedTokenCredential` allows users to set up custom authentication flow consisting of multiple creentials. +`ChainedTokenCredential` allows users to set up custom authentication flow consisting of multiple credentials. An example below demonstrates using `ChainedTokenCredential` which will attempt to authenticate using `EnvironmentCredential`, and fall back to authenticate using `ManagedIdentityCredential`. ```cpp @@ -139,7 +139,7 @@ Configuration is attempted in the above order. For example, if values for a clie ## Troubleshooting -1. Azure Identity [SDK log messages](https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/core/azure-core#sdk-log-messages) contain disgnostic infrmation, and start with "`Identity: `". +1. Azure Identity [SDK log messages](https://github.com/Azure/azure-sdk-for-cpp/tree/main/sdk/core/azure-core#sdk-log-messages) contain disgnostic information, and start with "`Identity: `". 1. Credentials raise exceptions either when they fail to authenticate or cannot execute authentication. When a credential fails to authenticate, an `AuthenticationException` is thrown. The exception has the `what()` function that provides more information about the failure. ## Contributing diff --git a/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp b/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp index d03d2fd3b1..6bde29f95e 100644 --- a/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp +++ b/sdk/identity/azure-identity/inc/azure/identity/default_azure_credential.hpp @@ -39,7 +39,8 @@ namespace Azure { namespace Identity { * @brief Constructs `%DefaultAzureCredential`. * */ - explicit DefaultAzureCredential() : DefaultAzureCredential({}){}; + explicit DefaultAzureCredential() + : DefaultAzureCredential(Core::Credentials::TokenCredentialOptions{}){}; /** * @brief Constructs `%DefaultAzureCredential`. diff --git a/sdk/identity/azure-identity/src/azure_cli_credential.cpp b/sdk/identity/azure-identity/src/azure_cli_credential.cpp index 47a11a94f7..83e3e4b05c 100644 --- a/sdk/identity/azure-identity/src/azure_cli_credential.cpp +++ b/sdk/identity/azure-identity/src/azure_cli_credential.cpp @@ -94,7 +94,7 @@ AzureCliCredential::AzureCliCredential( logLevel, MsgPrefix + " created.\n" - "Successful creation does not guarantee further succesful token retrieval."); + "Successful creation does not guarantee further successful token retrieval."); } } diff --git a/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp b/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp index 4fb17964f6..31b740494e 100644 --- a/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/azure_cli_credential_test.cpp @@ -130,21 +130,21 @@ TEST(AzureCliCredential, Error) Logger::SetListener([&](auto lvl, auto msg) { log.push_back(std::make_pair(lvl, msg)); }); AzureCliTestCredential const azCliCred( - EchoCommand("ERROR: Please run 'az login' to setup account.")); + EchoCommand("ERROR: Please run az login to setup account.")); EXPECT_EQ(log.size(), LogMsgVec::size_type(1)); EXPECT_EQ(log[0].first, Logger::Level::Informational); EXPECT_EQ( log[0].second, "Identity: AzureCliCredential created." - "\nSuccessful creation does not guarantee further succesful token retrieval."); + "\nSuccessful creation does not guarantee further successful token retrieval."); TokenRequestContext trc; trc.Scopes.push_back("https://storage.azure.com/.default"); log.clear(); auto const errorMsg = "Identity: AzureCliCredential didn't get the token:" - " \"ERROR: Please run 'az login' to setup account." + " \"ERROR: Please run az login to setup account." #if defined(AZ_PLATFORM_WINDOWS) "\r" #endif diff --git a/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp b/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp index 67e5c4aa47..0b7826f7e0 100644 --- a/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp +++ b/sdk/identity/azure-identity/test/ut/default_azure_credential_test.cpp @@ -73,7 +73,7 @@ TEST(DefaultAzureCredential, LogMessages) EXPECT_EQ( log[2].second, "Identity: AzureCliCredential created." - "\nSuccessful creation does not guarantee further succesful token retrieval."); + "\nSuccessful creation does not guarantee further successful token retrieval."); EXPECT_EQ(log[3].first, Logger::Level::Verbose); EXPECT_EQ( From 51df426b557f8b29c2037aba53500418c73e8d07 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Mon, 6 Mar 2023 16:17:30 -0800 Subject: [PATCH 6/9] Update coverage goal --- sdk/identity/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/ci.yml b/sdk/identity/ci.yml index f3d26dd382..90162d4ebf 100644 --- a/sdk/identity/ci.yml +++ b/sdk/identity/ci.yml @@ -29,7 +29,7 @@ stages: CtestRegex: azure-identity. LiveTestCtestRegex: azure-identity. LineCoverageTarget: 95 - BranchCoverageTarget: 57 + BranchCoverageTarget: 56 Artifacts: - Name: azure-identity Path: azure-identity From e9ef2feac11f542dcbdf0f4eda762ad4d453adb5 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Tue, 7 Mar 2023 17:17:37 -0800 Subject: [PATCH 7/9] PR feedback --- .../identity/chained_token_credential.hpp | 4 +- .../src/chained_token_credential.cpp | 24 +++++----- .../src/environment_credential.cpp | 27 +++++------ .../src/managed_identity_source.cpp | 12 ++--- .../src/private/managed_identity_source.hpp | 2 +- .../test/ut/simplified_header_test.cpp | 48 ++++++------------- 6 files changed, 48 insertions(+), 69 deletions(-) diff --git a/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp b/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp index dfb181b024..9deacba2c0 100644 --- a/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp +++ b/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp @@ -63,10 +63,10 @@ namespace Azure { namespace Identity { explicit ChainedTokenCredential( Sources sources, std::string const& enclosingCredential, - std::vector sourceDescriptions); + std::vector sourcesFriendlyNames); Sources m_sources; - std::vector m_sourceDescriptions; + std::vector m_sourcesFriendlyNames; std::string m_logPrefix; }; diff --git a/sdk/identity/azure-identity/src/chained_token_credential.cpp b/sdk/identity/azure-identity/src/chained_token_credential.cpp index baf35a3afc..3a63bea9ce 100644 --- a/sdk/identity/azure-identity/src/chained_token_credential.cpp +++ b/sdk/identity/azure-identity/src/chained_token_credential.cpp @@ -26,26 +26,26 @@ ChainedTokenCredential::ChainedTokenCredential(ChainedTokenCredential::Sources s ChainedTokenCredential::ChainedTokenCredential( ChainedTokenCredential::Sources sources, std::string const& enclosingCredential, - std::vector sourceDescriptions) - : m_sources(std::move(sources)), m_sourceDescriptions(std::move(sourceDescriptions)) + std::vector sourcesFriendlyNames) + : m_sources(std::move(sources)), m_sourcesFriendlyNames(std::move(sourcesFriendlyNames)) { // LCOV_EXCL_START - AZURE_ASSERT(m_sourceDescriptions.empty() || m_sourceDescriptions.size() == m_sources.size()); + AZURE_ASSERT(m_sourcesFriendlyNames.empty() || m_sourcesFriendlyNames.size() == m_sources.size()); // LCOV_EXCL_STOP auto const logLevel = m_sources.empty() ? Logger::Level::Warning : Logger::Level::Informational; if (Log::ShouldWrite(logLevel)) { std::string credentialsList; - if (!m_sourceDescriptions.empty()) + if (!m_sourcesFriendlyNames.empty()) { - auto const sourceDescriptionsSize = m_sourceDescriptions.size(); + auto const sourceDescriptionsSize = m_sourcesFriendlyNames.size(); for (size_t i = 0; i < (sourceDescriptionsSize - 1); ++i) { - credentialsList += m_sourceDescriptions[i] + ", "; + credentialsList += m_sourcesFriendlyNames[i] + ", "; } - credentialsList += m_sourceDescriptions.back(); + credentialsList += m_sourcesFriendlyNames.back(); } Log::Write( @@ -55,7 +55,7 @@ ChainedTokenCredential::ChainedTokenCredential( ? "ChainedTokenCredential: Created" : (enclosingCredential + ": Created ChainedTokenCredential")) + " with " - + (m_sourceDescriptions.empty() + + (m_sourcesFriendlyNames.empty() ? (std::to_string(m_sources.size()) + " credentials.") : (std::string("the following credentials: ") + credentialsList + '.'))); } @@ -65,12 +65,12 @@ ChainedTokenCredential::ChainedTokenCredential( : (enclosingCredential + " -> ChainedTokenCredential")) + ": "; - if (m_sourceDescriptions.empty()) + if (m_sourcesFriendlyNames.empty()) { auto const sourcesSize = m_sources.size(); for (size_t i = 1; i <= sourcesSize; ++i) { - m_sourceDescriptions.push_back(std::string("credential #") + std::to_string(i)); + m_sourcesFriendlyNames.push_back(std::string("credential #") + std::to_string(i)); } } } @@ -106,7 +106,7 @@ AccessToken ChainedTokenCredential::GetToken( { Log::Write( logLevel, - m_logPrefix + "Successfully got token from " + m_sourceDescriptions[i] + '.'); + m_logPrefix + "Successfully got token from " + m_sourcesFriendlyNames[i] + '.'); } } @@ -120,7 +120,7 @@ AccessToken ChainedTokenCredential::GetToken( { Log::Write( logLevel, - m_logPrefix + "Failed to get token from " + m_sourceDescriptions[i] + ": " + m_logPrefix + "Failed to get token from " + m_sourcesFriendlyNames[i] + ": " + e.what()); } } diff --git a/sdk/identity/azure-identity/src/environment_credential.cpp b/sdk/identity/azure-identity/src/environment_credential.cpp index 4ee984c4a2..2d6eb2479c 100644 --- a/sdk/identity/azure-identity/src/environment_credential.cpp +++ b/sdk/identity/azure-identity/src/environment_credential.cpp @@ -24,11 +24,11 @@ using Azure::Core::Diagnostics::Logger; using Azure::Core::Diagnostics::_internal::Log; namespace { -auto const AzureTenantIdEnvVarName = "AZURE_TENANT_ID"; -auto const AzureClientIdEnvVarName = "AZURE_CLIENT_ID"; -auto const AzureClientSecretEnvVarName = "AZURE_CLIENT_SECRET"; -auto const AzureAuthorityHostEnvVarName = "AZURE_AUTHORITY_HOST"; -auto const AzureClientCertificatePathEnvVarName = "AZURE_CLIENT_CERTIFICATE_PATH"; +constexpr auto AzureTenantIdEnvVarName = "AZURE_TENANT_ID"; +constexpr auto AzureClientIdEnvVarName = "AZURE_CLIENT_ID"; +constexpr auto AzureClientSecretEnvVarName = "AZURE_CLIENT_SECRET"; +constexpr auto AzureAuthorityHostEnvVarName = "AZURE_AUTHORITY_HOST"; +constexpr auto AzureClientCertificatePathEnvVarName = "AZURE_CLIENT_CERTIFICATE_PATH"; std::string const LogMsgPrefix = "Identity: EnvironmentCredential"; @@ -124,8 +124,7 @@ EnvironmentCredential::EnvironmentCredential(TokenCredentialOptions options) auto const logLevel = Logger::Level::Warning; if (Log::ShouldWrite(logLevel)) { - std::string const basicMessage - = LogMsgPrefix + " was not initialized with underlying credential"; + auto const basicMessage = LogMsgPrefix + " was not initialized with underlying credential"; if (!Log::ShouldWrite(Logger::Level::Verbose)) { @@ -133,7 +132,7 @@ EnvironmentCredential::EnvironmentCredential(TokenCredentialOptions options) } else { - std::string logMsg = basicMessage + ": Both '" + AzureTenantIdEnvVarName + "' and '" + auto logMsg = basicMessage + ": Both '" + AzureTenantIdEnvVarName + "' and '" + AzureClientIdEnvVarName + "', and at least one of '" + AzureClientSecretEnvVarName + "', '" + AzureClientCertificatePathEnvVarName + "' needs to be set. Additionally, '" + AzureAuthorityHostEnvVarName @@ -164,7 +163,7 @@ AccessToken EnvironmentCredential::GetToken( { if (!m_credentialImpl) { - std::string const AuthUnavailable = LogMsgPrefix + " authentication unavailable. "; + auto const AuthUnavailable = LogMsgPrefix + " authentication unavailable. "; { auto const logLevel = Logger::Level::Warning; @@ -206,9 +205,9 @@ void PrintCredentialCreationLogMessage( AZURE_ASSERT(envVarsToParamsSize > 1); // LCOV_EXCL_STOP - std::string const Tick = "'"; - std::string const Comma = ", "; - std::string const TickComma = Tick + Comma; + constexpr auto Tick = "'"; + constexpr auto Comma = ", "; + std::string const And = "and "; std::string envVars; @@ -216,8 +215,8 @@ void PrintCredentialCreationLogMessage( for (size_t i = 0; i < envVarsToParamsSize - 1; ++i) // not iterating over the last element for ", and". { - envVars += Tick + envVarsToParams[i].first + TickComma; - credParams += envVarsToParams[i].second + Comma; + envVars += Tick + std::string(envVarsToParams[i].first) + Tick + Comma; + credParams += std::string(envVarsToParams[i].second) + Comma; } envVars += And + Tick + envVarsToParams.back().first + Tick; diff --git a/sdk/identity/azure-identity/src/managed_identity_source.cpp b/sdk/identity/azure-identity/src/managed_identity_source.cpp index 43b749e10b..de7e8e6c3c 100644 --- a/sdk/identity/azure-identity/src/managed_identity_source.cpp +++ b/sdk/identity/azure-identity/src/managed_identity_source.cpp @@ -20,11 +20,11 @@ using Azure::Core::Diagnostics::_internal::Log; namespace { std::string const IdentityPrefix = "Identity: "; -std::string const CredPrefix = "ManagedIdentityCredential"; +constexpr auto CredPrefix = "ManagedIdentityCredential"; std::string WithSourceMessage(std::string const& credSource) { - return std::string(" with ") + credSource + " source"; + return " with " + credSource + " source"; } void PrintEnvNotSetUpMessage(std::string const& credSource) @@ -43,7 +43,7 @@ void PrintEnvNotSetUpMessage(std::string const& credSource) Azure::Core::Url ManagedIdentitySource::ParseEndpointUrl( std::string const& url, char const* envVarName, - std::string const credSource) + std::string const& credSource) { using Azure::Core::Url; using Azure::Core::Credentials::AuthenticationException; @@ -185,15 +185,15 @@ 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()) { return std::unique_ptr(new CloudShellManagedIdentitySource( - clientId, options, ParseEndpointUrl(msiEndpoint, EndpointVarName, credSource))); + clientId, options, ParseEndpointUrl(msiEndpoint, EndpointVarName, CredSource))); } - PrintEnvNotSetUpMessage(credSource); + PrintEnvNotSetUpMessage(CredSource); return nullptr; } 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 f7d4606b4b..9972ce904a 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,7 @@ namespace Azure { namespace Identity { namespace _detail { static Core::Url ParseEndpointUrl( std::string const& url, char const* envVarName, - std::string const credSource); + std::string const& credSource); explicit ManagedIdentitySource( std::string clientId, diff --git a/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp b/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp index 9addc9b048..a198a0387e 100644 --- a/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp +++ b/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp @@ -12,43 +12,23 @@ #include -TEST(SimplifiedHeader, identity) -{ - using namespace Azure::Identity; - - static_assert( - sizeof(AzureCliCredential) != 0, - "azure/identity.hpp does not include azure_cli_credential.hpp"); - - static_assert( - sizeof(ChainedTokenCredential) != 0, - "azure/identity.hpp does not include chained_token_credential.hpp"); - - static_assert( - sizeof(ClientCertificateCredential) != 0, - "azure/identity.hpp does not include client_certificate_credential.hpp"); - - static_assert( - sizeof(ClientSecretCredential) != 0, - "azure/identity.hpp does not include client_secret_credential.hpp"); - - static_assert( - sizeof(DefaultAzureCredential) != 0, - "azure/identity.hpp does not include default_azure_credential.hpp"); - #if !defined(AZ_IDENTITY_DLLEXPORT) - static_assert(false, "azure/identity.hpp does not include dll_import_export.hpp"); +#error "azure/identity.hpp does not include dll_import_export.hpp" #endif - static_assert( - sizeof(EnvironmentCredential) != 0, - "azure/identity.hpp does not include environment_credential.hpp"); - - static_assert( - sizeof(ManagedIdentityCredential) != 0, - "azure/identity.hpp does not include managed_identity_credential.hpp"); - #if !defined(AZ_IDENTITY_RTTI) - static_assert(false, "azure/identity.hpp does not include rtti.hpp"); +#error "azure/identity.hpp does not include rtti.hpp" #endif + +TEST(SimplifiedHeader, identity) +{ + using namespace Azure::Identity; + + static_cast(sizeof(AzureCliCredential)); + static_cast(sizeof(ChainedTokenCredential)); + static_cast(sizeof(ClientCertificateCredential)); + static_cast(sizeof(ClientSecretCredential)); + static_cast(sizeof(DefaultAzureCredential)); + static_cast(sizeof(EnvironmentCredential)); + static_cast(sizeof(ManagedIdentityCredential)); } From 3df03dfa07cf563df09cc06fee54d411657341cc Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Tue, 7 Mar 2023 18:41:04 -0800 Subject: [PATCH 8/9] AZ_RTTI macro can't be checked the way it is made - it is supposed to not be defined when RTTI is off --- .../azure-identity/test/ut/simplified_header_test.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp b/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp index a198a0387e..fa2e67b758 100644 --- a/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp +++ b/sdk/identity/azure-identity/test/ut/simplified_header_test.cpp @@ -16,10 +16,6 @@ #error "azure/identity.hpp does not include dll_import_export.hpp" #endif -#if !defined(AZ_IDENTITY_RTTI) -#error "azure/identity.hpp does not include rtti.hpp" -#endif - TEST(SimplifiedHeader, identity) { using namespace Azure::Identity; From 2978a356c2dfd55e85551328c7d6875a7cef1148 Mon Sep 17 00:00:00 2001 From: Anton Kolesnyk Date: Tue, 7 Mar 2023 19:57:39 -0800 Subject: [PATCH 9/9] Comment on friend --- .../inc/azure/identity/chained_token_credential.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp b/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp index 9deacba2c0..726735c2ef 100644 --- a/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp +++ b/sdk/identity/azure-identity/inc/azure/identity/chained_token_credential.hpp @@ -24,6 +24,8 @@ namespace Azure { namespace Identity { * */ class ChainedTokenCredential final : public Core::Credentials::TokenCredential { + // Friend declaration is needed for DefaultAzureCredential to access ChainedTokenCredential's + // private constructor built to be used specifically by it. friend class DefaultAzureCredential; public: