Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions sdk/identity/Azure.Identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Bugs Fixed

- `ManagedIdentityCredential` throws `CredentialUnavilableException` when the IMDS endpoint is unavailable. This addresses a regression in how it behaves in the `ChainedTokenCredential` ([47057](https://github.com/Azure/azure-sdk-for-net/issues/47057)).

### Other Changes

## 1.14.0 (2025-05-13)
Expand Down
31 changes: 27 additions & 4 deletions sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,19 @@ public async ValueTask<AccessToken> AuthenticateAsync(bool async, TokenRequestCo
return await tokenExchangeManagedIdentitySource.AuthenticateAsync(async, context, cancellationToken).ConfigureAwait(false);
}

// The default case is to use the MSAL implementation, which does no probing of the IMDS endpoint.
result = async ?
await _msalManagedIdentityClient.AcquireTokenForManagedIdentityAsync(context, cancellationToken).ConfigureAwait(false) :
_msalManagedIdentityClient.AcquireTokenForManagedIdentity(context, cancellationToken);
try
{
// The default case is to use the MSAL implementation, which does no probing of the IMDS endpoint.
result = async ?
await _msalManagedIdentityClient.AcquireTokenForManagedIdentityAsync(context, cancellationToken).ConfigureAwait(false) :
_msalManagedIdentityClient.AcquireTokenForManagedIdentity(context, cancellationToken);
}
// If the IMDS endpoint is not available, we will throw a CredentialUnavailableException.
catch (MsalServiceException ex) when (HasInnerExceptionMatching(ex, e => e is RequestFailedException && e.Message.Contains("timed out")))
{
// If the managed identity is not found, throw a more specific exception.
throw new CredentialUnavailableException(MsiUnavailableError, ex);
}

return result.ToAccessToken();
}
Expand Down Expand Up @@ -127,5 +136,19 @@ private static ManagedIdentitySource SelectManagedIdentitySource(ManagedIdentity
return TokenExchangeManagedIdentitySource.TryCreate(options) ??
new ImdsManagedIdentityProbeSource(options, client);
}

private static bool HasInnerExceptionMatching(Exception exception, Func<Exception, bool> condition)
{
var current = exception;
while (current != null)
{
if (condition(current))
{
return true;
}
current = current.InnerException;
}
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Azure.Core.TestFramework;
using Azure.Identity.Tests.Mock;
using Microsoft.AspNetCore.Http;
using Microsoft.Identity.Client;
using NUnit.Framework;
using NUnit.Framework.Internal;

Expand Down Expand Up @@ -841,6 +842,30 @@ public async Task VerifyMsiUnavailableOnIMDSRequestFailedExcpetion()
await Task.CompletedTask;
}

[NonParallelizable]
[Test]
public async Task ThrowsCredentialUnavailableWhenIMDSTimesOut()
{
using var environment = new TestEnvVar(new() { { "MSI_ENDPOINT", null }, { "MSI_SECRET", null }, { "IDENTITY_ENDPOINT", null }, { "IDENTITY_HEADER", null }, { "AZURE_POD_IDENTITY_AUTHORITY_HOST", null } });

var mockTransport = new MockTransport(req =>
{
throw new MsalServiceException(MsalError.ManagedIdentityRequestFailed, "Retry failed", new RequestFailedException("Operation timed out (169.254.169.254:80"));
});
var options = new TokenCredentialOptions() { IsChainedCredential = false, Transport = mockTransport };

ManagedIdentityCredential credential = InstrumentClient(new ManagedIdentityCredential(
new ManagedIdentityClient(
new ManagedIdentityClientOptions() { IsForceRefreshEnabled = true, Options = options, Pipeline = CredentialPipeline.GetInstance(options, IsManagedIdentityCredential: true) })
));

var ex = Assert.ThrowsAsync<CredentialUnavailableException>(async () => await credential.GetTokenAsync(new TokenRequestContext(MockScopes.Default)));

Assert.That(ex.Message, Does.Contain(ManagedIdentityClient.MsiUnavailableError));

await Task.CompletedTask;
}

[NonParallelizable]
[Test]
public async Task VerifyMsiUnavailableOnIMDSGatewayErrorResponse([Values(502, 504)] int statusCode)
Expand Down
Loading