azure-sdk-for-cpp/sdk/identity/azure-identity/src/managed_identity_source.cpp
Anton Kolesnyk 83f736d8ad
Simpler identity logging (#4455)
* Simpler identity logging

* Even simpler

* Remove refactoring artifact

* Cosmetic change

* foreach

---------

Co-authored-by: Anton Kolesnyk <antkmsft@users.noreply.github.com>
2023-03-16 12:50:15 -07:00

437 lines
14 KiB
C++

// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "private/managed_identity_source.hpp"
#include <azure/core/internal/environment.hpp>
#include <azure/core/internal/diagnostics/log.hpp>
#include <fstream>
#include <iterator>
#include <stdexcept>
#include <utility>
using namespace Azure::Identity::_detail;
using Azure::Core::_internal::Environment;
using Azure::Core::Diagnostics::Logger;
using Azure::Core::Diagnostics::_internal::Log;
namespace {
constexpr auto IdentityPrefix = "Identity: ";
std::string WithSourceMessage(std::string const& credSource)
{
return " with " + credSource + " source";
}
void PrintEnvNotSetUpMessage(std::string const& credName, std::string const& credSource)
{
Log::Write(
Logger::Level::Verbose,
IdentityPrefix + credName + ": Environment is not set up for the credential to be created"
+ WithSourceMessage(credSource) + '.');
}
} // namespace
Azure::Core::Url ManagedIdentitySource::ParseEndpointUrl(
std::string const& credName,
std::string const& url,
char const* envVarName,
std::string const& credSource)
{
using Azure::Core::Url;
using Azure::Core::Credentials::AuthenticationException;
try
{
auto const endpointUrl = Url(url);
Log::Write(
Logger::Level::Informational,
IdentityPrefix + credName + " will be created" + WithSourceMessage(credSource) + '.');
return endpointUrl;
}
catch (std::invalid_argument const&)
{
}
catch (std::out_of_range const&)
{
}
auto const errorMessage = credName + WithSourceMessage(credSource)
+ ": Failed to create: The environment variable \'" + envVarName
+ "\' contains an invalid URL.";
Log::Write(Logger::Level::Warning, IdentityPrefix + errorMessage);
throw AuthenticationException(errorMessage);
}
template <typename T>
std::unique_ptr<ManagedIdentitySource> AppServiceManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
Azure::Core::Credentials::TokenCredentialOptions const& options,
char const* endpointVarName,
char const* secretVarName,
char const* appServiceVersion)
{
auto const msiEndpoint = Environment::GetVariable(endpointVarName);
auto const msiSecret = Environment::GetVariable(secretVarName);
auto const credSource = std::string("App Service ") + appServiceVersion;
if (!msiEndpoint.empty() && !msiSecret.empty())
{
return std::unique_ptr<ManagedIdentitySource>(new T(
clientId,
options,
ParseEndpointUrl(credName, msiEndpoint, endpointVarName, credSource),
msiSecret));
}
PrintEnvNotSetUpMessage(credName, credSource);
return nullptr;
}
AppServiceManagedIdentitySource::AppServiceManagedIdentitySource(
std::string const& clientId,
Azure::Core::Credentials::TokenCredentialOptions const& options,
Azure::Core::Url endpointUrl,
std::string const& secret,
std::string const& apiVersion,
std::string const& secretHeaderName,
std::string const& clientIdHeaderName)
: ManagedIdentitySource(clientId, endpointUrl.GetHost(), options),
m_request(Azure::Core::Http::HttpMethod::Get, std::move(endpointUrl))
{
{
using Azure::Core::Url;
auto& url = m_request.GetUrl();
url.AppendQueryParameter("api-version", apiVersion);
if (!clientId.empty())
{
url.AppendQueryParameter(clientIdHeaderName, clientId);
}
}
m_request.SetHeader(secretHeaderName, secret);
}
Azure::Core::Credentials::AccessToken AppServiceManagedIdentitySource::GetToken(
Azure::Core::Credentials::TokenRequestContext const& tokenRequestContext,
Azure::Core::Context const& context) const
{
std::string scopesStr;
{
auto const& scopes = tokenRequestContext.Scopes;
if (!scopes.empty())
{
scopesStr = TokenCredentialImpl::FormatScopes(scopes, true);
}
}
// TokenCache::GetToken() and TokenCredentialImpl::GetToken() can only use the lambda argument
// when they are being executed. They are not supposed to keep a reference to lambda argument to
// call it later. Therefore, any capture made here will outlive the possible time frame when the
// lambda might get called.
return m_tokenCache.GetToken(scopesStr, tokenRequestContext.MinimumExpiration, [&]() {
return TokenCredentialImpl::GetToken(context, [&]() {
auto request = std::make_unique<TokenRequest>(m_request);
if (!scopesStr.empty())
{
request->HttpRequest.GetUrl().AppendQueryParameter("resource", scopesStr);
}
return request;
});
});
}
std::unique_ptr<ManagedIdentitySource> AppServiceV2017ManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
Core::Credentials::TokenCredentialOptions const& options)
{
return AppServiceManagedIdentitySource::Create<AppServiceV2017ManagedIdentitySource>(
credName, clientId, options, "MSI_ENDPOINT", "MSI_SECRET", "2017");
}
std::unique_ptr<ManagedIdentitySource> AppServiceV2019ManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
Core::Credentials::TokenCredentialOptions const& options)
{
return AppServiceManagedIdentitySource::Create<AppServiceV2019ManagedIdentitySource>(
credName, clientId, options, "IDENTITY_ENDPOINT", "IDENTITY_HEADER", "2019");
}
std::unique_ptr<ManagedIdentitySource> CloudShellManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
Azure::Core::Credentials::TokenCredentialOptions const& options)
{
constexpr auto EndpointVarName = "MSI_ENDPOINT";
auto msiEndpoint = Environment::GetVariable(EndpointVarName);
std::string const CredSource = "Cloud Shell";
if (!msiEndpoint.empty())
{
return std::unique_ptr<ManagedIdentitySource>(new CloudShellManagedIdentitySource(
clientId, options, ParseEndpointUrl(credName, msiEndpoint, EndpointVarName, CredSource)));
}
PrintEnvNotSetUpMessage(credName, CredSource);
return nullptr;
}
CloudShellManagedIdentitySource::CloudShellManagedIdentitySource(
std::string const& clientId,
Azure::Core::Credentials::TokenCredentialOptions const& options,
Azure::Core::Url endpointUrl)
: ManagedIdentitySource(clientId, endpointUrl.GetHost(), options), m_url(std::move(endpointUrl))
{
using Azure::Core::Url;
if (!clientId.empty())
{
m_body = std::string("client_id=" + Url::Encode(clientId));
}
}
Azure::Core::Credentials::AccessToken CloudShellManagedIdentitySource::GetToken(
Azure::Core::Credentials::TokenRequestContext const& tokenRequestContext,
Azure::Core::Context const& context) const
{
std::string scopesStr;
{
auto const& scopes = tokenRequestContext.Scopes;
if (!scopes.empty())
{
scopesStr = TokenCredentialImpl::FormatScopes(scopes, true);
}
}
// TokenCache::GetToken() and TokenCredentialImpl::GetToken() can only use the lambda argument
// when they are being executed. They are not supposed to keep a reference to lambda argument to
// call it later. Therefore, any capture made here will outlive the possible time frame when the
// lambda might get called.
return m_tokenCache.GetToken(scopesStr, tokenRequestContext.MinimumExpiration, [&]() {
return TokenCredentialImpl::GetToken(context, [&]() {
using Azure::Core::Url;
using Azure::Core::Http::HttpMethod;
std::string resource;
if (!scopesStr.empty())
{
resource = "resource=" + scopesStr;
if (!m_body.empty())
{
resource += "&";
}
}
auto request = std::make_unique<TokenRequest>(HttpMethod::Post, m_url, resource + m_body);
request->HttpRequest.SetHeader("Metadata", "true");
return request;
});
});
}
std::unique_ptr<ManagedIdentitySource> AzureArcManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
Azure::Core::Credentials::TokenCredentialOptions const& options)
{
using Azure::Core::Credentials::AuthenticationException;
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(credName, credSource);
return nullptr;
}
if (!clientId.empty())
{
throw AuthenticationException(
"User assigned identity is not supported by the Azure Arc Managed Identity Endpoint. "
"To authenticate with the system assigned identity, omit the client ID "
"when constructing the ManagedIdentityCredential.");
}
return std::unique_ptr<ManagedIdentitySource>(new AzureArcManagedIdentitySource(
options, ParseEndpointUrl(credName, identityEndpoint, EndpointVarName, credSource)));
}
AzureArcManagedIdentitySource::AzureArcManagedIdentitySource(
Azure::Core::Credentials::TokenCredentialOptions const& options,
Azure::Core::Url endpointUrl)
: ManagedIdentitySource(std::string(), endpointUrl.GetHost(), options),
m_url(std::move(endpointUrl))
{
m_url.AppendQueryParameter("api-version", "2019-11-01");
}
Azure::Core::Credentials::AccessToken AzureArcManagedIdentitySource::GetToken(
Azure::Core::Credentials::TokenRequestContext const& tokenRequestContext,
Azure::Core::Context const& context) const
{
std::string scopesStr;
{
auto const& scopes = tokenRequestContext.Scopes;
if (!scopes.empty())
{
scopesStr = TokenCredentialImpl::FormatScopes(scopes, true);
}
}
auto const createRequest = [&]() {
using Azure::Core::Http::HttpMethod;
using Azure::Core::Http::Request;
auto request = std::make_unique<TokenRequest>(Request(HttpMethod::Get, m_url));
{
auto& httpRequest = request->HttpRequest;
httpRequest.SetHeader("Metadata", "true");
if (!scopesStr.empty())
{
httpRequest.GetUrl().AppendQueryParameter("resource", scopesStr);
}
}
return request;
};
// TokenCache::GetToken() and TokenCredentialImpl::GetToken() can only use the lambda argument
// when they are being executed. They are not supposed to keep a reference to lambda argument to
// call it later. Therefore, any capture made here will outlive the possible time frame when the
// lambda might get called.
return m_tokenCache.GetToken(scopesStr, tokenRequestContext.MinimumExpiration, [&]() {
return TokenCredentialImpl::GetToken(
context,
createRequest,
[&](auto const statusCode, auto const& response) -> std::unique_ptr<TokenRequest> {
using Core::Credentials::AuthenticationException;
using Core::Http::HttpStatusCode;
if (statusCode != HttpStatusCode::Unauthorized)
{
return nullptr;
}
auto const& headers = response.GetHeaders();
auto authHeader = headers.find("WWW-Authenticate");
if (authHeader == headers.end())
{
throw AuthenticationException(
"Did not receive expected WWW-Authenticate header "
"in the response from Azure Arc Managed Identity Endpoint.");
}
constexpr auto ChallengeValueSeparator = '=';
auto const& challenge = authHeader->second;
auto eq = challenge.find(ChallengeValueSeparator);
if (eq == std::string::npos
|| challenge.find(ChallengeValueSeparator, eq + 1) != std::string::npos)
{
throw AuthenticationException(
"The WWW-Authenticate header in the response from Azure Arc "
"Managed Identity Endpoint did not match the expected format.");
}
auto request = createRequest();
std::ifstream secretFile(challenge.substr(eq + 1));
request->HttpRequest.SetHeader(
"Authorization",
"Basic "
+ std::string(
std::istreambuf_iterator<char>(secretFile),
std::istreambuf_iterator<char>()));
return request;
});
});
}
std::unique_ptr<ManagedIdentitySource> ImdsManagedIdentitySource::Create(
std::string const& credName,
std::string const& clientId,
Azure::Core::Credentials::TokenCredentialOptions const& options)
{
Log::Write(
Logger::Level::Informational,
IdentityPrefix + credName + " will be created"
+ WithSourceMessage("Azure Instance Metadata Service")
+ ".\nSuccessful creation does not guarantee further successful token retrieval.");
return std::unique_ptr<ManagedIdentitySource>(new ImdsManagedIdentitySource(clientId, options));
}
ImdsManagedIdentitySource::ImdsManagedIdentitySource(
std::string const& clientId,
Azure::Core::Credentials::TokenCredentialOptions const& options)
: ManagedIdentitySource(clientId, std::string(), options),
m_request(
Azure::Core::Http::HttpMethod::Get,
Azure::Core::Url("http://169.254.169.254/metadata/identity/oauth2/token"))
{
{
using Azure::Core::Url;
auto& url = m_request.GetUrl();
url.AppendQueryParameter("api-version", "2018-02-01");
if (!clientId.empty())
{
url.AppendQueryParameter("client_id", clientId);
}
}
m_request.SetHeader("Metadata", "true");
}
Azure::Core::Credentials::AccessToken ImdsManagedIdentitySource::GetToken(
Azure::Core::Credentials::TokenRequestContext const& tokenRequestContext,
Azure::Core::Context const& context) const
{
std::string scopesStr;
{
auto const& scopes = tokenRequestContext.Scopes;
if (!scopes.empty())
{
scopesStr = TokenCredentialImpl::FormatScopes(scopes, true);
}
}
// TokenCache::GetToken() and TokenCredentialImpl::GetToken() can only use the lambda argument
// when they are being executed. They are not supposed to keep a reference to lambda argument to
// call it later. Therefore, any capture made here will outlive the possible time frame when the
// lambda might get called.
return m_tokenCache.GetToken(scopesStr, tokenRequestContext.MinimumExpiration, [&]() {
return TokenCredentialImpl::GetToken(context, [&]() {
auto request = std::make_unique<TokenRequest>(m_request);
if (!scopesStr.empty())
{
request->HttpRequest.GetUrl().AppendQueryParameter("resource", scopesStr);
}
return request;
});
});
}