Skip to content

Add ManagedIdentityCredential support in ConfigurableCredentialProvider#56283

Open
m-nash wants to merge 1 commit intomainfrom
configurable-managed-identity
Open

Add ManagedIdentityCredential support in ConfigurableCredentialProvider#56283
m-nash wants to merge 1 commit intomainfrom
configurable-managed-identity

Conversation

@m-nash
Copy link
Member

@m-nash m-nash commented Feb 14, 2026

Description

Adds ManagedIdentityCredential (MIC) support to the ConfigurableCredentialProvider test infrastructure.

Fixes #55502

Changes

Production code

  • DefaultAzureCredentialOptions: Added internal config properties \IsProbeEnabled, \UseManagedIdentityPipeline, and \ManagedIdentityObjectId\ with config reading and Clone support
  • DefaultAzureCredentialFactory: \CreateManagedIdentityCredential\ now reads \IsProbeEnabled, \UseManagedIdentityPipeline, and \ManagedIdentityObjectId\ from options instead of hardcoding defaults

Test code

  • ManagedIdentityCredentialTests.cs: Added virtual factory methods for credential construction; refactored all ~40 tests to use factories instead of direct MIC instantiation; added private helpers to reduce duplication
  • ConfigurableCredentials/ManagedIdentityCredentialTests.cs (new): CC test class with factory-only overrides (zero test overrides, zero skips)
  • ConfigurableCredentials/ManagedIdentityCredentialCreationTests.cs (new): 12 creation tests validating all config properties flow through to MIC

Test results

  • Base MIC tests: 142/142 pass
  • CC MIC tests: 142/142 pass, 0 skipped
  • CC creation tests: 12/12 pass
  • Full CC regression: 0 failures

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds ManagedIdentityCredential support to the ConfigurableCredentialProvider test infrastructure by plumbing Managed Identity–specific settings through DefaultAzureCredentialOptions/DefaultAzureCredentialFactory, and refactoring existing MIC tests to be reusable by ConfigurableCredential-derived test fixtures.

Changes:

  • Add internal Managed Identity config properties (IsProbeEnabled, UseManagedIdentityPipeline, ManagedIdentityObjectId) to DefaultAzureCredentialOptions with config-reading + clone support.
  • Update DefaultAzureCredentialFactory.CreateManagedIdentityCredential to honor the new options (probe, pipeline choice, objectId).
  • Refactor ManagedIdentityCredentialTests to use virtual factory methods and add new ConfigurableCredentials test fixtures (including creation/config flow tests).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs Refactors MIC tests to use factory methods and adds helpers for reuse by ConfigurableCredentials tests.
sdk/identity/Azure.Identity/tests/ConfigurableCredentials/ManagedIdentityCredentialTests.cs New CC-derived MIC test fixture overriding factories to create credentials via IConfiguration/DAC.
sdk/identity/Azure.Identity/tests/ConfigurableCredentials/ManagedIdentityCredentialCreationTests.cs New creation tests validating config-to-MIC option flow/precedence.
sdk/identity/Azure.Identity/src/DefaultAzureCredentialFactory.cs Honors new Managed Identity options when creating MIC; adds objectId support.
sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredentialOptions.cs Adds internal config properties for MIC probe/pipeline/objectId and clones them.

