Add support for providing an object ID to ManagedIdentityCredential. (#5910)
* Add support for providing an object ID to ManagedIdentityCredential. * Update cspell checks. * Add support for creating a Uuid from a string, and use that as the MICred overload for objectId. * Add a remark comment to make it clear that object and client ids are not interchangeable. * Remove the non-const ToString() as that isn't required. * Update tests to use valid hex Uuid values. * Use a discriminated union design approach with a ManagedIdentityType object and ManagedIdentityIdType enum. * Fix typo and remove Uuid CreateFromString. * Address PR feedback. * Update doc comment. * Update comments and exception message to consistently use hyphens between user/system and assigned.
This commit is contained in:
parent
758460d618
commit
54a7bc363b
@ -5,6 +5,7 @@
|
||||
### Features Added
|
||||
|
||||
- Request logs to now include the `accept-range`, `content-range`, `range`, `WWW-Authenticate`, `x-ms-date`, `x-ms-error-code`, `x-ms-range`, and `x-ms-version` headers.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
### Bugs Fixed
|
||||
|
||||
@ -33,9 +33,9 @@ namespace Azure { namespace Core {
|
||||
public:
|
||||
/**
|
||||
* @brief Gets Uuid as a string.
|
||||
* @details A string is in canonical format (4-2-2-2-6 lowercase hex and dashes only).
|
||||
* @details A string is in canonical format (8-4-4-4-12 lowercase hex and dashes only).
|
||||
*/
|
||||
std::string ToString();
|
||||
std::string ToString() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the binary value of the Uuid for consumption by clients who need non-string
|
||||
|
||||
@ -17,7 +17,7 @@ static thread_local std::mt19937_64 randomGenerator(std::random_device{}());
|
||||
#endif
|
||||
|
||||
namespace Azure { namespace Core {
|
||||
std::string Uuid::ToString()
|
||||
std::string Uuid::ToString() const
|
||||
{
|
||||
// Guid is 36 characters
|
||||
// Add one byte for the \0
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
### Features Added
|
||||
|
||||
- Added support for providing an object ID to `ManagedIdentityCredential`.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Previously, if a clientId was specified for Cloud Shell managed identity, which is not supported, the clientId was passed into the request body. Now, an exception will be thrown if a clientId is specified for Cloud Shell managed identity.
|
||||
|
||||
@ -42,6 +42,91 @@ namespace Azure { namespace Identity {
|
||||
std::string ToString() const { return m_resourceId; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The type of managed identity identifier depending on how the managed identity is
|
||||
* configured.
|
||||
*
|
||||
* @remark This can either be system-assigned, or user-assigned which corresponds to an identifier
|
||||
* that represents either client ID, resource ID, or object ID, depending on how the managed
|
||||
* identity is configured.
|
||||
*/
|
||||
enum class ManagedIdentityIdType
|
||||
{
|
||||
SystemAssigned,
|
||||
ClientId,
|
||||
ObjectId,
|
||||
ResourceId,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The type of managed identity and its corresponding identifier.
|
||||
*
|
||||
* @remark This class holds the type and unique identifier for either a system or user-assigned
|
||||
* managed identity.
|
||||
*/
|
||||
class ManagedIdentityType final {
|
||||
private:
|
||||
ManagedIdentityIdType m_idType;
|
||||
std::string m_id;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs the type of managed identity.
|
||||
*
|
||||
* @remark This defaults to ManagedIdentityIdType::SystemAssigned.
|
||||
*/
|
||||
explicit ManagedIdentityType() : m_idType(ManagedIdentityIdType::SystemAssigned) {}
|
||||
|
||||
/**
|
||||
* @brief Constructs the type of managed identity.
|
||||
*
|
||||
* @param idType The type of the managed identity identifier.
|
||||
* @param id The value of the managed identity identifier. This can be either a client ID,
|
||||
* resource ID, or object ID.
|
||||
*
|
||||
* @remark For ManagedIdentityIdType::SystemAssigned, the id must be an empty string.
|
||||
*
|
||||
* @remark Make sure the type of ID matches the value of the ID. For example, the client
|
||||
* ID and object ID are NOT interchangeable, even though they are both Uuid values.
|
||||
*/
|
||||
explicit ManagedIdentityType(ManagedIdentityIdType idType, std::string id)
|
||||
: m_idType(idType), m_id(id)
|
||||
{
|
||||
if (idType == ManagedIdentityIdType::SystemAssigned && !id.empty())
|
||||
{
|
||||
throw std::invalid_argument(
|
||||
"There is no need to provide an ID (such as client, object, or resource ID) if you are "
|
||||
"using system-assigned managed identity.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the identifier for a user-assigned managed identity.
|
||||
*
|
||||
* @remark In the case of system-assigned managed identity, this will return an empty string.
|
||||
*/
|
||||
std::string const& GetId() const { return m_id; }
|
||||
|
||||
/**
|
||||
* @brief Gets the type of identifier used for the managed identity, depending on how it is
|
||||
* configured.
|
||||
*/
|
||||
ManagedIdentityIdType GetManagedIdentityIdType() const { return m_idType; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Options for managed identity credential.
|
||||
*
|
||||
*/
|
||||
struct ManagedIdentityCredentialOptions final : public Core::Credentials::TokenCredentialOptions
|
||||
{
|
||||
/**
|
||||
* @brief Specifies the type of managed identity and its corresponding identifier, based on how
|
||||
* it was configured.
|
||||
*/
|
||||
ManagedIdentityType IdentityType;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Attempts authentication using a managed identity that has been assigned to the
|
||||
* deployment environment. This authentication type works in Azure VMs, App Service and Azure
|
||||
@ -71,6 +156,14 @@ namespace Azure { namespace Identity {
|
||||
Azure::Core::Credentials::TokenCredentialOptions const& options
|
||||
= Azure::Core::Credentials::TokenCredentialOptions());
|
||||
|
||||
/**
|
||||
* @brief Constructs a Managed Identity Credential.
|
||||
*
|
||||
* @param options Options for token retrieval.
|
||||
*/
|
||||
explicit ManagedIdentityCredential(
|
||||
Azure::Identity::ManagedIdentityCredentialOptions const& options);
|
||||
|
||||
/**
|
||||
* @brief Constructs an instance of ManagedIdentityCredential capable of authenticating a
|
||||
* resource with a user-assigned managed identity.
|
||||
|
||||
@ -11,6 +11,7 @@ namespace {
|
||||
std::unique_ptr<_detail::ManagedIdentitySource> CreateManagedIdentitySource(
|
||||
std::string const& credentialName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Azure::Core::Credentials::TokenCredentialOptions const& options)
|
||||
{
|
||||
@ -19,6 +20,7 @@ std::unique_ptr<_detail::ManagedIdentitySource> CreateManagedIdentitySource(
|
||||
static std::unique_ptr<ManagedIdentitySource> (*managedIdentitySourceCreate[])(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
TokenCredentialOptions const& options)
|
||||
= {AppServiceV2019ManagedIdentitySource::Create,
|
||||
@ -31,7 +33,7 @@ std::unique_ptr<_detail::ManagedIdentitySource> CreateManagedIdentitySource(
|
||||
// For that reason, it is not possible to cover that execution branch in tests.
|
||||
for (auto create : managedIdentitySourceCreate)
|
||||
{
|
||||
if (auto source = create(credentialName, clientId, resourceId, options))
|
||||
if (auto source = create(credentialName, clientId, objectId, resourceId, options))
|
||||
{
|
||||
return source;
|
||||
}
|
||||
@ -49,7 +51,34 @@ ManagedIdentityCredential::ManagedIdentityCredential(
|
||||
Azure::Core::Credentials::TokenCredentialOptions const& options)
|
||||
: TokenCredential("ManagedIdentityCredential")
|
||||
{
|
||||
m_managedIdentitySource = CreateManagedIdentitySource(GetCredentialName(), clientId, {}, options);
|
||||
m_managedIdentitySource
|
||||
= CreateManagedIdentitySource(GetCredentialName(), clientId, {}, {}, options);
|
||||
}
|
||||
|
||||
ManagedIdentityCredential::ManagedIdentityCredential(
|
||||
Azure::Identity::ManagedIdentityCredentialOptions const& options)
|
||||
: TokenCredential("ManagedIdentityCredential")
|
||||
{
|
||||
ManagedIdentityIdType idType = options.IdentityType.GetManagedIdentityIdType();
|
||||
switch (idType)
|
||||
{
|
||||
case ManagedIdentityIdType::SystemAssigned:
|
||||
m_managedIdentitySource
|
||||
= CreateManagedIdentitySource(GetCredentialName(), {}, {}, {}, options);
|
||||
break;
|
||||
case ManagedIdentityIdType::ClientId:
|
||||
m_managedIdentitySource = CreateManagedIdentitySource(
|
||||
GetCredentialName(), options.IdentityType.GetId(), {}, {}, options);
|
||||
break;
|
||||
case ManagedIdentityIdType::ObjectId:
|
||||
m_managedIdentitySource = CreateManagedIdentitySource(
|
||||
GetCredentialName(), {}, options.IdentityType.GetId(), {}, options);
|
||||
break;
|
||||
case ManagedIdentityIdType::ResourceId:
|
||||
m_managedIdentitySource = CreateManagedIdentitySource(
|
||||
GetCredentialName(), {}, {}, options.IdentityType.GetId(), options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ManagedIdentityCredential::ManagedIdentityCredential(
|
||||
@ -58,7 +87,7 @@ ManagedIdentityCredential::ManagedIdentityCredential(
|
||||
: TokenCredential("ManagedIdentityCredential")
|
||||
{
|
||||
m_managedIdentitySource
|
||||
= CreateManagedIdentitySource(GetCredentialName(), {}, resourceId.ToString(), options);
|
||||
= CreateManagedIdentitySource(GetCredentialName(), {}, {}, resourceId.ToString(), options);
|
||||
}
|
||||
|
||||
ManagedIdentityCredential::ManagedIdentityCredential(
|
||||
|
||||
@ -140,6 +140,7 @@ template <typename T>
|
||||
std::unique_ptr<ManagedIdentitySource> AppServiceManagedIdentitySource::Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Azure::Core::Credentials::TokenCredentialOptions const& options,
|
||||
char const* endpointVarName,
|
||||
@ -155,6 +156,7 @@ std::unique_ptr<ManagedIdentitySource> AppServiceManagedIdentitySource::Create(
|
||||
{
|
||||
return std::unique_ptr<ManagedIdentitySource>(new T(
|
||||
clientId,
|
||||
objectId,
|
||||
resourceId,
|
||||
options,
|
||||
ParseEndpointUrl(credName, msiEndpoint, endpointVarName, credSource),
|
||||
@ -167,6 +169,7 @@ std::unique_ptr<ManagedIdentitySource> AppServiceManagedIdentitySource::Create(
|
||||
|
||||
AppServiceManagedIdentitySource::AppServiceManagedIdentitySource(
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Azure::Core::Credentials::TokenCredentialOptions const& options,
|
||||
Azure::Core::Url endpointUrl,
|
||||
@ -183,13 +186,19 @@ AppServiceManagedIdentitySource::AppServiceManagedIdentitySource(
|
||||
|
||||
url.AppendQueryParameter("api-version", apiVersion);
|
||||
|
||||
// Only one of clientId or resourceId will be set to a non-empty value.
|
||||
// Only one of clientId, objectId, or resourceId will be set to a non-empty value.
|
||||
// AppService uses mi_res_id, and not msi_res_id:
|
||||
// https://learn.microsoft.com/azure/app-service/overview-managed-identity?tabs=portal%2Chttp#rest-endpoint-reference
|
||||
// Based on the App Service documentation, using principal_id for the query parameter name here
|
||||
// instead of object_id (which is used as an alias).
|
||||
if (!clientId.empty())
|
||||
{
|
||||
url.AppendQueryParameter(clientIdHeaderName, clientId);
|
||||
}
|
||||
else if (!objectId.empty())
|
||||
{
|
||||
url.AppendQueryParameter("principal_id", objectId);
|
||||
}
|
||||
else if (!resourceId.empty())
|
||||
{
|
||||
url.AppendQueryParameter("mi_res_id", resourceId);
|
||||
@ -233,26 +242,36 @@ Azure::Core::Credentials::AccessToken AppServiceManagedIdentitySource::GetToken(
|
||||
std::unique_ptr<ManagedIdentitySource> AppServiceV2017ManagedIdentitySource::Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options)
|
||||
{
|
||||
return AppServiceManagedIdentitySource::Create<AppServiceV2017ManagedIdentitySource>(
|
||||
credName, clientId, resourceId, options, "MSI_ENDPOINT", "MSI_SECRET", "2017");
|
||||
credName, clientId, objectId, resourceId, options, "MSI_ENDPOINT", "MSI_SECRET", "2017");
|
||||
}
|
||||
|
||||
std::unique_ptr<ManagedIdentitySource> AppServiceV2019ManagedIdentitySource::Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options)
|
||||
{
|
||||
return AppServiceManagedIdentitySource::Create<AppServiceV2019ManagedIdentitySource>(
|
||||
credName, clientId, resourceId, options, "IDENTITY_ENDPOINT", "IDENTITY_HEADER", "2019");
|
||||
credName,
|
||||
clientId,
|
||||
objectId,
|
||||
resourceId,
|
||||
options,
|
||||
"IDENTITY_ENDPOINT",
|
||||
"IDENTITY_HEADER",
|
||||
"2019");
|
||||
}
|
||||
|
||||
std::unique_ptr<ManagedIdentitySource> CloudShellManagedIdentitySource::Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Azure::Core::Credentials::TokenCredentialOptions const& options)
|
||||
{
|
||||
@ -265,11 +284,11 @@ std::unique_ptr<ManagedIdentitySource> CloudShellManagedIdentitySource::Create(
|
||||
|
||||
if (!msiEndpoint.empty())
|
||||
{
|
||||
if (!clientId.empty() || !resourceId.empty())
|
||||
if (!clientId.empty() || !objectId.empty() || !resourceId.empty())
|
||||
{
|
||||
throw AuthenticationException(
|
||||
"User-assigned managed identities are not supported in Cloud Shell environments. Omit "
|
||||
"the clientId or resourceId when constructing the ManagedIdentityCredential.");
|
||||
"the clientId, objectId, or resourceId when constructing the ManagedIdentityCredential.");
|
||||
}
|
||||
|
||||
return std::unique_ptr<ManagedIdentitySource>(new CloudShellManagedIdentitySource(
|
||||
@ -328,6 +347,7 @@ Azure::Core::Credentials::AccessToken CloudShellManagedIdentitySource::GetToken(
|
||||
std::unique_ptr<ManagedIdentitySource> AzureArcManagedIdentitySource::Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Azure::Core::Credentials::TokenCredentialOptions const& options)
|
||||
{
|
||||
@ -344,12 +364,12 @@ std::unique_ptr<ManagedIdentitySource> AzureArcManagedIdentitySource::Create(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!clientId.empty() || !resourceId.empty())
|
||||
if (!clientId.empty() || !objectId.empty() || !resourceId.empty())
|
||||
{
|
||||
throw AuthenticationException(
|
||||
"User assigned identity is not supported by the Azure Arc Managed Identity Endpoint. "
|
||||
"To authenticate with the system assigned identity, omit the client or resource ID "
|
||||
"when constructing the ManagedIdentityCredential.");
|
||||
"To authenticate with the system assigned identity, omit the client, object, or resource "
|
||||
"ID when constructing the ManagedIdentityCredential.");
|
||||
}
|
||||
|
||||
return std::unique_ptr<ManagedIdentitySource>(new AzureArcManagedIdentitySource(
|
||||
@ -455,6 +475,7 @@ Azure::Core::Credentials::AccessToken AzureArcManagedIdentitySource::GetToken(
|
||||
std::unique_ptr<ManagedIdentitySource> ImdsManagedIdentitySource::Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Azure::Core::Credentials::TokenCredentialOptions const& options)
|
||||
{
|
||||
@ -475,11 +496,12 @@ std::unique_ptr<ManagedIdentitySource> ImdsManagedIdentitySource::Create(
|
||||
imdsUrl.AppendPath("/metadata/identity/oauth2/token");
|
||||
|
||||
return std::unique_ptr<ManagedIdentitySource>(
|
||||
new ImdsManagedIdentitySource(clientId, resourceId, imdsUrl, options));
|
||||
new ImdsManagedIdentitySource(clientId, objectId, resourceId, imdsUrl, options));
|
||||
}
|
||||
|
||||
ImdsManagedIdentitySource::ImdsManagedIdentitySource(
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Azure::Core::Url const& imdsUrl,
|
||||
Azure::Core::Credentials::TokenCredentialOptions const& options)
|
||||
@ -492,13 +514,17 @@ ImdsManagedIdentitySource::ImdsManagedIdentitySource(
|
||||
|
||||
url.AppendQueryParameter("api-version", "2018-02-01");
|
||||
|
||||
// Only one of clientId or resourceId will be set to a non-empty value.
|
||||
// Only one of clientId, objectId, or resourceId will be set to a non-empty value.
|
||||
// IMDS uses msi_res_id, and not mi_res_id:
|
||||
// https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
|
||||
if (!clientId.empty())
|
||||
{
|
||||
url.AppendQueryParameter("client_id", clientId);
|
||||
}
|
||||
else if (!objectId.empty())
|
||||
{
|
||||
url.AppendQueryParameter("object_id", objectId);
|
||||
}
|
||||
else if (!resourceId.empty())
|
||||
{
|
||||
url.AppendQueryParameter("msi_res_id", resourceId);
|
||||
|
||||
@ -54,6 +54,7 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
protected:
|
||||
explicit AppServiceManagedIdentitySource(
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options,
|
||||
Core::Url endpointUrl,
|
||||
@ -66,6 +67,7 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
static std::unique_ptr<ManagedIdentitySource> Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options,
|
||||
char const* endpointVarName,
|
||||
@ -84,12 +86,14 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
private:
|
||||
explicit AppServiceV2017ManagedIdentitySource(
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options,
|
||||
Core::Url endpointUrl,
|
||||
std::string const& secret)
|
||||
: AppServiceManagedIdentitySource(
|
||||
clientId,
|
||||
objectId,
|
||||
resourceId,
|
||||
options,
|
||||
endpointUrl,
|
||||
@ -104,6 +108,7 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
static std::unique_ptr<ManagedIdentitySource> Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options);
|
||||
};
|
||||
@ -114,12 +119,14 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
private:
|
||||
explicit AppServiceV2019ManagedIdentitySource(
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options,
|
||||
Core::Url endpointUrl,
|
||||
std::string const& secret)
|
||||
: AppServiceManagedIdentitySource(
|
||||
clientId,
|
||||
objectId,
|
||||
resourceId,
|
||||
options,
|
||||
endpointUrl,
|
||||
@ -134,6 +141,7 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
static std::unique_ptr<ManagedIdentitySource> Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options);
|
||||
};
|
||||
@ -151,6 +159,7 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
static std::unique_ptr<ManagedIdentitySource> Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options);
|
||||
|
||||
@ -171,6 +180,7 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
static std::unique_ptr<ManagedIdentitySource> Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options);
|
||||
|
||||
@ -185,6 +195,7 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
|
||||
explicit ImdsManagedIdentitySource(
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Url const& imdsUrl,
|
||||
Core::Credentials::TokenCredentialOptions const& options);
|
||||
@ -193,6 +204,7 @@ namespace Azure { namespace Identity { namespace _detail {
|
||||
static std::unique_ptr<ManagedIdentitySource> Create(
|
||||
std::string const& credName,
|
||||
std::string const& clientId,
|
||||
std::string const& objectId,
|
||||
std::string const& resourceId,
|
||||
Core::Credentials::TokenCredentialOptions const& options);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user