From dd4ff35a1ea4e0ae6389b0a50401fb6ec20967e7 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 5 Jun 2025 13:45:21 -0500 Subject: [PATCH 1/3] ManagedIdentityCredential throws CUE when endpoint unavailable --- .../src/ManagedIdentityClient.cs | 31 ++++++++++++++++--- .../tests/ManagedIdentityCredentialTests.cs | 25 +++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs b/sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs index 9feef57957db..cdad21aa14eb 100644 --- a/sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs +++ b/sdk/identity/Azure.Identity/src/ManagedIdentityClient.cs @@ -87,10 +87,19 @@ public async ValueTask 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(); } @@ -127,5 +136,19 @@ private static ManagedIdentitySource SelectManagedIdentitySource(ManagedIdentity return TokenExchangeManagedIdentitySource.TryCreate(options) ?? new ImdsManagedIdentityProbeSource(options, client); } + + private static bool HasInnerExceptionMatching(Exception exception, Func condition) + { + var current = exception; + while (current != null) + { + if (condition(current)) + { + return true; + } + current = current.InnerException; + } + return false; + } } } diff --git a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs index da218d18fdf3..157e4082d155 100644 --- a/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs +++ b/sdk/identity/Azure.Identity/tests/ManagedIdentityCredentialTests.cs @@ -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; @@ -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(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) From 76a416c8b512c7c3a3660380fa23154f8dd1c17a Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 5 Jun 2025 13:49:17 -0500 Subject: [PATCH 2/3] changelog --- sdk/identity/Azure.Identity/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 8fad58e42382..2762da587478 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -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) From e1a73cf0ddb92f4d3e806b55702a9a735aabc232 Mon Sep 17 00:00:00 2001 From: Christopher Scott Date: Thu, 5 Jun 2025 17:14:41 -0500 Subject: [PATCH 3/3] Update sdk/identity/Azure.Identity/CHANGELOG.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sdk/identity/Azure.Identity/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/identity/Azure.Identity/CHANGELOG.md b/sdk/identity/Azure.Identity/CHANGELOG.md index 2762da587478..8922046953e4 100644 --- a/sdk/identity/Azure.Identity/CHANGELOG.md +++ b/sdk/identity/Azure.Identity/CHANGELOG.md @@ -8,7 +8,7 @@ ### 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)). +- `ManagedIdentityCredential` throws `CredentialUnavailableException` 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