Skip to content

Commit

Permalink
Implement objectId support for ManagedIdentityCredential (#45605)
Browse files Browse the repository at this point in the history
  • Loading branch information
christothes authored Sep 4, 2024
1 parent 7116390 commit b46d6bf
Show file tree
Hide file tree
Showing 28 changed files with 370 additions and 124 deletions.
1 change: 1 addition & 0 deletions sdk/identity/Azure.Identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 1.13.0-beta.2 (Unreleased)

### Features Added
- `ManagedIdentityCredential` now supports specifying a user-assigned managed identity by object ID.

### Breaking Changes

Expand Down
32 changes: 30 additions & 2 deletions sdk/identity/Azure.Identity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,43 @@ These examples demonstrate authenticating `SecretClient` from the [Azure.Securit

#### Authenticate with a user-assigned managed identity

To authenticate with a user-assigned managed identity, you must specify an ID for the managed identity.

The following example demonstrates authenticating with a user-assigned managed identity using the client ID:

```C# Snippet:AuthenticatingWithManagedIdentityCredentialUserAssigned
var credential = new ManagedIdentityCredential(clientId: userAssignedClientId);
string userAssignedClientId = "some client ID";

var credential = new ManagedIdentityCredential(
ManagedIdentityId.FromUserAssignedClientId(userAssignedClientId));
var client = new SecretClient(new Uri("https://myvault.vault.azure.net/"), credential);
```

The following example demonstrates authenticating with a user-assigned managed identity using its resource ID:

```C# Snippet:AuthenticatingWithManagedIdentityCredentialUserAssignedResourceId
ResourceIdentifier userAssignedResourceId = new ResourceIdentifier(
"/subscriptions/<subscriptionID>/resourcegroups/<resource group>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<MI name>");

var credential = new ManagedIdentityCredential(
ManagedIdentityId.FromUserAssignedResourceId(userAssignedResourceId));
var client = new SecretClient(new Uri("https://myvault.vault.azure.net/"), credential);
```

The following example demonstrates authenticating with a user-assigned managed identity using its object ID:

```C# Snippet:AuthenticatingWithManagedIdentityCredentialUserAssignedObjectId
string userAssignedObjectId = "some object ID";

var credential = new ManagedIdentityCredential(
ManagedIdentityId.FromUserAssignedObjectId(userAssignedObjectId));
var client = new SecretClient(new Uri("https://myvault.vault.azure.net/"), credential);
```

#### Authenticate with a system-assigned managed identity

```C# Snippet:AuthenticatingWithManagedIdentityCredentialSystemAssigned
var credential = new ManagedIdentityCredential();
var credential = new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned);
var client = new SecretClient(new Uri("https://myvault.vault.azure.net/"), credential);
```

Expand Down
17 changes: 17 additions & 0 deletions sdk/identity/Azure.Identity/api/Azure.Identity.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -314,11 +314,28 @@ public InteractiveBrowserCredentialOptions() { }
public partial class ManagedIdentityCredential : Azure.Core.TokenCredential
{
protected ManagedIdentityCredential() { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public ManagedIdentityCredential(Azure.Core.ResourceIdentifier resourceId, Azure.Identity.TokenCredentialOptions options = null) { }
public ManagedIdentityCredential(Azure.Identity.ManagedIdentityCredentialOptions options) { }
public ManagedIdentityCredential(Azure.Identity.ManagedIdentityId managedIdentityId) { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public ManagedIdentityCredential(string clientId = null, Azure.Identity.TokenCredentialOptions options = null) { }
public override Azure.Core.AccessToken GetToken(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public override System.Threading.Tasks.ValueTask<Azure.Core.AccessToken> GetTokenAsync(Azure.Core.TokenRequestContext requestContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
}
public partial class ManagedIdentityCredentialOptions : Azure.Identity.TokenCredentialOptions
{
public ManagedIdentityCredentialOptions() { }
public Azure.Identity.ManagedIdentityId ManagedIdentityId { get { throw null; } set { } }
}
public partial class ManagedIdentityId
{
internal ManagedIdentityId() { }
public static Azure.Identity.ManagedIdentityId SystemAssigned { get { throw null; } }
public static Azure.Identity.ManagedIdentityId FromUserAssignedClientId(string clientId) { throw null; }
public static Azure.Identity.ManagedIdentityId FromUserAssignedObjectId(string objectId) { throw null; }
public static Azure.Identity.ManagedIdentityId FromUserAssignedResourceId(Azure.Core.ResourceIdentifier resourceIdentifier) { throw null; }
}
public partial class OnBehalfOfCredential : Azure.Core.TokenCredential
{
protected OnBehalfOfCredential() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ internal class AppServiceManagedIdentitySource : ManagedIdentitySource

private readonly Uri _endpoint;
private readonly string _secret;
private readonly string _clientId;
private readonly string _resourceId;
private readonly ManagedIdentityId _managedIdentityId;

protected static bool TryValidateEnvVars(string msiEndpoint, string secret, out Uri endpointUri)
{
Expand Down Expand Up @@ -47,8 +46,7 @@ protected AppServiceManagedIdentitySource(CredentialPipeline pipeline, Uri endpo
{
_endpoint = endpoint;
_secret = secret;
_clientId = options.ClientId;
_resourceId = options.ResourceIdentifier?.ToString();
_managedIdentityId = options.ManagedIdentityId;
}

protected override Request CreateRequest(string[] scopes)
Expand All @@ -64,14 +62,16 @@ protected override Request CreateRequest(string[] scopes)
request.Uri.AppendQuery("api-version", AppServiceMsiApiVersion);
request.Uri.AppendQuery("resource", resource);

if (!string.IsNullOrEmpty(_clientId))
string idQueryParam = _managedIdentityId?._idType switch
{
request.Uri.AppendQuery(ClientIdHeaderName, _clientId);
}
ManagedIdentityIdType.ClientId => ClientIdHeaderName,
ManagedIdentityIdType.ResourceId => Constants.ManagedIdentityResourceId,
_ => null
};

if (!string.IsNullOrEmpty(_resourceId))
if (idQueryParam != null)
{
request.Uri.AppendQuery(Constants.ManagedIdentityResourceId, _resourceId);
request.Uri.AppendQuery(idQueryParam, _managedIdentityId._userAssignedId);
}

return request;
Expand Down
10 changes: 5 additions & 5 deletions sdk/identity/Azure.Identity/src/AzureArcManagedIdentitySource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ internal class AzureArcManagedIdentitySource : ManagedIdentitySource
private const string IdentityEndpointInvalidUriError = "The environment variable IDENTITY_ENDPOINT contains an invalid Uri.";
private const string NoChallengeErrorMessage = "Did not receive expected WWW-Authenticate header in the response from Azure Arc Managed Identity Endpoint.";
private const string InvalidChallangeErrorMessage = "The WWW-Authenticate header in the response from Azure Arc Managed Identity Endpoint did not match the expected format.";
private const string UserAssignedNotSupportedErrorMessage = "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, or if authenticating with the DefaultAzureCredential ensure the AZURE_CLIENT_ID environment variable is not set.";
private const string UserAssignedNotSupportedErrorMessage = "User-assigned managed identity is not supported by the Azure Arc Managed Identity Endpoint. To authenticate with the system-assigned managed identity, omit the client ID when constructing the ManagedIdentityCredential, or if authenticating with DefaultAzureCredential, ensure the AZURE_CLIENT_ID environment variable is not set.";
private const string ArcApiVersion = "2019-11-01";

private readonly string _clientId;
private readonly ManagedIdentityId _managedIdentityId;
private readonly Uri _endpoint;

public static ManagedIdentitySource TryCreate(ManagedIdentityClientOptions options)
Expand All @@ -43,8 +43,8 @@ public static ManagedIdentitySource TryCreate(ManagedIdentityClientOptions optio
internal AzureArcManagedIdentitySource(Uri endpoint, ManagedIdentityClientOptions options) : base(options.Pipeline)
{
_endpoint = endpoint;
_clientId = options.ClientId;
if (!string.IsNullOrEmpty(_clientId) || null != options.ResourceIdentifier)
_managedIdentityId = options.ManagedIdentityId;
if (options.ManagedIdentityId._idType != ManagedIdentityIdType.SystemAssigned)
{
AzureIdentityEventSource.Singleton.UserAssignedManagedIdentityNotSupported("Azure Arc");
}
Expand All @@ -53,7 +53,7 @@ internal AzureArcManagedIdentitySource(Uri endpoint, ManagedIdentityClientOption
protected override Request CreateRequest(string[] scopes)
{
// arc MI endpoint doesn't support user assigned identities so if client id was specified throw AuthenticationFailedException
if (!string.IsNullOrEmpty(_clientId))
if (_managedIdentityId._idType != ManagedIdentityIdType.SystemAssigned)
{
throw new AuthenticationFailedException(UserAssignedNotSupportedErrorMessage);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static ManagedIdentitySource TryCreate(ManagedIdentityClientOptions optio
public CloudShellManagedIdentitySource(Uri endpoint, ManagedIdentityClientOptions options) : base(options.Pipeline)
{
_endpoint = endpoint;
if (!string.IsNullOrEmpty(options.ClientId) || null != options.ResourceIdentifier)
if (options.ManagedIdentityId._idType != ManagedIdentityIdType.SystemAssigned)
{
AzureIdentityEventSource.Singleton.UserAssignedManagedIdentityNotSupported("Cloud Shell");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ internal AzureApplicationCredential(AzureApplicationCredentialOptions options, E
{
_credential = new ChainedTokenCredential(
environmentCredential ?? new EnvironmentCredential(options),
managedIdentityCredential ?? new ManagedIdentityCredential(options.ManagedIdentityClientId)
managedIdentityCredential ?? new ManagedIdentityCredential(options.ManagedIdentityId._userAssignedId)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ internal class AzureApplicationCredentialOptions : TokenCredentialOptions
/// <summary>
/// Specifies the client id of the azure ManagedIdentity in the case of user assigned identity.
/// </summary>
public string ManagedIdentityClientId { get; set; } = GetNonEmptyStringOrNull(EnvironmentVariables.ClientId);
private static string GetNonEmptyStringOrNull(string str)
public ManagedIdentityId ManagedIdentityId { get; set; } = GetManagedIdentityIdFromEnvironment(EnvironmentVariables.ClientId);

private static ManagedIdentityId GetManagedIdentityIdFromEnvironment(string clientId)
{
return !string.IsNullOrEmpty(str) ? str : null;
return !string.IsNullOrEmpty(clientId) ? ManagedIdentityId.FromUserAssignedClientId(clientId) : ManagedIdentityId.SystemAssigned;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading.Tasks;
using Azure.Core.Pipeline;
using System.Linq;
using System.ComponentModel;

namespace Azure.Identity
{
Expand Down Expand Up @@ -41,8 +42,9 @@ protected ManagedIdentityCredential()
/// If not provided, a system-assigned managed identity is used.
/// </param>
/// <param name="options">Options to configure the management of the requests sent to Microsoft Entra ID.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public ManagedIdentityCredential(string clientId = null, TokenCredentialOptions options = null)
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { ClientId = clientId, Pipeline = CredentialPipeline.GetInstance(options, IsManagedIdentityCredential: true), Options = options }))
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { ManagedIdentityId = string.IsNullOrEmpty(clientId) ? ManagedIdentityId.SystemAssigned : ManagedIdentityId.FromUserAssignedClientId(clientId), Pipeline = CredentialPipeline.GetInstance(options, IsManagedIdentityCredential: true), Options = options }))
{
_logAccountDetails = options?.Diagnostics?.IsAccountIdentifierLoggingEnabled ?? false;
}
Expand All @@ -54,21 +56,41 @@ public ManagedIdentityCredential(string clientId = null, TokenCredentialOptions
/// The resource ID to authenticate for a <see href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview#how-a-user-assigned-managed-identity-works-with-an-azure-vm">user-assigned managed identity</see>.
/// </param>
/// <param name="options">Options to configure the management of the requests sent to Microsoft Entra ID.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public ManagedIdentityCredential(ResourceIdentifier resourceId, TokenCredentialOptions options = null)
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { ResourceIdentifier = resourceId, Pipeline = CredentialPipeline.GetInstance(options, IsManagedIdentityCredential: true), Options = options }))
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { ManagedIdentityId = ManagedIdentityId.FromUserAssignedResourceId(resourceId), Pipeline = CredentialPipeline.GetInstance(options, IsManagedIdentityCredential: true), Options = options }))
{
_logAccountDetails = options?.Diagnostics?.IsAccountIdentifierLoggingEnabled ?? false;
_clientId = resourceId.ToString();
}

/// <summary>
/// Creates an instance of <see cref="ManagedIdentityCredential"/> capable of authenticating using the specified <see cref="ManagedIdentityId"/>.
/// </summary>
/// <param name="managedIdentityId"></param>
public ManagedIdentityCredential(ManagedIdentityId managedIdentityId)
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { ManagedIdentityId = managedIdentityId, Pipeline = CredentialPipeline.GetInstance(null, IsManagedIdentityCredential: true), Options = null }))
{ }

/// <summary>
/// Creates an instance of <see cref="ManagedIdentityCredential"/> configured with the specified options.
/// </summary>
/// <param name="options">The options used to configure the credential.</param>
public ManagedIdentityCredential(ManagedIdentityCredentialOptions options)
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { ManagedIdentityId = options.ManagedIdentityId, Pipeline = CredentialPipeline.GetInstance(options, IsManagedIdentityCredential: true), Options = options }))
{
Argument.AssertNotNull(options, nameof(options));
_logAccountDetails = options?.Diagnostics?.IsAccountIdentifierLoggingEnabled ?? false;
}

internal ManagedIdentityCredential(string clientId, CredentialPipeline pipeline, TokenCredentialOptions options = null, bool preserveTransport = false)
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { Pipeline = pipeline, ClientId = clientId, PreserveTransport = preserveTransport, Options = options }))
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { Pipeline = pipeline, ManagedIdentityId = ManagedIdentityId.FromUserAssignedClientId(clientId), PreserveTransport = preserveTransport, Options = options }))
{
_clientId = clientId;
}

internal ManagedIdentityCredential(ResourceIdentifier resourceId, CredentialPipeline pipeline, TokenCredentialOptions options, bool preserveTransport = false)
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { Pipeline = pipeline, ResourceIdentifier = resourceId, PreserveTransport = preserveTransport, Options = options }))
: this(new ManagedIdentityClient(new ManagedIdentityClientOptions { Pipeline = pipeline, ManagedIdentityId = ManagedIdentityId.FromUserAssignedResourceId(resourceId), PreserveTransport = preserveTransport, Options = options }))
{
_clientId = resourceId.ToString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Azure.Identity
{
/// <summary>
/// Options used to configure the <see cref="ManagedIdentityCredential"/>.
/// </summary>
public class ManagedIdentityCredentialOptions : TokenCredentialOptions
{
/// <summary>
/// Specifies the configuration for the managed identity.
/// </summary>
public ManagedIdentityId ManagedIdentityId { get; set; }
}
}
21 changes: 19 additions & 2 deletions sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,34 @@ public virtual TokenCredential CreateManagedIdentityCredential()
var options = Options.Clone<DefaultAzureCredentialOptions>();
options.IsChainedCredential = true;

if (options.ManagedIdentityClientId != null && options.ManagedIdentityResourceId != null)
{
throw new ArgumentException(
$"{nameof(DefaultAzureCredentialOptions)} cannot specify both {nameof(options.ManagedIdentityResourceId)} and {nameof(options.ManagedIdentityClientId)}.");
}

var miOptions = new ManagedIdentityClientOptions
{
ResourceIdentifier = options.ManagedIdentityResourceId,
ClientId = options.ManagedIdentityClientId,
Pipeline = CredentialPipeline.GetInstance(options, IsManagedIdentityCredential: true),
Options = options,
InitialImdsConnectionTimeout = TimeSpan.FromSeconds(1),
ExcludeTokenExchangeManagedIdentitySource = options.ExcludeWorkloadIdentityCredential,
IsForceRefreshEnabled = options.IsForceRefreshEnabled,
};

if (!string.IsNullOrEmpty(options.ManagedIdentityClientId))
{
miOptions.ManagedIdentityId = ManagedIdentityId.FromUserAssignedClientId(options.ManagedIdentityClientId);
}
else if (options.ManagedIdentityResourceId != null)
{
miOptions.ManagedIdentityId = ManagedIdentityId.FromUserAssignedResourceId(options.ManagedIdentityResourceId);
}
else
{
miOptions.ManagedIdentityId = ManagedIdentityId.SystemAssigned;
}

return new ManagedIdentityCredential(new ManagedIdentityClient(miOptions));
}

Expand Down
Loading

0 comments on commit b46d6bf

Please sign in to comment.