Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure Options are passed down to ManagedIdentityClient #34078

Closed
wants to merge 3 commits into from

Conversation

pharring
Copy link
Contributor

Specifically, this is to pass the AuthorityHost to the MSAL client

Specifically, this is to pass the AuthorityHost to the MSAL client
@ghost ghost added the Azure.Identity label Feb 10, 2023
@pharring
Copy link
Contributor Author

Probably needs unit tests to show the problem. Note that DefaultAzureCredentialFactory.CreateManagedIdentityCredential already gets it right.

@pharring
Copy link
Contributor Author

I believe this is the fix for #34077

@azure-sdk
Copy link
Collaborator

API change check

API changes are not detected in this pull request.

@pharring
Copy link
Contributor Author

@schaabs ping me when you get a chance because I want to discuss this with you. There are a 3 different fixes in here. I think any one of them would fix the bug. Do we want to include all three?

@@ -146,7 +146,7 @@ protected override async ValueTask<IConfidentialClientApplication> CreateClientA

if (!string.IsNullOrEmpty(tenantId))
{
builder.WithAuthority(Pipeline.AuthorityHost.AbsoluteUri, tenantId);
builder.WithTenantId(tenantId);
Copy link
Contributor Author

@pharring pharring Feb 10, 2023

Choose a reason for hiding this comment

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

This code path is hit by the ChallengeBasedAuthenticationPolicy in KeyVault. The tenantId comes from the challenge response. If Pipeline.AuthorityHost doesn't agree with the authority passed to the ConfidentialClientBuilder, then we hit the MSAL exception about mismatched authorities (https://aka.ms/msal-net-authority-override).

Switching to .WithTenantId sidesteps the issue of knowing what authority was passed to the ConfidentialClientBuilder.

I think this alone would be sufficient to fix the bug, but it's really subtle.

var pipeline = CredentialPipeline.GetInstance(options);

var cred = new ManagedIdentityCredential(new ManagedIdentityClient(pipeline, clientId));
var cred = new ManagedIdentityCredential(clientId, options);
Copy link
Contributor Author

@pharring pharring Feb 10, 2023

Choose a reason for hiding this comment

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

Using the public constructor here instead of the internal one, now that the public one passes through options correctly.

@@ -25,7 +25,7 @@ public override TokenCredential CreateEnvironmentCredential()
=> new EnvironmentCredential(Pipeline, Options);

public override TokenCredential CreateManagedIdentityCredential()
=> new ManagedIdentityCredential(new ManagedIdentityClient(Pipeline, Options.ManagedIdentityClientId));
=> new ManagedIdentityCredential(Options.ManagedIdentityClientId, Pipeline, Options);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again, using the public constructor instead of the internal one.

@@ -58,7 +58,7 @@ public MsalConfidentialClient(CredentialPipeline pipeline, string tenantId, stri
: base(pipeline, tenantId, clientId, options)
{
_appTokenProviderCallback = appTokenProviderCallback;
_authority = options?.AuthorityHost ?? AzureAuthorityHosts.AzurePublicCloud;
_authority = options?.AuthorityHost ?? pipeline.AuthorityHost ?? AzureAuthorityHosts.AzurePublicCloud;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This isn't strictly necessary, but it seems like a reasonable fallback. What do you think?

@pharring pharring marked this pull request as ready for review February 10, 2023 23:34
@@ -45,9 +45,8 @@ protected ManagedIdentityCredential()
/// </param>
/// <param name="options">Options to configure the management of the requests sent to the Azure Active Directory service.</param>
public ManagedIdentityCredential(string clientId = null, TokenCredentialOptions options = null)
: this(clientId, CredentialPipeline.GetInstance(options))
: this(clientId, CredentialPipeline.GetInstance(options), options)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This (and the related changes in constructors below) is really the key fix. Ensuring that options gets passed down to the inner ManagedIdentityClient and eventually into the MsalConfidentialClient.
This is the big difference between using the ManagedIdentityCredential stand-alone versus via DefaultAzureCredential. DefaultAzureCredential's factory calls one of the internal constructors, correctly passing in Options. So what I'm doing here is making sure the two approaches have the same behavior.

This fix alone would be enough to fix the bug.

new ManagedIdentityCredential(
new ManagedIdentityClient(
new ManagedIdentityClientOptions { Pipeline = pipeline, ClientId = "mock-client-id", Options = options })));
new ManagedIdentityCredential("mock-client-id", pipeline, options));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again, making use of the public constructor instead of the internal one.

Copy link
Member

@christothes christothes left a comment

Choose a reason for hiding this comment

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

Thanks so much for taking the time to submit this PR! We saw the bug and came to the same conclusion for the primary fix.

However, I've already started work on a fix that includes some additional refactoring to avoid this kind of confusion going forward.


AccessToken actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default));
AccessToken actualToken = await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default, tenantId: "mock-tenant-id"));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding the tenantId here exercises the code-path that hits the original bug.

@pharring
Copy link
Contributor Author

@christothes Great. Put me on the final PR when you have it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants