Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
### New Features
- Exposed canonical OpenTelemetry tag names per metric via `MsalMetricsCatalog.CanonicalTagsByMetric` for discoverability and validation. [#6076](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/6076)

### Changes
- Managed identity error messages and request-failure logs now include the detected `ManagedIdentitySource` (e.g., `AppService`, `Imds`, `ServiceFabric`) so the host-issued `Managed Identity Correlation ID` can be traced to the correct host's telemetry. [#6101](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/6101)

Comment thread
Robbie-Microsoft marked this conversation as resolved.
4.85.0
======

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ protected virtual Task<ManagedIdentityResponse> HandleResponseAsync(

string message = GetMessageFromErrorResponse(response);

_requestContext.Logger.Error($"[Managed Identity] request failed, HttpStatusCode: {response.StatusCode} Error message: {message}");
_requestContext.Logger.Error($"[Managed Identity] request failed, Source: {_sourceType}, HttpStatusCode: {response.StatusCode} Error message: {message}");

MsalException exception = MsalServiceExceptionFactory.CreateManagedIdentityException(
MsalError.ManagedIdentityRequestFailed,
Expand Down Expand Up @@ -281,7 +281,10 @@ private string ExtractErrorMessageFromManagedIdentityErrorResponse(ManagedIdenti

if (!string.IsNullOrEmpty(managedIdentityErrorResponse.CorrelationId))
{
stringBuilder.Append($"Managed Identity Correlation ID: {managedIdentityErrorResponse.CorrelationId} Use this Correlation ID for further investigation.");
stringBuilder.Append(
$"Managed Identity Correlation ID: {managedIdentityErrorResponse.CorrelationId} " +
$"(issued by the '{_sourceType}' managed identity source; search that source's telemetry with this correlation ID). " +
$"Use this Correlation ID for further investigation.");
}

if (stringBuilder.Length == ManagedIdentityPrefix.Length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,8 @@ await mi.AcquireTokenForManagedIdentity(resource)
}

[TestMethod]
[DataRow("{\"statusCode\":500,\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID" })]
[DataRow("{\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID" })]
[DataRow("{\"statusCode\":500,\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID", "issued by the 'AppService' managed identity source" })]
[DataRow("{\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID", "issued by the 'AppService' managed identity source" })]
[DataRow("{\"error\":\"errorCode\",\"error_description\":\"Error message\"}", new string[] { "errorCode", "Error message" })]
[DataRow("{\"error_description\":\"Error message\"}", new string[] { "Error message" })]
[DataRow("{\"message\":\"Error message\"}", new string[] { "Error message" })]
Expand Down Expand Up @@ -606,6 +606,48 @@ await mi.AcquireTokenForManagedIdentity(Resource)
}
}

// The correlation-ID branch that attributes the host-issued correlation ID to a managed
// identity source is source-agnostic, so this guards it against regression for a
// non-AppService source (Imds) as well. It also asserts the full composed phrase rather
// than a bare source name, so the check cannot pass merely because the source name appears
// in the echoed error body.
[TestMethod]
[DataRow(ManagedIdentitySource.AppService, AppServiceEndpoint)]
[DataRow(ManagedIdentitySource.Imds, ImdsEndpoint)]
public async Task ManagedIdentityErrorMessageAttributesCorrelationIdToSourceAsync(ManagedIdentitySource managedIdentitySource, string endpoint)
{
using (new EnvVariableContext())
using (var httpManager = new MockHttpManager(disableInternalRetries: true))
{
SetEnvironmentVariables(managedIdentitySource, endpoint);

var mi = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned)
.WithHttpManager(httpManager)
.Build();

// camelCase "correlationId" maps to ManagedIdentityErrorResponse.CorrelationId, which
// triggers the correlation-ID branch that names the managed identity source.
const string errorResponse = "{\"statusCode\":500,\"message\":\"Error message\",\"correlationId\":\"some-correlation-id\"}";

httpManager.AddManagedIdentityMockHandler(endpoint, Resource, errorResponse,
managedIdentitySource, statusCode: HttpStatusCode.InternalServerError);

MsalServiceException ex = await Assert.ThrowsAsync<MsalServiceException>(async () =>
await mi.AcquireTokenForManagedIdentity(Resource)
.ExecuteAsync().ConfigureAwait(false)).ConfigureAwait(false);

Assert.IsNotNull(ex);
Assert.AreEqual(managedIdentitySource.ToString(), ex.AdditionalExceptionData[MsalException.ManagedIdentitySource]);
Assert.AreEqual(MsalError.ManagedIdentityRequestFailed, ex.ErrorCode);

// Assert the exact composed phrase (position-independent-proof): only MSAL's message
// builder produces this text, so it cannot be satisfied by the echoed error body.
string expectedSourcePhrase = $"issued by the '{managedIdentitySource}' managed identity source";
Assert.Contains(expectedSourcePhrase, ex.Message,
$"Expected the message to attribute the correlation ID to the '{managedIdentitySource}' source. Actual error message: {ex.Message}");
}
}

[TestMethod]
[DataRow("", ManagedIdentitySource.AppService, AppServiceEndpoint)]
[DataRow(null, ManagedIdentitySource.AppService, AppServiceEndpoint)]
Expand Down
Loading