Propagate KeyGuard attestation failures and bridge native MAA logs to the MSAL logger - #6081
Conversation
… the MSAL logger
KeyGuard attestation failures were silently swallowed: a failed attestation
returned null, which caused an empty / non-attested certificate request to be
sent to IMDS instead of surfacing the failure. Separately, native
AttestationClientLib (MAA) logs were written only to System.Diagnostics.Trace
and never reached the MSAL ILoggerAdapter, so the real failure reason was
invisible in MSAL verbose logs.
Error propagation:
- WithAttestationSupport now throws MsalServiceException("attestation_failed")
carrying Status / NativeErrorCode / reason instead of returning null. The
failure originates from the MAA service, so it is surfaced as a service error.
- ImdsV2ManagedIdentitySource.GetAttestationJwtAsync treats a null/empty token
from a configured provider as a hard failure for KeyGuard keys (no silent
non-attested fallback) and avoids double-wrapping MsalServiceException.
- AttestationClient maps the native return code to a readable reason via
AttestationErrors.Describe so the cause propagates to the caller.
- Client-side preconditions (mtls_pop_requires_keyguard,
credential_guard_requires_cng) remain MsalClientException.
Log bridging:
- AttestationLogger.CreateLoggerBridge forwards native AttestationClientLib log
callbacks into the MSAL ILoggerAdapter (Error/Warn/Info/Debug ->
Error/Warning/Info/Verbose), gated on IsLoggingEnabled and exception-safe,
with a Trace fallback when no logger is supplied.
- AttestationClient takes an optional ILoggerAdapter and keeps a strong
reference to the bridge delegate for the native callback lifetime;
PopKeyAttestor forwards the logger.
Tests:
- Added/updated unit tests in ImdsV2Tests covering attestation_failed
propagation (NativeError/Exception/TokenEmpty/NotInitialized/empty-JWT,
null/empty/throwing providers) and the log bridge (level mapping, routing,
gating, exception safety, null logger) plus AttestationErrors.Describe.
Fixes #6079
Fixes #6080
There was a problem hiding this comment.
Pull request overview
This PR fixes two related defects in the IMDSv2 mTLS PoP + Credential Guard/KeyGuard attestation path by (1) ensuring attestation failures are surfaced to callers (instead of silently falling back to a non-attested IMDS request) and (2) forwarding native AttestationClientLib (MAA) logs into the MSAL ILoggerAdapter for diagnosability.
Changes:
- Convert KeyGuard attestation failure cases into
MsalServiceException("attestation_failed")and treat null/empty attestation tokens as hard failures for KeyGuard keys. - Add a native-to-MSAL logger bridge (
AttestationLogger.CreateLoggerBridge) and plumbILoggerAdapterthroughPopKeyAttestor→AttestationClient. - Expand unit coverage for attestation failure propagation, native error reason mapping, and logger level mapping/routing.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs | Updates/expands unit tests to validate attestation_failed propagation and native log bridging behavior. |
| src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs | Enforces hard-fail behavior for empty attestation JWTs (KeyGuard) and avoids double-wrapping MsalServiceException. |
| src/client/Microsoft.Identity.Client.KeyAttestation/PopKeyAttestor.cs | Passes MSAL logger through to the native attestation client to enable bridged logging. |
| src/client/Microsoft.Identity.Client.KeyAttestation/ManagedIdentityAttestationExtensions.cs | Changes .WithAttestationSupport() to throw MsalServiceException("attestation_failed") on attestation failure instead of returning null. |
| src/client/Microsoft.Identity.Client.KeyAttestation/Attestation/AttestationLogger.cs | Implements logger bridging and level mapping from native AttestationClientLib logs into MSAL logs. |
| src/client/Microsoft.Identity.Client.KeyAttestation/Attestation/AttestationClient.cs | Adds optional logger support, keeps a strong reference to the native callback delegate, and maps native error codes to readable reasons. |
Exclude OperationCanceledException (incl. TaskCanceledException) from the generic catch in GetAttestationJwtAsync so cancellation bubbles out unchanged instead of being masked as an attestation_failed service error. Adds a unit test. Addresses PR review feedback.
|
thanks a lot Gladwin Johnson (@gladjohn) for accomodating the changes. Would this also propage the logs from the underlying cpp code (AttestationClientLib) when we set |
yes, Sheshagiri Rao Mallipedhi (@Sheshagiri) it will |
…ngelog - AttestationLogger.CreateLoggerBridge: route native Error/Warning/Info to the scrubbed (non-PII) slot so MAA diagnostics stay visible, and route the most verbose output (Debug -> Verbose, plus unknown levels) through the PII slot so any richer payload fragments are redacted unless PII logging is enabled. - Update routing test and add AttestationLogger_CreateLoggerBridge_VerboseLine_LogsAsPiiNotScrubbed. - CHANGELOG: call out the fail-closed behavior change for KeyGuard attestation plus attestation_failed propagation and native MAA log bridging. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pport WithAttestationSupport used IsNullOrEmpty, so a whitespace-only JWT from a "Success" AttestationResult was returned as a valid token. Downstream IMDSv2 code (IsNullOrWhiteSpace) then threw a generic "returned no token" error, discarding the richer Status/NativeErrorCode/Reason. Use IsNullOrWhiteSpace so the failure is surfaced here with full detail. Adds regression test MtlsPop_WithAttestationSupport_SuccessButWhitespaceJwt_ThrowsAttestationFailedWithReason. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Updated [Microsoft.Extensions.Configuration](https://github.com/dotnet/dotnet) from 10.0.9 to 10.0.10. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.Abstractions](https://github.com/dotnet/dotnet) from 10.0.9 to 10.0.10. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.Abstractions's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.Binder](https://github.com/dotnet/dotnet) from 10.0.9 to 10.0.10. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.Binder's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.EnvironmentVariables](https://github.com/dotnet/dotnet) from 10.0.9 to 10.0.10. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.EnvironmentVariables's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.Json](https://github.com/dotnet/dotnet) from 10.0.9 to 10.0.10. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.Json's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Configuration.UserSecrets](https://github.com/dotnet/dotnet) from 10.0.9 to 10.0.10. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Configuration.UserSecrets's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Extensions.Logging.Abstractions](https://github.com/dotnet/dotnet) from 10.0.9 to 10.0.10. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Extensions.Logging.Abstractions's releases](https://github.com/dotnet/dotnet/releases)._ No release notes found for this version range. Commits viewable in [compare view](https://github.com/dotnet/dotnet/commits). </details> Updated [Microsoft.Identity.Client](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) from 4.84.2 to 4.87.0. <details> <summary>Release notes</summary> _Sourced from [Microsoft.Identity.Client's releases](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/releases)._ ## 4.87.0 ## What's Changed * Expose `MsalServiceException.ErrorCodesForLogging` for diagnostics in #6138 * Expose `WithOtelTagsEnricher` for managed identity requests in #6144 * Forward OpenTelemetry tags enricher to the client-assertion callback in #6142 * Add client-side opaque-token log scrubber in #6119 * Populate `ExecutionResult.Exception` for non-MSAL failures in #6139 * Use PSS padding in KeyGuard liveness probe (CodeQL SM03799) in #6141 * Remove managed identity support from `WithClaimsFromClient` (confidential-client only) in #6113 * Remove experimental features from client setup in #6143 * Update Azure Arc managed identity API version from 2019-11-01 to 2020-06-01 in #6130 **Full Changelog**: AzureAD/microsoft-authentication-library-for-dotnet@4.86.1...4.87.0 ## 4.86.1 ### Bug Fixes - Fixed the mTLS Proof-of-Possession token cache to key on the certificate's full DER (`x5t#S256`) instead of only the public key, preventing a stale token (and `AADSTS500181`) after a same-key certificate renewal. [#6123](AzureAD/microsoft-authentication-library-for-dotnet#6123) - Fell back to RS256 when a certificate's PSS signing operation is rejected by `RSACryptoServiceProvider`, rebuilding the client assertion so authentication can proceed. [#6126](AzureAD/microsoft-authentication-library-for-dotnet#6126) - Detect and reject symbolic links in the Unix cache-file write path (lstat pre-check plus `O_NOFOLLOW`), closing a TOCTOU window. [#6115](AzureAD/microsoft-authentication-library-for-dotnet#6115) - Corrected misleading "region required" error messages and doc comments in the mTLS PoP flow. [#6127](AzureAD/microsoft-authentication-library-for-dotnet#6127) ## 4.86.0 ## What's Changed * Propagate KeyGuard attestation failures and bridge native MAA logs to the MSAL logger by @gladjohn in AzureAD/microsoft-authentication-library-for-dotnet#6081 * Token Binding Demo Helper by @gladjohn in AzureAD/microsoft-authentication-library-for-dotnet#6097 * Include ManagedIdentitySource in managed identity error messages and request-failure logs by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6101 * Remove Mooncake (AzureChinaCloud) lab client from WAM dev apps by @RyAuld in AzureAD/microsoft-authentication-library-for-dotnet#6103 * Surface token-acquisition failure metadata on MsalException by @neha-bhargava in AzureAD/microsoft-authentication-library-for-dotnet#6096 * Skip IMDS lookup for explicit regions by @4gust in AzureAD/microsoft-authentication-library-for-dotnet#6092 * Fix region failure-metadata test broken by explicit-region IMDS skip by @neha-bhargava in AzureAD/microsoft-authentication-library-for-dotnet#6105 **Full Changelog**: AzureAD/microsoft-authentication-library-for-dotnet@4.85.2...4.86.0 ## 4.85.2 ## What's Changed * Delegate IMDSv2 mTLS-PoP token leg to internal TokenClient exchange (MSIv2 WithClaimsFromClient) by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6070 * Enforce mTLS PoP minimum binding strength for Managed Identity (#6049 Phase 2) by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6059 * Add refresh token cache partitioning support by @iNinja in AzureAD/microsoft-authentication-library-for-dotnet#6077 * Detach ImdsV2ManagedIdentitySource from AbstractManagedIdentity (refused-bequest cleanup) by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6089 **Full Changelog**: AzureAD/microsoft-authentication-library-for-dotnet@4.85.1...4.85.2 ## 4.85.1 ## What's Changed * Migrate OBO tests from old lab to ID4SLAB1 by @RyAuld in AzureAD/microsoft-authentication-library-for-dotnet#6021 * Mark regional SNI mTLS PoP test inconclusive on AAD test-slice Bearer downgrade by @neha-bhargava in AzureAD/microsoft-authentication-library-for-dotnet#6084 * Expose canonical tag names per-metric by @ssmelov in AzureAD/microsoft-authentication-library-for-dotnet#6076 **Full Changelog**: AzureAD/microsoft-authentication-library-for-dotnet@4.85.0...4.85.1 ## 4.85.0 ## What's Changed * Fix proactive token refresh bypassing cancellation, leading to unbounded semaphore wait by @jayesh-a-shah in AzureAD/microsoft-authentication-library-for-dotnet#6054 * Add GovFr, GovDe, GovSg to AzureCloudInstance enum by @bgavrilMS in AzureAD/microsoft-authentication-library-for-dotnet#6023 * Take home account from request, if not available elsewhere. by @yowl in AzureAD/microsoft-authentication-library-for-dotnet#5657 * Validate Azure region format to prevent region poisoning (fixes #6060) by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6061 * Promote MsalServiceException.SubError to public by @neha-bhargava in AzureAD/microsoft-authentication-library-for-dotnet#6063 * Migrate region discovery to IMDS /compute JSON endpoint (#6039) by @Robbie-Microsoft in AzureAD/microsoft-authentication-library-for-dotnet#6057 * fix: Service Fabric MI sends principalId for ObjectId; reject ClientId/ResourceId (mirror of #6066) by @neha-bhargava in AzureAD/microsoft-authentication-library-for-dotnet#6069 * Exclude caller SDK telemetry from access token cache keys by @bgavrilMS in AzureAD/microsoft-authentication-library-for-dotnet#6074 * Add MSAL.NET telemetry enrichment by @ssmelov in AzureAD/microsoft-authentication-library-for-dotnet#6071 ## New Contributors * @jayesh-a-shah made their first contribution in AzureAD/microsoft-authentication-library-for-dotnet#6054 * @yowl made their first contribution in AzureAD/microsoft-authentication-library-for-dotnet#5657 **Full Changelog**: AzureAD/microsoft-authentication-library-for-dotnet@4.84.2...4.85.0 Commits viewable in [compare view](AzureAD/microsoft-authentication-library-for-dotnet@4.84.2...4.87.0). </details> Updated [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 18.6.0 to 18.8.1. <details> <summary>Release notes</summary> _Sourced from [Microsoft.NET.Test.Sdk's releases](https://github.com/microsoft/vstest/releases)._ ## 18.8.1 ## What's Changed * Fix protocol negotiation timeout when STJ reflection is disabled (18.8.1) by @nohwnd in microsoft/vstest#16281 **Full Changelog**: microsoft/vstest@v18.8.0...v18.8.1 ## 18.8.0 ## What's Changed * Migrate from Newtonsoft.Json to System.Text.Json / Jsonite (merge to main) by @nohwnd in microsoft/vstest#15687 - For more detail refer to https://devblogs.microsoft.com/dotnet/vs-test-is-removing-its-newtonsoft-json-dependency/ * Create source-only filter package by @Youssef1313 in microsoft/vstest#15638 * Add ARM64 msdia140.dll support to test platform packages by @nohwnd in microsoft/vstest#15692 * Fix mutex cleanup crash on macOS/Linux by @nohwnd in microsoft/vstest#15684 * Restrict artifact temp directory permissions on Unix by @nohwnd in microsoft/vstest#15729 * Add support for filtering uncategorized tests with TestCategory=None by @Evangelink in microsoft/vstest#15727 * Fix SCI binding failure in DTA hosts (main) by @nohwnd in microsoft/vstest#15724 * Fix HTML logger parallel file collision by @nohwnd in microsoft/vstest#15435 * Improve error message when testhost cannot be found by @nohwnd in microsoft/vstest#16053 * Fix HTML logger exception on invalid XML chars in test display names by @nohwnd in microsoft/vstest#16051 **Full Changelog**: microsoft/vstest@v18.7.0...v18.8.0 ## 18.7.0 ## What's Changed * Add ARM64 msdia140.dll support to test platform packages by @jamesmcroft in microsoft/vstest#15689 * Update System.Memory from 4.5.5 to 4.6.3 by @nohwnd in microsoft/vstest#15706 ## New Contributors * @jamesmcroft made their first contribution in microsoft/vstest#15689 **Full Changelog**: microsoft/vstest@v18.6.0...v18.7.0 Commits viewable in [compare view](microsoft/vstest@v18.6.0...v18.8.1). </details> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
Fixes two defects in the IMDSv2 mTLS PoP + Credential Guard / KeyGuard attestation flow.
1. Propagate KeyGuard attestation failures (instead of sending an empty token to IMDS)
Previously, a failed attestation was mapped to
null, which caused an empty / non-attested certificate request to be sent to IMDS — silently dropping the real failure (e.g. an MAA400 PolicyEvaluationError/x-ms-azurevm-dbxvalidated=falsedeny).WithAttestationSupportnow throwsMsalServiceException("attestation_failed")carryingStatus/NativeErrorCode/ reason instead of returningnull. The failure originates from the MAA service, so it surfaces as a service exception.ImdsV2ManagedIdentitySource.GetAttestationJwtAsynctreats a null/empty token from a configured provider as a hard failure for KeyGuard keys (no silent non-attested fallback) and avoids double-wrappingMsalServiceException.AttestationClientmaps the native return code to a readable reason viaAttestationErrors.Describeso the cause propagates.mtls_pop_requires_keyguard,credential_guard_requires_cng) remainMsalClientException.2. Bridge native MAA logs into the MSAL logger
Native
AttestationClientLib(MAA) logs were written only toSystem.Diagnostics.Traceand never reached the MSALILoggerAdapter, so failures were invisible in MSAL verbose logs.AttestationLogger.CreateLoggerBridgeforwards native log callbacks into the MSAL logger (Error/Warn/Info/Debug→Error/Warning/Info/Verbose), gated onIsLoggingEnabled, exception-safe, with aTracefallback when no logger is supplied.AttestationClienttakes an optionalILoggerAdapter(keeping a strong reference to the bridge delegate for the native callback lifetime);PopKeyAttestorforwards the logger.Validation
ImdsV2Testscoveringattestation_failedpropagation (NativeError / Exception / TokenEmpty / NotInitialized / empty-JWT, and null / empty / throwing providers) and the log bridge (level mapping, routing, gating, exception safety, null logger) plusAttestationErrors.Describe.Microsoft.Identity.Test.Unitbuilds clean (net8.0 + netstandard2.0);ImdsV2Tests99/99 pass.Fixes #6079
Fixes #6080