Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@

### Features Added

- `DefaultAzureCredential` uses `AZURE_CLIENT_ID` to select a user-assigned managed identity.

### Breaking Changes

- When `AZURE_CLIENT_ID` is set, the managed identity credential in `DefaultAzureCredential`
now selects that identity instead of sending no identity selector. Standalone
`ManagedIdentityCredential` constructors are unchanged.

### Bugs Fixed

### Other Changes
Expand Down
12 changes: 12 additions & 0 deletions sdk/identity/azure-identity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ The [Managed identity authentication](https://learn.microsoft.com/entra/identity
* [Azure Arc](https://learn.microsoft.com/azure/azure-arc/servers/managed-identity-authentication)
* [Azure Virtual Machines](https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/how-to-use-vm-token)

### Specify a user-assigned managed identity with `DefaultAzureCredential`

Set `AZURE_CLIENT_ID` to the client ID of a user-assigned managed identity
attached to the Azure host. `DefaultAzureCredential` passes this value to the
managed identity credential in its chain. An unset or empty value preserves
the existing behavior of sending no identity selector.

This also applies when `AZURE_TOKEN_CREDENTIALS=ManagedIdentityCredential`.
It does not change credential ordering or configure credentials constructed
directly with `ManagedIdentityCredential`; those still require explicit
identity selection as shown below.

### Specify a user-assigned managed identity with `ManagedIdentityCredential`

Many Azure hosts allow the assignment of a user-assigned managed identity. The following examples demonstrate configuring `ManagedIdentityCredential` to authenticate a user-assigned managed identity when deployed to an Azure host. The sample code uses the credential to authenticate a `BlobClient` from the [Azure::Storage::Blobs][blobs_client_library] client library. It also demonstrates how you can specify a user-assigned managed identity either by a client ID, resource ID, or an object ID.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ namespace Azure { namespace Identity {
* though the credentials being used and their order is documented, it may be changed in the
* future versions of the SDK, potentially introducing breaking changes in its behavior.
*
* @details The managed identity credential in the chain uses the client ID from the
* `AZURE_CLIENT_ID` environment variable when it is non-empty. Otherwise, it does not specify
* an identity selector. This does not change standalone
* #Azure::Identity::ManagedIdentityCredential constructors.
*
* @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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ DefaultAzureCredential::DefaultAzureCredential(
true,
"ManagedIdentityCredential",
[&specificCred](auto options) {
return std::shared_ptr<ManagedIdentityCredential>(
new ManagedIdentityCredential({}, !specificCred, options));
return std::shared_ptr<ManagedIdentityCredential>(new ManagedIdentityCredential(
Environment::GetVariable("AZURE_CLIENT_ID"), !specificCred, options));
Comment on lines +101 to +102
}},
CredentialInfo{
false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// Licensed under the MIT License.

#include "azure/identity/default_azure_credential.hpp"
#include "azure/identity/managed_identity_credential.hpp"
#include "credential_test_helper.hpp"

#include <azure/core/diagnostics/logger.hpp>
#include <azure/core/url.hpp>

#include <../src/private/chained_token_credential_impl.hpp>
#include <gtest/gtest.h>
Expand Down Expand Up @@ -274,7 +276,8 @@ TEST_P(LogMessages, )
EXPECT_EQ(
log.at(i).second,
"Identity: ManagedIdentityCredential will be created "
"with Azure Instance Metadata Service source."
"with Azure Instance Metadata Service source "
"and Client ID 'fedcba98-7654-3210-0123-456789abcdef'."
"\nSuccessful creation does not guarantee further successful token retrieval.");

{
Expand Down Expand Up @@ -488,7 +491,8 @@ TEST_P(LogMessagesForSpecificCredential, )
EXPECT_EQ(
log.at(i).second,
"Identity: ManagedIdentityCredential will be created "
"with Azure Instance Metadata Service source."
"with Azure Instance Metadata Service source "
"and Client ID 'fedcba98-7654-3210-0123-456789abcdef'."
"\nSuccessful creation does not guarantee further successful token retrieval.");
}
else if (specificCredentialInfo.CredentialName == "AzureCliCredential")
Expand Down Expand Up @@ -529,6 +533,98 @@ TEST(DefaultAzureCredential, RequireCredentialSpecifierEnvVarValue)
static_cast<void>(std::make_unique<DefaultAzureCredential>(true)), AuthenticationException);
}

TEST(DefaultAzureCredential, ManagedIdentityClientIdFromEnvironment)
{
for (std::string const credentialSpecifier : {"", "prod", "ManagedIdentityCredential"})
{
for (std::string const clientId : {"", "fedcba98-7654-3210-0123-456789abcdef"})
{
SCOPED_TRACE(credentialSpecifier + ": " + clientId);
CredentialTestHelper::EnvironmentOverride const env({
{"AZURE_TOKEN_CREDENTIALS", credentialSpecifier},
{"AZURE_CLIENT_ID", clientId},
{"AZURE_TENANT_ID", ""},
{"AZURE_CLIENT_SECRET", ""},
{"AZURE_CLIENT_CERTIFICATE_PATH", ""},
{"AZURE_FEDERATED_TOKEN_FILE", ""},
{"AZURE_AUTHORITY_HOST", ""},
{"AZURE_POD_IDENTITY_AUTHORITY_HOST", ""},
{"MSI_ENDPOINT", ""},
{"MSI_SECRET", ""},
{"IDENTITY_ENDPOINT", ""},
{"IDENTITY_HEADER", ""},
{"IMDS_ENDPOINT", ""},
});

auto const actual = CredentialTestHelper::SimulateTokenRequest(
[](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
return std::make_unique<DefaultAzureCredential>(options);
},
{{"https://storage.azure.com/.default"}},
{"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN\"}"});

ASSERT_EQ(actual.Requests.size(), 1U);
auto const query = Azure::Core::Url(actual.Requests.at(0).AbsoluteUrl).GetQueryParameters();
if (clientId.empty())
{
EXPECT_EQ(query.count("client_id"), 0U);
}
else
{
ASSERT_EQ(query.count("client_id"), 1U);
EXPECT_EQ(query.at("client_id"), clientId);
}
ASSERT_EQ(actual.Responses.size(), 1U);
EXPECT_EQ(actual.Responses.at(0).AccessToken.Token, "ACCESSTOKEN");
}
}
}

TEST(DefaultAzureCredential, StandaloneManagedIdentityIgnoresClientIdEnvironment)
{
CredentialTestHelper::EnvironmentOverride const env({
{"AZURE_CLIENT_ID", "fedcba98-7654-3210-0123-456789abcdef"},
{"AZURE_POD_IDENTITY_AUTHORITY_HOST", ""},
{"MSI_ENDPOINT", ""},
{"MSI_SECRET", ""},
{"IDENTITY_ENDPOINT", ""},
{"IDENTITY_HEADER", ""},
{"IMDS_ENDPOINT", ""},
});

for (std::string const explicitClientId : {"", "01234567-89ab-cdef-fedc-ba8976543210"})
{
SCOPED_TRACE(explicitClientId);
auto const actual = CredentialTestHelper::SimulateTokenRequest(
[&](auto transport) {
TokenCredentialOptions options;
options.Transport.Transport = transport;
if (explicitClientId.empty())
{
return std::make_unique<Azure::Identity::ManagedIdentityCredential>(options);
}
return std::make_unique<Azure::Identity::ManagedIdentityCredential>(
explicitClientId, options);
},
{{"https://storage.azure.com/.default"}},
{"{\"expires_in\":3600, \"access_token\":\"ACCESSTOKEN\"}"});

ASSERT_EQ(actual.Requests.size(), 1U);
auto const query = Azure::Core::Url(actual.Requests.at(0).AbsoluteUrl).GetQueryParameters();
if (explicitClientId.empty())
{
EXPECT_EQ(query.count("client_id"), 0U);
}
else
{
ASSERT_EQ(query.count("client_id"), 1U);
EXPECT_EQ(query.at("client_id"), explicitClientId);
}
}
}

TEST(DefaultAzureCredential, ImdsProbe)
{
using Azure::Core::Http::HttpStatusCode;
Expand Down
Loading