Comment on lines +284 to +310
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
{
Pipeline = CredentialPipeline.GetInstance(options, IsManagedIdentityCredential: true),
Pipeline = CredentialPipeline.GetInstance(options, IsManagedIdentityCredential: options.UseManagedIdentityPipeline ?? 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 if (!string.IsNullOrEmpty(options.ManagedIdentityObjectId))
{
miOptions.ManagedIdentityId = ManagedIdentityId.FromUserAssignedObjectId(options.ManagedIdentityObjectId);
}
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateManagedIdentityCredential now supports ManagedIdentityObjectId, but the mutual-exclusion validation only checks ManagedIdentityClientId + ManagedIdentityResourceId. Per the option docs, ManagedIdentityObjectId should also be mutually exclusive with the other two; otherwise ambiguous configuration will be silently resolved by precedence. Please extend the validation to throw when more than one of (clientId, resourceId, objectId) is set.

Copilot uses AI. Check for mistakes.
Comment on lines +364 to +365
/// When <c>null</c> (default), the standard pipeline is used for single-credential selection
/// and the MI pipeline is used when MIC is part of the default credential chain.
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML doc for UseManagedIdentityPipeline says that when null the standard pipeline is used for single-credential selection, but DefaultAzureCredentialFactory currently defaults it with options.UseManagedIdentityPipeline ?? true, so the MI pipeline is used even for single selection unless explicitly overridden. Please update the doc comment to match actual behavior, or change the factory defaulting logic to align with the documented behavior.

Suggested change
/// When <c>null</c> (default), the standard pipeline is used for single-credential selection
/// and the MI pipeline is used when MIC is part of the default credential chain.
/// When <c>null</c> (default), the managed identity pipeline is used. Set to <c>false</c> to force use of
/// the standard pipeline instead.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +39
bool? UseManagedIdentityPipeline = null,
TimeSpan? maxRetryDelay = null,
TimeSpan? retryDelay = null,
RetryMode? retryMode = null,
TimeSpan? networkTimeout = null)
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateConfiguredCredential and CreateCredentialForImdsWithRetryOptions use a PascalCase parameter name (UseManagedIdentityPipeline). Parameter names in this codebase are consistently camelCase; using PascalCase here is inconsistent and makes the named-argument call sites harder to read. Please rename these parameters to useManagedIdentityPipeline (and update the named argument uses accordingly).

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +96
protected override TokenCredential CreateCredentialForImds(
MockTransport transport,
string clientId = null,
bool isChained = false,
bool isForceRefreshEnabled = true,
Uri authorityHost = null)
{
var credential = CreateConfiguredCredential(transport, clientId: clientId, isForceRefreshEnabled: isForceRefreshEnabled, isChained: isChained);
return InstrumentClient(credential);
}
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateCredentialForImds receives an authorityHost parameter but it isn't applied to the configured credential (no config value / options property is set from it). This means the derived configurable tests won't exercise the authority-host path the base tests are intended to cover. Please plumb authorityHost through (e.g., set the appropriate config key or set dacOptions.AuthorityHost).

Copilot uses AI. Check for mistakes.
bool isChained = false,
bool isForceRefreshEnabled = true)
{
var credential = CreateConfiguredCredential(transport, resourceId: resourceId.ToString(), isForceRefreshEnabled: isForceRefreshEnabled);
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateCredentialForImdsWithResourceId ignores the isChained parameter (it never gets forwarded into CreateConfiguredCredential / IsProbeEnabled). If any base tests call this factory with isChained: true, the configurable-credential variant will behave differently. Please pass isChained through so the IMDS probe behavior matches the base factory method contract.

Suggested change
var credential = CreateConfiguredCredential(transport, resourceId: resourceId.ToString(), isForceRefreshEnabled: isForceRefreshEnabled);
var credential = CreateConfiguredCredential(
transport,
resourceId: resourceId.ToString(),
isForceRefreshEnabled: isForceRefreshEnabled,
isChained: isChained);

Copilot uses AI. Check for mistakes.
bool isForceRefreshEnabled = true,
TimeSpan? maxRetryDelay = null)
{
var credential = CreateConfiguredCredential(transport, clientId: clientId, resourceId: resourceId, isForceRefreshEnabled: isForceRefreshEnabled);
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateCredentialForNonImdsSource accepts maxRetryDelay but never forwards it into CreateConfiguredCredential, so callers (e.g., tests passing TimeSpan.Zero to avoid extra delay) won't get the requested retry behavior. Please pass maxRetryDelay through when constructing the configured credential.

Suggested change
var credential = CreateConfiguredCredential(transport, clientId: clientId, resourceId: resourceId, isForceRefreshEnabled: isForceRefreshEnabled);
var credential = CreateConfiguredCredential(
transport,
clientId: clientId,
resourceId: resourceId,
isForceRefreshEnabled: isForceRefreshEnabled,
maxRetryDelay: maxRetryDelay);

Copilot uses AI. Check for mistakes.
Comment on lines +155 to +174
protected override TokenCredential CreateCredentialWithManagedIdentityId(
MockTransport transport,
ManagedIdentityId managedIdentityId,
bool isForceRefreshEnabled = true)
{
string idStr = managedIdentityId.ToString();
string clientId = null;
string resourceId = null;
string objectId = null;

if (idStr.StartsWith("ClientId "))
clientId = idStr.Substring("ClientId ".Length);
else if (idStr.StartsWith("ResourceId "))
resourceId = idStr.Substring("ResourceId ".Length);
else if (idStr.StartsWith("ObjectId "))
objectId = idStr.Substring("ObjectId ".Length);

var credential = CreateConfiguredCredential(transport, clientId: clientId, resourceId: resourceId, objectId: objectId, isForceRefreshEnabled: isForceRefreshEnabled);
return InstrumentClient(credential);
}
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateCredentialWithManagedIdentityId infers the identity type by parsing ManagedIdentityId.ToString(). This is brittle (string format changes would silently break the mapping). Please use a more robust approach (e.g., reflect the internal _idType/_userAssignedId fields, similar to other tests in this area) to determine which config property to set.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

Azure.Identity: Add ManagedIdentityCredential Support

1 participant