diff --git a/CHANGELOG.md b/CHANGELOG.md index 17a217cc10..4a8de3ec48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ====== ### New Features +- Added `ManagedIdentityApplication.GetManagedIdentityCapabilitiesAsync(CancellationToken)` returning a `ManagedIdentityCapabilities` object that reports the detected managed identity `Source`, the host's `MaxSupportedBindingStrength` (new `MtlsBindingStrength` enum: `None`, `Software`, `KeyGuard`), and a derived `IsMtlsPopSupportedByHost`. Replaces `GetManagedIdentitySourceAsync()`/`ManagedIdentitySourceResult`. The public `ManagedIdentitySource.ImdsV2` value is folded into `Imds` (v1/v2 routing remains internal). [#6049](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/6049) - Added `CacheOptions.DisableInternalCacheOptions` static property and `CacheOptions.IsInternalCacheDisabled` to allow disabling MSAL's internal token cache. Added `CacheRefreshReason.CacheDisabled` and `MsalError.InternalCacheDisabled` to support this scenario. [#5947](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5947) - Added `AuthenticationResultExtensions.GetRefreshToken()` extension method for accessing refresh tokens from `AuthenticationResult`. [#5947](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5947) - Added `WithAttributeTokens` and `WithExtraBodyParameters` extension methods on `AbstractConfidentialClientAcquireTokenParameterBuilder` for enhanced extensibility. [#5888](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/5888) diff --git a/src/client/Microsoft.Identity.Client/AppConfig/MtlsBindingStrength.cs b/src/client/Microsoft.Identity.Client/AppConfig/MtlsBindingStrength.cs new file mode 100644 index 0000000000..e792b60f86 --- /dev/null +++ b/src/client/Microsoft.Identity.Client/AppConfig/MtlsBindingStrength.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Identity.Client.AppConfig +{ + /// + /// Describes the strength with which a token can be bound to a cryptographic key on the + /// current host. Higher values indicate stronger binding. The value reflects what the host + /// is capable of producing, not what a particular request used. + /// + /// + /// This type is shared by managed identity and confidential client mTLS Proof-of-Possession + /// scenarios. A value greater than means the host can bind a token to a + /// key; it does not by itself imply hardware attestation. Attestation corresponds to + /// the tier specifically. + /// + public enum MtlsBindingStrength + { + /// + /// No key binding is available, so the host cannot perform mTLS Proof-of-Possession. This + /// is the floor of the range (for example, on .NET Framework 4.6.2, which does not support + /// PoP). + /// + None = 0, + + /// + /// The token can be bound to a software-backed key (for example, a persisted CNG key on + /// Windows, or a software RSA key elsewhere). The key is not hardware-isolated. + /// + Software = 1, + + // 2 is reserved for a future tier (for example, TPM-backed keys). + + /// + /// The token can be bound to a key isolated by Virtualization-based Security (VBS), such + /// as KeyGuard on a Trusted Launch (TVM) or Confidential (CVM) virtual machine. This is + /// the only tier that implies hardware-backed attestation. + /// + KeyGuard = 3 + } +} diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs index 2668c14c02..a4771ba7fc 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs @@ -38,6 +38,11 @@ protected AbstractManagedIdentity(RequestContext requestContext, ManagedIdentity _sourceType = sourceType; } + // True only for the IMDSv1 source. IMDSv1 and IMDSv2 both report + // publicly, so this flag preserves the + // v1-specific MSIv1 claims validation without relying on the (folded) source label. + protected virtual bool RequiresMsiV1ClaimsValidation => false; + private const string XmsAzNwperimid = "xms_az_nwperimid"; public virtual async Task AuthenticateAsync( @@ -65,16 +70,16 @@ public virtual async Task AuthenticateAsync( // ignoring the value and polluting the cache with keys the endpoint never saw. if (!string.IsNullOrEmpty(parameters.ClientClaims)) { - if (_sourceType != ManagedIdentitySource.Imds && _sourceType != ManagedIdentitySource.ImdsV2) + if (_sourceType != ManagedIdentitySource.Imds) { throw new MsalClientException( MsalError.InvalidRequest, $"WithClaimsFromClient is only supported for IMDS-based managed identity sources. " + $"The detected source is {_sourceType}. " + - "Only ManagedIdentitySource.Imds and ManagedIdentitySource.ImdsV2 support the 'claims' parameter."); + "Only ManagedIdentitySource.Imds supports the 'claims' parameter."); } - if (_sourceType == ManagedIdentitySource.Imds) + if (RequiresMsiV1ClaimsValidation) { ValidateMsiv1Claims(parameters.ClientClaims); } diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ComputeMetadataResponse.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ComputeMetadataResponse.cs new file mode 100644 index 0000000000..b358685ead --- /dev/null +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ComputeMetadataResponse.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Identity.Client.Platforms.net; +using JsonProperty = System.Text.Json.Serialization.JsonPropertyNameAttribute; + +namespace Microsoft.Identity.Client.ManagedIdentity +{ + /// + /// Represents compute metadata retrieved from the Azure Instance Metadata Service (IMDS). + /// + [JsonObject] + [Preserve(AllMembers = true)] + internal class ComputeMetadataResponse + { + /// Operating system type (e.g., Windows, Linux). + [JsonProperty("osType")] + public string OsType { get; set; } + + /// + /// Security profile indicating platform security posture. May be null when IMDS + /// does not return security profile information for the current VM. + /// + [JsonProperty("securityProfile")] + public ComputeSecurityProfile SecurityProfile { get; set; } + } + + /// + /// Represents the security profile of an Azure VM from IMDS compute metadata. + /// + [JsonObject] + [Preserve(AllMembers = true)] + internal class ComputeSecurityProfile + { + /// Security type of the VM (e.g., TrustedLaunch, ConfidentialVM). + [JsonProperty("securityType")] + public string SecurityType { get; set; } + } +} diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsComputeMetadataManager.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsComputeMetadataManager.cs new file mode 100644 index 0000000000..b076faaa94 --- /dev/null +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsComputeMetadataManager.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Identity.Client.Core; +using Microsoft.Identity.Client.Http; +using Microsoft.Identity.Client.Http.Retry; +using Microsoft.Identity.Client.Utils; + +namespace Microsoft.Identity.Client.ManagedIdentity +{ + /// + /// Fetches compute metadata from the Azure Instance Metadata Service (IMDS) + /// to determine VM characteristics such as OS type and security profile. + /// + internal static class ImdsComputeMetadataManager + { + internal const string ImdsComputePath = "/metadata/instance/compute"; + internal const string ImdsComputeApiVersion = "2021-02-01"; + + internal static async Task GetComputeMetadataAsync( + IHttpManager httpManager, + ILoggerAdapter logger, + CancellationToken cancellationToken) + { + var headers = new Dictionary + { + { "Metadata", "true" } + }; + + try + { + string queryParams = + $"{ImdsManagedIdentitySource.ApiVersionQueryParam}={ImdsComputeApiVersion}"; + + Uri endpoint = ImdsManagedIdentitySource.GetValidatedEndpoint( + logger, + ImdsComputePath, + queryParams); + + HttpResponse response = await httpManager.SendRequestAsync( + endpoint, + headers, + body: null, + method: HttpMethod.Get, + logger: logger, + doNotThrow: true, + mtlsCertificate: null, + validateServerCertificate: null, + cancellationToken: cancellationToken, + retryPolicy: new ImdsRetryPolicy()) + .ConfigureAwait(false); + + if (response is null || response.StatusCode != HttpStatusCode.OK) + { + logger.Info($"[Managed Identity] IMDS compute metadata request failed. " + + $"StatusCode: {response?.StatusCode}"); + return null; + } + + return JsonHelper.TryToDeserializeFromJson(response.Body); + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + logger.Info($"[Managed Identity] IMDS compute metadata request failed with exception: {ex.Message}"); + return null; + } + } + + /// + /// Determines whether the host VM supports mTLS PoP based on compute metadata. + /// mTLS PoP is supported when the VM runs Windows and is a TVM (TrustedLaunch) or CVM (ConfidentialVM). + /// + internal static bool IsMtlsPopSupported(ComputeMetadataResponse metadata) + { + if (metadata is null) + { + return false; + } + + bool isWindows = string.Equals(metadata.OsType, "Windows", StringComparison.OrdinalIgnoreCase); + + string securityType = metadata.SecurityProfile?.SecurityType; + bool isTvmOrCvm = string.Equals(securityType, "TrustedLaunch", StringComparison.OrdinalIgnoreCase) + || string.Equals(securityType, "ConfidentialVM", StringComparison.OrdinalIgnoreCase); + + return isWindows && isTvmOrCvm; + } + } +} diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsManagedIdentitySource.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsManagedIdentitySource.cs index 94b8eb833d..71d1ca7c61 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsManagedIdentitySource.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsManagedIdentitySource.cs @@ -60,6 +60,9 @@ internal ImdsManagedIdentitySource(RequestContext requestContext) : requestContext.Logger.Verbose(() => "[Managed Identity] Creating IMDS managed identity source. Endpoint URI: " + _imdsEndpoint); } + // IMDSv1 enforces MSIv1-specific claims validation; IMDSv2 does not. + protected override bool RequiresMsiV1ClaimsValidation => true; + protected override Task CreateRequestAsync(string resource) { ManagedIdentityRequest request = new(HttpMethod.Get, _imdsEndpoint); diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityCapabilities.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityCapabilities.cs new file mode 100644 index 0000000000..23e624aaff --- /dev/null +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityCapabilities.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Identity.Client.AppConfig; + +namespace Microsoft.Identity.Client.ManagedIdentity +{ + /// + /// Describes the managed identity capabilities detected for the current host, including the + /// detected source and the strength with which tokens can be bound to a key. + /// + /// + /// This type is returned by . + /// It is useful for credential chains such as DefaultAzureCredential to decide whether + /// managed identity is available and what binding strength the host supports. + /// + public class ManagedIdentityCapabilities + { + /// + /// Gets the detected managed identity source. + /// + /// + /// The detected on the environment, or + /// if no source was detected. The internal + /// IMDS v1/v2 distinction is not surfaced here; both report + /// . + /// + public ManagedIdentitySource Source { get; } + + /// + /// Gets the reason detection failed, if any. + /// + /// + /// A single string describing why managed identity detection failed, or null when + /// a source was detected. + /// + public string ErrorReason { get; } + + /// + /// Gets the highest binding strength the current host is capable of producing. + /// + /// + /// The strongest available on this host. This is the + /// primary capability signal; callers should branch on it rather than on the source label. + /// + public MtlsBindingStrength MaxSupportedBindingStrength { get; } + + /// + /// Gets a value indicating whether the host can bind a token to a key (mTLS + /// Proof-of-Possession). + /// + /// + /// true when is greater than + /// . This means the host can bind a token to a key; + /// it does not imply hardware attestation. Callers that require attestation must + /// check for the tier. + /// + public bool IsMtlsPopSupportedByHost => MaxSupportedBindingStrength > MtlsBindingStrength.None; + + /// + /// Initializes a new instance of the class. + /// + /// The detected managed identity source. + /// The highest binding strength the host supports. + /// The reason detection failed, or null on success. + internal ManagedIdentityCapabilities( + ManagedIdentitySource source, + MtlsBindingStrength maxSupportedBindingStrength, + string errorReason = null) + { + Source = source; + MaxSupportedBindingStrength = maxSupportedBindingStrength; + ErrorReason = errorReason; + } + } +} diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs index 005c70c5cd..df1f7e4102 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client.ApiConfig.Parameters; +using Microsoft.Identity.Client.AppConfig; using Microsoft.Identity.Client.Core; using Microsoft.Identity.Client.Internal; using Microsoft.Identity.Client.ManagedIdentity.V2; @@ -22,9 +23,13 @@ internal class ManagedIdentityClient private const string WindowsHimdsFilePath = "%Programfiles%\\AzureConnectedMachineAgent\\himds.exe"; private const string LinuxHimdsFilePath = "/opt/azcmagent/bin/himds"; - // Non-null only after the explicit discovery API (GetManagedIdentitySourceAsync) runs. + // Non-null only after the explicit discovery API (GetManagedIdentityCapabilitiesAsync) runs. // Allows caching "NoneFound" (Source=None) without confusing it with "not discovered yet". - private static ManagedIdentitySourceResult s_cachedSourceResult = null; + private static ManagedIdentityDiscoveryResult s_cachedSourceResult = null; + + // Serializes explicit capability discovery so concurrent callers at process startup do not + // issue redundant IMDS probes or provision the binding key more than once. + private static readonly SemaphoreSlim s_discoveryLock = new SemaphoreSlim(1, 1); // Holds the most recently minted mTLS binding certificate for this application instance. private X509Certificate2 _runtimeMtlsBindingCertificate; @@ -67,11 +72,13 @@ private Task GetOrSelectManagedIdentitySourceAsync( cancellationToken.ThrowIfCancellationRequested(); ManagedIdentitySource source; + bool isImdsV2 = false; if (s_cachedSourceResult != null) { // Use the cached explicit discovery result (including NoneFound) source = s_cachedSourceResult.Source; + isImdsV2 = s_cachedSourceResult.DetectedImdsVersion == ImdsVersion.V2; requestContext.Logger.Info($"[Managed Identity] Using cached discovery result: {source}"); } else @@ -103,18 +110,18 @@ private Task GetOrSelectManagedIdentitySourceAsync( // Per-request fallback: if ImdsV2 is cached but mTLS PoP not requested, use ImdsV1 for this request only. // We do NOT latch this state; future PoP requests can still leverage the cached ImdsV2 discovery. - if (source == ManagedIdentitySource.ImdsV2 && !isMtlsPopRequested) + if (isImdsV2 && !isMtlsPopRequested) { requestContext.Logger.Info("[Managed Identity] ImdsV2 detected, but mTLS PoP was not requested. Using ImdsV1 for this request only. Please use the \"WithMtlsProofOfPossession\" API to request a token via ImdsV2."); // Do NOT modify s_cachedSourceResult; keep cached ImdsV2 so future PoP - // requests can leverage it. - source = ManagedIdentitySource.Imds; + // requests can leverage it. Route this request through IMDSv1 only. + isImdsV2 = false; } // If the source is determined to be ImdsV1 and mTLS PoP was requested, // throw an exception since ImdsV1 does not support mTLS PoP - if (source == ManagedIdentitySource.Imds && isMtlsPopRequested) + if (source == ManagedIdentitySource.Imds && !isImdsV2 && isMtlsPopRequested) { throw new MsalClientException( MsalError.MtlsPopTokenNotSupportedinImdsV1, @@ -128,70 +135,200 @@ private Task GetOrSelectManagedIdentitySourceAsync( ManagedIdentitySource.MachineLearning => MachineLearningManagedIdentitySource.Create(requestContext), ManagedIdentitySource.CloudShell => CloudShellManagedIdentitySource.Create(requestContext), ManagedIdentitySource.AzureArc => AzureArcManagedIdentitySource.Create(requestContext), - ManagedIdentitySource.ImdsV2 => ImdsV2ManagedIdentitySource.Create(requestContext), - ManagedIdentitySource.Imds => ImdsManagedIdentitySource.Create(requestContext), + ManagedIdentitySource.Imds => isImdsV2 + ? ImdsV2ManagedIdentitySource.Create(requestContext) + : ImdsManagedIdentitySource.Create(requestContext), _ => throw CreateManagedIdentityUnavailableException(s_cachedSourceResult) }); } } - private static ManagedIdentitySourceResult CacheDiscoveryResult(ManagedIdentitySourceResult result) + private static ManagedIdentityDiscoveryResult CacheDiscoveryResult(ManagedIdentityDiscoveryResult result) { s_cachedSourceResult = result; return result; } // Detect managed identity source by probing IMDS endpoints. - // This method is called only by the explicit discovery path (GetManagedIdentitySourceAsync in ManagedIdentityApplication.cs). + // This method is called only by the explicit discovery path (GetManagedIdentityCapabilitiesAsync in ManagedIdentityApplication.cs). // It probes IMDS v2 first, then v1 if v2 fails, and caches the result. - internal async Task GetManagedIdentitySourceAsync( + internal async Task GetManagedIdentityCapabilitiesAsync( RequestContext requestContext, CancellationToken cancellationToken) { - // Return cached result if explicit discovery already ran + // Fast path: explicit discovery already completed. if (s_cachedSourceResult != null) { return s_cachedSourceResult; } - // First check env vars to avoid the probe if possible - ManagedIdentitySource source = GetManagedIdentitySourceNoImds(requestContext.Logger); - - if (source != ManagedIdentitySource.None) + // Single-flight: ensure only one caller probes IMDS / provisions a binding key at a + // time. Concurrent callers at process startup wait here and then observe the cached + // result instead of issuing redundant probes. Try a non-blocking acquire first so an + // uncontended caller keeps the existing cancellation point (the HTTP probe); only a + // contended caller waits, and that wait is cancelable. + bool lockTaken = s_discoveryLock.Wait(0); + if (!lockTaken) { - return CacheDiscoveryResult(new ManagedIdentitySourceResult(source)); + await s_discoveryLock.WaitAsync(cancellationToken).ConfigureAwait(false); + lockTaken = true; } - string imdsV1FailureReason = null; - string imdsV2FailureReason = null; + try + { + // Re-check under the lock in case another caller completed discovery while we waited. + if (s_cachedSourceResult != null) + { + return s_cachedSourceResult; + } + + // First check env vars to avoid the probe if possible + ManagedIdentitySource source = GetManagedIdentitySourceNoImds(requestContext.Logger); + + if (source != ManagedIdentitySource.None) + { + return CacheDiscoveryResult(new ManagedIdentityDiscoveryResult(source)); + } + + string imdsV1FailureReason = null; + string imdsV2FailureReason = null; + + // Probe IMDS v2 first. The v2 path (CSR metadata endpoint) only exists on hosts that + // actually support IMDSv2; on v1-only hosts it returns 404. Probing v2 first avoids + // the v1 success-on-400 contract masking a v2-capable host (see issue #6024). + var (imdsV2Success, imdsV2Failure) = await ImdsManagedIdentitySource.ProbeImdsEndpointAsync(requestContext, ImdsVersion.V2, cancellationToken).ConfigureAwait(false); + if (imdsV2Success) + { + requestContext.Logger.Info("[Managed Identity] ImdsV2 detected."); + + // A successful IMDSv2 probe proves the host speaks the key-bound CSR (PoP) protocol, + // so it can bind at least at Software strength. Probe the platform key provider to see + // whether it can produce a VBS-isolated KeyGuard key and thus advertise the stronger, + // attested KeyGuard tier. The v2 PoP token flow itself requires a KeyGuard key, so this + // mirrors what an actual PoP request would obtain. + MtlsBindingStrength v2Strength = await DetermineImdsV2BindingStrengthAsync(requestContext, cancellationToken).ConfigureAwait(false); + requestContext.Logger.Info($"[Managed Identity] Host max supported binding strength: {v2Strength}."); + + return CacheDiscoveryResult(new ManagedIdentityDiscoveryResult( + ManagedIdentitySource.Imds, + ImdsVersion.V2, + v2Strength)); + } + imdsV2FailureReason = imdsV2Failure; + + // If v2 fails, fall back to probing IMDS v1. + var (imdsV1Success, imdsV1Failure) = await ImdsManagedIdentitySource.ProbeImdsEndpointAsync(requestContext, ImdsVersion.V1, cancellationToken).ConfigureAwait(false); + if (imdsV1Success) + { + requestContext.Logger.Info("[Managed Identity] ImdsV1 detected."); + + MtlsBindingStrength strength = await DetermineImdsV1BindingStrengthAsync(requestContext, cancellationToken).ConfigureAwait(false); + requestContext.Logger.Info($"[Managed Identity] Host max supported binding strength: {strength}."); - // Probe IMDS v2 first. The v2 path (CSR metadata endpoint) only exists on hosts that - // actually support IMDSv2; on v1-only hosts it returns 404. Probing v2 first avoids - // the v1 success-on-400 contract masking a v2-capable host (see issue #6024). - var (imdsV2Success, imdsV2Failure) = await ImdsManagedIdentitySource.ProbeImdsEndpointAsync(requestContext, ImdsVersion.V2, cancellationToken).ConfigureAwait(false); - if (imdsV2Success) + return CacheDiscoveryResult(new ManagedIdentityDiscoveryResult( + ManagedIdentitySource.Imds, + ImdsVersion.V1, + strength)); + } + imdsV1FailureReason = imdsV1Failure; + + requestContext.Logger.Info($"[Managed Identity] {MsalErrorMessage.ManagedIdentityAllSourcesUnavailable}"); + return CacheDiscoveryResult(new ManagedIdentityDiscoveryResult( + ManagedIdentitySource.None, + imdsV1FailureReason: imdsV1FailureReason, + imdsV2FailureReason: imdsV2FailureReason)); + } + finally { - requestContext.Logger.Info("[Managed Identity] ImdsV2 detected."); - return CacheDiscoveryResult(new ManagedIdentitySourceResult(ManagedIdentitySource.ImdsV2)); + if (lockTaken) + { + s_discoveryLock.Release(); + } } - imdsV2FailureReason = imdsV2Failure; + } - // If v2 fails, fall back to probing IMDS v1. - var (imdsV1Success, imdsV1Failure) = await ImdsManagedIdentitySource.ProbeImdsEndpointAsync(requestContext, ImdsVersion.V1, cancellationToken).ConfigureAwait(false); - if (imdsV1Success) + // Determines the host's maximum mTLS binding strength for IMDSv1-only hosts using the + // /metadata/instance/compute security profile. mTLS PoP is not supported on .NET + // Framework 4.6.2, so the host is reported as None there. + private static Task DetermineImdsV1BindingStrengthAsync( + RequestContext requestContext, + CancellationToken cancellationToken) + { +#if NET462 + return Task.FromResult(MtlsBindingStrength.None); +#else + return DetermineImdsV1BindingStrengthCoreAsync(requestContext, cancellationToken); +#endif + } + +#if !NET462 + private static async Task DetermineImdsV1BindingStrengthCoreAsync( + RequestContext requestContext, + CancellationToken cancellationToken) + { + ComputeMetadataResponse computeMetadata = await ImdsComputeMetadataManager.GetComputeMetadataAsync( + requestContext.ServiceBundle.HttpManager, + requestContext.Logger, + cancellationToken).ConfigureAwait(false); + + // A Windows TVM/CVM security profile indicates key-binding capability. We report + // Software (binding available) rather than KeyGuard: the security profile alone does + // not prove a successful VBS/KeyGuard attestation, and an IMDSv1-only host cannot use + // the v2 CSR (PoP) flow regardless, so we must not overclaim attestation. + return ImdsComputeMetadataManager.IsMtlsPopSupported(computeMetadata) + ? MtlsBindingStrength.Software + : MtlsBindingStrength.None; + } +#endif + + // Determines the IMDSv2 host's maximum mTLS binding strength. The host supports at least + // Software binding (the v2 CSR flow binds a token to a key); if the platform can produce a + // VBS-isolated KeyGuard key it supports the stronger, attested KeyGuard tier. mTLS PoP is + // unavailable on .NET Framework 4.6.2, so the host is reported as None there. + private static Task DetermineImdsV2BindingStrengthAsync( + RequestContext requestContext, + CancellationToken cancellationToken) + { +#if NET462 + return Task.FromResult(MtlsBindingStrength.None); +#else + return DetermineImdsV2BindingStrengthCoreAsync(requestContext, cancellationToken); +#endif + } + +#if !NET462 + private static async Task DetermineImdsV2BindingStrengthCoreAsync( + RequestContext requestContext, + CancellationToken cancellationToken) + { + ManagedIdentityKeyType keyType; + try { - requestContext.Logger.Info("[Managed Identity] ImdsV1 detected."); - return CacheDiscoveryResult(new ManagedIdentitySourceResult(ManagedIdentitySource.Imds)); + IManagedIdentityKeyProvider keyProvider = requestContext.ServiceBundle.PlatformProxy.ManagedIdentityKeyProvider; + ManagedIdentityKeyInfo keyInfo = await keyProvider + .GetOrCreateKeyAsync(requestContext.Logger, cancellationToken) + .ConfigureAwait(false); + keyType = keyInfo.Type; } - imdsV1FailureReason = imdsV1Failure; - - requestContext.Logger.Info($"[Managed Identity] {MsalErrorMessage.ManagedIdentityAllSourcesUnavailable}"); - return CacheDiscoveryResult(new ManagedIdentitySourceResult(ManagedIdentitySource.None) + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) { - ImdsV1FailureReason = imdsV1FailureReason, - ImdsV2FailureReason = imdsV2FailureReason - }); + // Failing to obtain a key does not invalidate the host's v2/Software capability; + // keep the Software floor rather than failing capability discovery. + requestContext.Logger.Info($"[Managed Identity] KeyGuard probe failed; reporting Software binding strength. {ex.Message}"); + return MtlsBindingStrength.Software; + } + + // Only a VBS-isolated KeyGuard key justifies the attested KeyGuard tier. Any other key + // type stays at the Software floor; we never downgrade a confirmed v2 host below Software. + return keyType == ManagedIdentityKeyType.KeyGuard + ? MtlsBindingStrength.KeyGuard + : MtlsBindingStrength.Software; } +#endif /// /// Detects the managed identity source based on the availability of environment variables. @@ -281,24 +418,14 @@ private static bool ValidateAzureArcEnvironment(string identityEndpoint, string /// Creates an MsalClientException for when no managed identity source is available, /// including detailed failure information from IMDS probes if available. /// - private static MsalClientException CreateManagedIdentityUnavailableException(ManagedIdentitySourceResult sourceResult) + private static MsalClientException CreateManagedIdentityUnavailableException(ManagedIdentityDiscoveryResult discoveryResult) { string errorMessage = MsalErrorMessage.ManagedIdentityAllSourcesUnavailable; - if (sourceResult != null) + string combinedReason = discoveryResult?.GetCombinedErrorReason(); + if (!string.IsNullOrEmpty(combinedReason)) { - if (!string.IsNullOrEmpty(sourceResult.ImdsV1FailureReason) || !string.IsNullOrEmpty(sourceResult.ImdsV2FailureReason)) - { - errorMessage += " MSAL was not able to detect the Azure Instance Metadata Service (IMDS) that runs on VMs:"; - if (!string.IsNullOrEmpty(sourceResult.ImdsV2FailureReason)) - { - errorMessage += $" IMDSv2: {sourceResult.ImdsV2FailureReason}."; - } - if (!string.IsNullOrEmpty(sourceResult.ImdsV1FailureReason)) - { - errorMessage += $" IMDSv1: {sourceResult.ImdsV1FailureReason}."; - } - } + errorMessage += " The Azure Instance Metadata Service (IMDS) that runs on VMs was not detected: " + combinedReason; } return new MsalClientException(MsalError.ManagedIdentityAllSourcesUnavailable, errorMessage); diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityDiscoveryResult.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityDiscoveryResult.cs new file mode 100644 index 0000000000..7abdedb984 --- /dev/null +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityDiscoveryResult.cs @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Identity.Client.AppConfig; +using Microsoft.Identity.Client.ManagedIdentity.V2; + +namespace Microsoft.Identity.Client.ManagedIdentity +{ + /// + /// Internal result of managed identity source discovery. Carries the public-facing source + /// plus internal-only routing details (such as the detected IMDS protocol version) that are + /// not exposed on the public surface. + /// + internal sealed class ManagedIdentityDiscoveryResult + { + /// + /// The detected managed identity source. The IMDS v1/v2 distinction is folded into + /// ; the version is carried separately in + /// for internal routing. + /// + public ManagedIdentitySource Source { get; } + + /// + /// The IMDS protocol version detected, when is + /// ; otherwise null. Internal routing only. + /// + public ImdsVersion? DetectedImdsVersion { get; } + + /// + /// The highest binding strength the host can produce. + /// + public MtlsBindingStrength MaxSupportedBindingStrength { get; } + + /// + /// Failure reason from the IMDSv1 probe, if it failed; otherwise null. + /// + public string ImdsV1FailureReason { get; } + + /// + /// Failure reason from the IMDSv2 probe, if it failed; otherwise null. + /// + public string ImdsV2FailureReason { get; } + + public ManagedIdentityDiscoveryResult( + ManagedIdentitySource source, + ImdsVersion? detectedImdsVersion = null, + MtlsBindingStrength maxSupportedBindingStrength = MtlsBindingStrength.None, + string imdsV1FailureReason = null, + string imdsV2FailureReason = null) + { + Source = source; + DetectedImdsVersion = detectedImdsVersion; + MaxSupportedBindingStrength = maxSupportedBindingStrength; + ImdsV1FailureReason = imdsV1FailureReason; + ImdsV2FailureReason = imdsV2FailureReason; + } + + /// + /// Builds a single, combined error reason from the IMDS probe failures, or null + /// when neither probe reported a failure. + /// + public string GetCombinedErrorReason() + { + bool hasV1 = !string.IsNullOrEmpty(ImdsV1FailureReason); + bool hasV2 = !string.IsNullOrEmpty(ImdsV2FailureReason); + + if (!hasV1 && !hasV2) + { + return null; + } + + var sb = new System.Text.StringBuilder(); + if (hasV2) + { + sb.Append($"IMDSv2: {ImdsV2FailureReason}."); + } + if (hasV1) + { + if (sb.Length > 0) + { + sb.Append(' '); + } + sb.Append($"IMDSv1: {ImdsV1FailureReason}."); + } + + return sb.ToString(); + } + } +} diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySource.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySource.cs index faf3e6f16c..489556bd24 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySource.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySource.cs @@ -48,17 +48,12 @@ public enum ManagedIdentitySource /// Indicates that the source is defaulted to IMDS since no environment variables are set. /// This is used to detect the managed identity source. /// - [Obsolete("In use only to support the now obsolete GetManagedIdentitySource API. Will be removed in a future version. Use GetManagedIdentitySourceAsync instead.")] + [Obsolete("In use only to support the now obsolete GetManagedIdentitySource API. Will be removed in a future version. Use GetManagedIdentityCapabilitiesAsync instead.")] DefaultToImds, /// /// The source to acquire token for managed identity is Machine Learning Service. /// MachineLearning, - - /// - /// The source to acquire token for managed identity is IMDSV2. - /// - ImdsV2, } } diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySourceResult.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySourceResult.cs deleted file mode 100644 index 5ae7eb7a22..0000000000 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySourceResult.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Microsoft.Identity.Client.ManagedIdentity -{ - /// - /// Result of managed identity source detection, including the detected source and any failure information from IMDS probes. - /// - /// - /// This class is returned by to provide - /// detailed information about managed identity source detection, including failure reasons when IMDS probes fail. - /// This information is useful for credential chains like DefaultAzureCredential to determine whether to skip - /// managed identity authentication entirely. - /// - public class ManagedIdentitySourceResult - { - /// - /// Gets the detected managed identity source. - /// - /// - /// The that was detected on the environment. - /// Returns if no managed identity source was detected. - /// - public ManagedIdentitySource Source { get; } - - /// - /// Gets or sets the failure reason from the IMDSv1 probe, if it failed. - /// - /// - /// A string describing why the IMDSv1 probe failed, or null if the probe succeeded or was not attempted. - /// - public string ImdsV1FailureReason { get; set; } - - /// - /// Gets or sets the failure reason from the IMDSv2 probe, if it failed. - /// - /// - /// A string describing why the IMDSv2 probe failed, or null if the probe succeeded or was not attempted. - /// - public string ImdsV2FailureReason { get; set; } - - /// - /// Initializes a new instance of the class. - /// - /// The detected managed identity source. - public ManagedIdentitySourceResult(ManagedIdentitySource source) - { - Source = source; - } - } -} diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequestResponse.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequestResponse.cs index c92d342ea1..793d4871a4 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequestResponse.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequestResponse.cs @@ -41,7 +41,7 @@ public static void Validate(CertificateRequestResponse certificateRequestRespons MsalError.ManagedIdentityRequestFailed, $"[ImdsV2] ImdsV2ManagedIdentitySource.ExecuteCertificateRequestAsync failed because the certificate request response is malformed. Status code: 200", null, - ManagedIdentitySource.ImdsV2, + ManagedIdentitySource.Imds, (int)HttpStatusCode.OK); } } diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs index 166f859881..db1cd63d06 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs @@ -113,7 +113,7 @@ private static void ThrowCsrMetadataRequestException( MsalError.ManagedIdentityRequestFailed, $"[ImdsV2] {errorMessage}", ex, - ManagedIdentitySource.ImdsV2, + ManagedIdentitySource.Imds, statusCode); } @@ -177,7 +177,7 @@ internal ImdsV2ManagedIdentitySource(RequestContext requestContext) internal ImdsV2ManagedIdentitySource( RequestContext requestContext, IMtlsCertificateCache mtlsCache) - : base(requestContext, ManagedIdentitySource.ImdsV2) + : base(requestContext, ManagedIdentitySource.Imds) { _mtlsCache = mtlsCache ?? throw new ArgumentNullException(nameof(mtlsCache)); } @@ -312,7 +312,7 @@ private async Task ExecuteCertificateRequestAsync( MsalError.ManagedIdentityRequestFailed, "[ImdsV2] ImdsV2ManagedIdentitySource.ExecuteCertificateRequestAsync failed.", ex, - ManagedIdentitySource.ImdsV2, + ManagedIdentitySource.Imds, statusCode); } @@ -322,7 +322,7 @@ private async Task ExecuteCertificateRequestAsync( MsalError.ManagedIdentityRequestFailed, $"[ImdsV2] ImdsV2ManagedIdentitySource.ExecuteCertificateRequestAsync failed due to HTTP error. Status code: {response.StatusCode} Body: {response.Body}", null, - ManagedIdentitySource.ImdsV2, + ManagedIdentitySource.Imds, (int)response.StatusCode); } @@ -351,7 +351,10 @@ protected override async Task CreateRequestAsync(string { throw new MsalClientException( "mtls_pop_requires_keyguard", - $"[ImdsV2] mTLS Proof-of-Possession requires KeyGuard keys. Current key type: {keyInfo.Type}"); + $"[ImdsV2] mTLS Proof-of-Possession currently requires a KeyGuard key, but this host produced a '{keyInfo.Type}' key. " + + "The host may report Software-strength binding capability (which means it can bind a token to a key), " + + "but the IMDSv2 PoP token flow only accepts VBS-isolated KeyGuard keys today. " + + "Ensure Virtualization-based Security (VBS)/KeyGuard is enabled on the host, or request a bearer token instead."); } } diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentityApplication.cs b/src/client/Microsoft.Identity.Client/ManagedIdentityApplication.cs index 05a771759b..a5ae1c8624 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentityApplication.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentityApplication.cs @@ -55,20 +55,39 @@ public AcquireTokenForManagedIdentityParameterBuilder AcquireTokenForManagedIden resource); } - /// - public async Task GetManagedIdentitySourceAsync(CancellationToken cancellationToken) + /// + /// Detects the managed identity source available on the host and the strongest mTLS + /// binding the host can produce. Useful for credential chains (such as + /// DefaultAzureCredential) to decide whether managed identity is available and + /// what binding strength to expect. + /// + /// + /// On hosts capable of key binding, detecting the strongest available strength may provision + /// (and persist) a binding key as a side effect, pre-warming the cache reused by a subsequent + /// token request. The key provider is created once per process and its key is cached. + /// + /// A cancellation token to observe while waiting for the detection to complete. + /// A describing the detected source and host capabilities. + public async Task GetManagedIdentityCapabilitiesAsync(CancellationToken cancellationToken) { // Create a temporary RequestContext for the logger and the IMDS probe request. var requestContext = new RequestContext(this.ServiceBundle, Guid.NewGuid(), null, cancellationToken); - return await ManagedIdentityClient.GetManagedIdentitySourceAsync(requestContext, cancellationToken).ConfigureAwait(false); + ManagedIdentityDiscoveryResult discoveryResult = await ManagedIdentityClient + .GetManagedIdentityCapabilitiesAsync(requestContext, cancellationToken) + .ConfigureAwait(false); + + return new ManagedIdentityCapabilities( + discoveryResult.Source, + discoveryResult.MaxSupportedBindingStrength, + discoveryResult.GetCombinedErrorReason()); } /// /// Detects and returns the managed identity source available on the environment. /// /// Managed identity source detected on the environment if any. - [Obsolete("Use GetManagedIdentitySourceAsync() instead. \"ManagedIdentityApplication mi = miBuilder.Build() as ManagedIdentityApplication;\"")] + [Obsolete("Use GetManagedIdentityCapabilitiesAsync() instead. \"ManagedIdentityApplication mi = miBuilder.Build() as ManagedIdentityApplication;\"")] public static ManagedIdentitySource GetManagedIdentitySource() { var source = ManagedIdentityClient.GetManagedIdentitySourceNoImds(); diff --git a/src/client/Microsoft.Identity.Client/Platforms/net/MsalJsonSerializerContext.cs b/src/client/Microsoft.Identity.Client/Platforms/net/MsalJsonSerializerContext.cs index 4fc00ab332..e9b6384cc0 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/net/MsalJsonSerializerContext.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/net/MsalJsonSerializerContext.cs @@ -42,6 +42,8 @@ namespace Microsoft.Identity.Client.Platforms.net [JsonSerializable(typeof(CuidInfo))] [JsonSerializable(typeof(CertificateRequestBody))] [JsonSerializable(typeof(CertificateRequestResponse))] + [JsonSerializable(typeof(ComputeMetadataResponse))] + [JsonSerializable(typeof(ComputeSecurityProfile))] [JsonSerializable(typeof(Dictionary))] [JsonSourceGenerationOptions] internal partial class MsalJsonSerializerContext : JsonSerializerContext diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Shipped.txt index e370d1cf34..7bf56d508d 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Shipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Shipped.txt @@ -1087,7 +1087,6 @@ const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = " const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void const Microsoft.Identity.Client.MsalError.ImdsServiceError = "imds_service_error" -> string @@ -1111,14 +1110,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft. Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ManagedIdentitySourceResult(Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource source) -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource -Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder Microsoft.Identity.Client.AppConfig.CertificateOptions @@ -1151,4 +1142,4 @@ Microsoft.Identity.Client.CacheRefreshReason.CacheDisabled = 5 -> Microsoft.Iden Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions const Microsoft.Identity.Client.MsalError.InternalCacheDisabled = "internal_cache_disabled" -> string static Microsoft.Identity.Client.CacheOptions.DisableInternalCacheOptions.get -> Microsoft.Identity.Client.CacheOptions -static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index c08c06c96f..bc21638d2a 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -6,4 +6,14 @@ Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2 static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder builder, string key, string value) -> T -static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.None = 0 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.Software = 1 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.KeyGuard = 3 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.ErrorReason.get -> string +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.IsMtlsPopSupportedByHost.get -> bool +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.MaxSupportedBindingStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentityCapabilitiesAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Shipped.txt index 00156f03fa..10711bb7da 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Shipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Shipped.txt @@ -1087,7 +1087,6 @@ const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = " const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void const Microsoft.Identity.Client.MsalError.ImdsServiceError = "imds_service_error" -> string @@ -1111,14 +1110,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft. Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ManagedIdentitySourceResult(Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource source) -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource -Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder static Microsoft.Identity.Client.Extensibility.AbstractConfidentialClientAcquireTokenParameterBuilderExtension.WithExtraClientAssertionClaims(this Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder builder, string clientAssertionClaims) -> Microsoft.Identity.Client.AbstractAcquireTokenParameterBuilder @@ -1151,4 +1142,4 @@ Microsoft.Identity.Client.CacheRefreshReason.CacheDisabled = 5 -> Microsoft.Iden Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions const Microsoft.Identity.Client.MsalError.InternalCacheDisabled = "internal_cache_disabled" -> string static Microsoft.Identity.Client.CacheOptions.DisableInternalCacheOptions.get -> Microsoft.Identity.Client.CacheOptions -static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index c08c06c96f..bc21638d2a 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -6,4 +6,14 @@ Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2 static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder builder, string key, string value) -> T -static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.None = 0 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.Software = 1 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.KeyGuard = 3 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.ErrorReason.get -> string +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.IsMtlsPopSupportedByHost.get -> bool +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.MaxSupportedBindingStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentityCapabilitiesAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt index e65db40158..4c4d454304 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Shipped.txt @@ -1053,7 +1053,6 @@ const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = " const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void const Microsoft.Identity.Client.MsalError.ImdsServiceError = "imds_service_error" -> string @@ -1077,14 +1076,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft. Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ManagedIdentitySourceResult(Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource source) -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource -Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder Microsoft.Identity.Client.AppConfig.CertificateOptions @@ -1117,4 +1108,4 @@ Microsoft.Identity.Client.CacheRefreshReason.CacheDisabled = 5 -> Microsoft.Iden Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions const Microsoft.Identity.Client.MsalError.InternalCacheDisabled = "internal_cache_disabled" -> string static Microsoft.Identity.Client.CacheOptions.DisableInternalCacheOptions.get -> Microsoft.Identity.Client.CacheOptions -static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index c08c06c96f..bc21638d2a 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -6,4 +6,14 @@ Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2 static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder builder, string key, string value) -> T -static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.None = 0 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.Software = 1 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.KeyGuard = 3 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.ErrorReason.get -> string +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.IsMtlsPopSupportedByHost.get -> bool +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.MaxSupportedBindingStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentityCapabilitiesAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Shipped.txt index 2009cb3479..8859c66843 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Shipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Shipped.txt @@ -1055,7 +1055,6 @@ const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = " const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void const Microsoft.Identity.Client.MsalError.ImdsServiceError = "imds_service_error" -> string @@ -1079,14 +1078,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft. Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ManagedIdentitySourceResult(Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource source) -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource -Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder Microsoft.Identity.Client.AppConfig.CertificateOptions @@ -1119,4 +1110,4 @@ Microsoft.Identity.Client.CacheRefreshReason.CacheDisabled = 5 -> Microsoft.Iden Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions const Microsoft.Identity.Client.MsalError.InternalCacheDisabled = "internal_cache_disabled" -> string static Microsoft.Identity.Client.CacheOptions.DisableInternalCacheOptions.get -> Microsoft.Identity.Client.CacheOptions -static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index c08c06c96f..bc21638d2a 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -6,4 +6,14 @@ Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2 static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder builder, string key, string value) -> T -static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.None = 0 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.Software = 1 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.KeyGuard = 3 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.ErrorReason.get -> string +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.IsMtlsPopSupportedByHost.get -> bool +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.MaxSupportedBindingStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentityCapabilitiesAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Shipped.txt index 6a0d655679..a88846f0f7 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Shipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Shipped.txt @@ -1049,7 +1049,6 @@ const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = " const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void const Microsoft.Identity.Client.MsalError.ImdsServiceError = "imds_service_error" -> string @@ -1073,14 +1072,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft. Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ManagedIdentitySourceResult(Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource source) -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource -Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder Microsoft.Identity.Client.AppConfig.CertificateOptions @@ -1113,4 +1104,4 @@ Microsoft.Identity.Client.CacheRefreshReason.CacheDisabled = 5 -> Microsoft.Iden Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions const Microsoft.Identity.Client.MsalError.InternalCacheDisabled = "internal_cache_disabled" -> string static Microsoft.Identity.Client.CacheOptions.DisableInternalCacheOptions.get -> Microsoft.Identity.Client.CacheOptions -static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index c08c06c96f..bc21638d2a 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -6,4 +6,14 @@ Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2 static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder builder, string key, string value) -> T -static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.None = 0 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.Software = 1 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.KeyGuard = 3 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.ErrorReason.get -> string +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.IsMtlsPopSupportedByHost.get -> bool +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.MaxSupportedBindingStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentityCapabilitiesAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Shipped.txt index 64058c8fc0..067f7cfdb4 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -1049,7 +1049,6 @@ const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = " const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void const Microsoft.Identity.Client.MsalError.ImdsServiceError = "imds_service_error" -> string @@ -1073,14 +1072,6 @@ Microsoft.Identity.Client.Extensibility.ExecutionResult.Result.get -> Microsoft. Microsoft.Identity.Client.Extensibility.ExecutionResult.Successful.get -> bool static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnCompletion(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func onCompletion) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder static Microsoft.Identity.Client.Extensibility.ConfidentialClientApplicationBuilderExtensions.OnMsalServiceFailure(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Func> onMsalServiceFailure) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ManagedIdentitySourceResult(Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource source) -> void -Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource -Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder.WithSendX5C(bool withSendX5C) -> Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder Microsoft.Identity.Client.AppConfig.CertificateOptions @@ -1113,4 +1104,4 @@ Microsoft.Identity.Client.CacheRefreshReason.CacheDisabled = 5 -> Microsoft.Iden Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions const Microsoft.Identity.Client.MsalError.InternalCacheDisabled = "internal_cache_disabled" -> string static Microsoft.Identity.Client.CacheOptions.DisableInternalCacheOptions.get -> Microsoft.Identity.Client.CacheOptions -static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AuthenticationResultExtensions.GetRefreshToken(this Microsoft.Identity.Client.AuthenticationResult result) -> string diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index c08c06c96f..bc21638d2a 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -6,4 +6,14 @@ Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.CredentialEvaluationContext(System.Security.Cryptography.X509Certificates.X509Certificate2 mtlsCertificate) -> void Microsoft.Identity.Client.AuthScheme.CredentialEvaluationContext.MtlsCertificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2 static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithCachePartitionKey(this Microsoft.Identity.Client.BaseAbstractAcquireTokenParameterBuilder builder, string key, string value) -> T -static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder \ No newline at end of file +static Microsoft.Identity.Client.Extensibility.AcquireTokenParameterBuilderExtensions.WithReservedScopes(this Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder builder, bool offlineAccessScope) -> Microsoft.Identity.Client.AcquireTokenByAuthorizationCodeParameterBuilder +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.None = 0 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.Software = 1 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.AppConfig.MtlsBindingStrength.KeyGuard = 3 -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.ErrorReason.get -> string +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.IsMtlsPopSupportedByHost.get -> bool +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.MaxSupportedBindingStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength +Microsoft.Identity.Client.ManagedIdentity.ManagedIdentityCapabilities.Source.get -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource +Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentityCapabilitiesAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task diff --git a/src/client/Microsoft.Identity.Lab.Api/Helpers/ManagedIdentityTestUtil.cs b/src/client/Microsoft.Identity.Lab.Api/Helpers/ManagedIdentityTestUtil.cs index 5f151d294c..3132ffb0ae 100644 --- a/src/client/Microsoft.Identity.Lab.Api/Helpers/ManagedIdentityTestUtil.cs +++ b/src/client/Microsoft.Identity.Lab.Api/Helpers/ManagedIdentityTestUtil.cs @@ -63,7 +63,6 @@ public static void SetEnvironmentVariables(ManagedIdentitySource managedIdentity break; case ManagedIdentitySource.Imds: - case ManagedIdentitySource.ImdsV2: Environment.SetEnvironmentVariable("AZURE_POD_IDENTITY_AUTHORITY_HOST", endpoint); break; diff --git a/src/client/Microsoft.Identity.Lab.Api/Http/MockHelpers.cs b/src/client/Microsoft.Identity.Lab.Api/Http/MockHelpers.cs index ecc4643f96..0deddf4bf9 100644 --- a/src/client/Microsoft.Identity.Lab.Api/Http/MockHelpers.cs +++ b/src/client/Microsoft.Identity.Lab.Api/Http/MockHelpers.cs @@ -948,6 +948,50 @@ internal static MockHttpMessageHandler MockImdsProbeFailure( return MockImdsProbe(imdsVersion, userAssignedIdentityId, userAssignedId, success: false, retry: retry); } + /// + /// Creates a mock IMDS compute metadata (/metadata/instance/compute) response handler. + /// + /// The OS type to return (e.g., "Windows", "Linux"). + /// The security profile type (e.g., "TrustedLaunch", "ConfidentialVM"), or null for no profile. + /// A configured . + internal static MockHttpMessageHandler MockImdsComputeMetadata( + string osType = "Windows", + string securityType = "TrustedLaunch") + { + string securityProfileJson = securityType != null + ? $", \"securityProfile\": {{ \"securityType\": \"{securityType}\" }}" + : ""; + + string body = $"{{ \"osType\": \"{osType}\"{securityProfileJson} }}"; + + return new MockHttpMessageHandler() + { + ExpectedUrl = $"{ImdsManagedIdentitySource.DefaultImdsBaseEndpoint}{ImdsComputeMetadataManager.ImdsComputePath}", + ExpectedMethod = HttpMethod.Get, + ResponseMessage = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(body), + } + }; + } + + /// + /// Creates a mock IMDS compute metadata 404 (not found) response handler. + /// + /// A configured . + internal static MockHttpMessageHandler MockImdsComputeMetadataNotFound() + { + return new MockHttpMessageHandler() + { + ExpectedUrl = $"{ImdsManagedIdentitySource.DefaultImdsBaseEndpoint}{ImdsComputeMetadataManager.ImdsComputePath}", + ExpectedMethod = HttpMethod.Get, + ResponseMessage = new HttpResponseMessage(HttpStatusCode.NotFound) + { + Content = new StringContent(""), + } + }; + } + /// /// Creates a mock CSR metadata response handler for IMDS v2 flows. /// diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AppServiceTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AppServiceTests.cs index ac75ca0e92..b233dfd0c1 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AppServiceTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/AppServiceTests.cs @@ -70,7 +70,7 @@ public async Task TestAppServiceUpgradeScenario( ManagedIdentityApplication mi = miBuilder.Build() as ManagedIdentityApplication; - var miSourceResult = await mi.GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); + var miSourceResult = await mi.GetManagedIdentityCapabilitiesAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); Assert.AreEqual(expectedManagedIdentitySource, miSourceResult.Source); } } diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs index 7f48193be8..ea586bc078 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs @@ -132,7 +132,9 @@ private async Task CreateManagedIdentityAsync( if (addSourceCheck) { - var miSourceResultV1 = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); + // V1 success triggers a compute-metadata call to determine host binding strength. + httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata()); + var miSourceResultV1 = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentityCapabilitiesAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); Assert.AreEqual(ManagedIdentitySource.Imds, miSourceResultV1.Source); } @@ -145,13 +147,9 @@ private async Task CreateManagedIdentityAsync( httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V2, userAssignedIdentityId, userAssignedId)); } - if (addSourceCheck) - { - var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); - Assert.AreEqual(ManagedIdentitySource.ImdsV2, miSourceResult.Source); - } - - // Choose deterministic key source for tests. + // Choose deterministic key source for tests. This must be injected BEFORE discovery runs, + // because IMDSv2 capability discovery probes the key provider to determine whether the host + // can produce a KeyGuard key (and therefore advertise the KeyGuard binding-strength tier). IManagedIdentityKeyProvider managedIdentityKeyProvider = keyProvider; if (managedIdentityKeyProvider == null) { @@ -177,6 +175,12 @@ private async Task CreateManagedIdentityAsync( .ServiceBundle.SetPlatformProxyForTest(platformProxy); } + if (addSourceCheck) + { + var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentityCapabilitiesAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); + Assert.AreEqual(ManagedIdentitySource.Imds, miSourceResult.Source); + } + return managedIdentityApp; } @@ -193,7 +197,7 @@ public async Task mTLSPopTokenHappyPath( using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, userAssignedIdentityId, userAssignedId, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -235,7 +239,7 @@ public async Task mTLSPopTokenIsPerIdentity( using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); #region Identity 1 var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, userAssignedIdentityId, userAssignedId, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -316,7 +320,7 @@ public async Task mTLSPopTokenIsReAcquiredWhenCertificateIsExpired( using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, userAssignedIdentityId, userAssignedId, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -448,7 +452,7 @@ public async Task ApplicationsCanSwitchBetweenImdsVersions( using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, userAssignedIdentityId, userAssignedId, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -470,8 +474,8 @@ public async Task ApplicationsCanSwitchBetweenImdsVersions( Assert.AreEqual(Bearer, bearerResult.TokenType); Assert.AreEqual(TokenSource.IdentityProvider, bearerResult.AuthenticationResultMetadata.TokenSource); - var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); - Assert.AreEqual(ManagedIdentitySource.ImdsV2, miSourceResult.Source); + var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentityCapabilitiesAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); + Assert.AreEqual(ManagedIdentitySource.Imds, miSourceResult.Source); // Arrange: mocks for the IMDSv2 mTLS PoP token request AddMocksToGetEntraToken(httpManager, userAssignedIdentityId, userAssignedId); @@ -498,7 +502,7 @@ public async Task ProbeImdsEndpointAsyncSucceeds() using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Discovery order: V2 probed first (succeeds) httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V2)); @@ -513,7 +517,7 @@ public async Task ProbeImdsEndpointAsyncSucceedsAfterRetry() using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Discovery order: V2 probed first (first attempt fails with retry, second succeeds) // `retry: true` indicates a retriable status code will be returned @@ -531,7 +535,7 @@ public async Task ProbeImdsEndpointAsyncFails404WhichIsNonRetriableAndRetryPolic using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Discovery order: V2 probed first (fails with non-retriable 404), then V1 (succeeds) // `retry: false` indicates a non-retriable status code (404) will be returned for V2 @@ -540,7 +544,10 @@ public async Task ProbeImdsEndpointAsyncFails404WhichIsNonRetriableAndRetryPolic var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, addProbeMock: false, addSourceCheck: false).ConfigureAwait(false); - var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); + // V1 success triggers a compute-metadata call to determine host binding strength. + httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata()); + + var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentityCapabilitiesAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); Assert.AreEqual(ManagedIdentitySource.Imds, miSourceResult.Source); } } @@ -567,7 +574,7 @@ public async Task ImdsProbeEndpointAsync_TimeOutThrowsOperationCanceledException var imdsProbesCancellationToken = cts.Token; await Assert.ThrowsAsync(async () => - await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentitySourceAsync(imdsProbesCancellationToken) + await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentityCapabilitiesAsync(imdsProbesCancellationToken) .ConfigureAwait(false)) .ConfigureAwait(false); } @@ -587,7 +594,7 @@ public async Task NonMtlsRequest_FallsBackToImdsV1( using (var httpManager = new MockHttpManager()) { ManagedIdentityClient.ResetSourceForTest(); - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, userAssignedIdentityId, userAssignedId, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -609,8 +616,8 @@ public async Task NonMtlsRequest_FallsBackToImdsV1( Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource); // indicates ImdsV2 is still available - var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); - Assert.AreEqual(ManagedIdentitySource.ImdsV2, miSourceResult.Source); + var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentityCapabilitiesAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); + Assert.AreEqual(ManagedIdentitySource.Imds, miSourceResult.Source); } } @@ -620,7 +627,7 @@ public async Task BothImdsProbesFailMaxRetries_ReturnsNoneFound() using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Discovery order: V2 probed first (fails with max retries), then V1 (fails) → NoneFound const int Num500Errors = 1 + TestImdsProbeRetryPolicy.ExponentialStrategyNumRetries; @@ -634,7 +641,7 @@ public async Task BothImdsProbesFailMaxRetries_ReturnsNoneFound() var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, addProbeMock: false, addSourceCheck: false).ConfigureAwait(false); - var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); + var miSourceResult = await (managedIdentityApp as ManagedIdentityApplication).GetManagedIdentityCapabilitiesAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false); Assert.AreEqual(ManagedIdentitySource.None, miSourceResult.Source); } } @@ -647,7 +654,7 @@ public async Task GetCsrMetadataAsyncFailsWithMissingServerHeader() using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var managedIdentityApp = await CreateManagedIdentityAsync(httpManager).ConfigureAwait(false); @@ -669,7 +676,7 @@ public async Task GetCsrMetadataAsyncFailsWithInvalidFormat() using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var managedIdentityApp = await CreateManagedIdentityAsync(httpManager).ConfigureAwait(false); @@ -764,7 +771,7 @@ public async Task MtlsPop_NoAttestationProvider_UsesNonAttestedFlow() using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var mi = await CreateManagedIdentityAsync(httpManager, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -790,7 +797,7 @@ public async Task MtlsPop_AttestationProviderReturnsNull_UsesNonAttestedFlow() using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var mi = await CreateManagedIdentityAsync(httpManager, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -815,7 +822,7 @@ public async Task MtlsPop_AttestationProviderReturnsEmptyToken_UsesNonAttestedFl using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var mi = await CreateManagedIdentityAsync(httpManager, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -840,7 +847,7 @@ public async Task mTLSPop_RequestedWithoutKeyGuard_ThrowsClientException() using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Force in-memory keys (i.e., not KeyGuard) var managedIdentityApp = await CreateManagedIdentityAsync( @@ -872,7 +879,7 @@ public async Task mTLSPop_AttestationSupport_IsRequired_ToReusePersistedCert() using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Force KeyGuard provider so attestation support is relevant. var mi = await CreateManagedIdentityAsync( @@ -923,7 +930,7 @@ public async Task mTLSPop_AttestationSupport_AffectsTokenCacheKey_NonAttestedReq using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Force KeyGuard provider so attestation support is relevant. var mi = await CreateManagedIdentityAsync( @@ -983,7 +990,7 @@ public async Task MaaTokenCache_Hit_DoesNotCallAttestationProviderAgain() using (var httpManager = new MockHttpManager()) { ManagedIdentityClient.ResetSourceForTest(); - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var rawCert = CreateRawCertForCsrKeyWithCnDc( Constants.ManagedIdentityDefaultClientId, TestConstants.TenantId, @@ -1054,7 +1061,7 @@ public async Task MaaTokenCache_ExpiredToken_CallsAttestationProviderAgain() using (var httpManager = new MockHttpManager()) { ManagedIdentityClient.ResetSourceForTest(); - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var rawCert = CreateRawCertForCsrKeyWithCnDc( Constants.ManagedIdentityDefaultClientId, TestConstants.TenantId, @@ -1247,7 +1254,7 @@ public async Task MaaTokenCache_CertCacheMiss_MaaTokenCacheStillFresh_DoesNotCal using (var httpManager = new MockHttpManager()) { ManagedIdentityClient.ResetSourceForTest(); - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var rawCert = CreateRawCertForCsrKeyWithCnDc( Constants.ManagedIdentityDefaultClientId, TestConstants.TenantId, @@ -1298,7 +1305,7 @@ public async Task mTLSPop_ForceRefresh_UsesCachedCert_NoIssueCredential_PostsCan using (var httpManager = new MockHttpManager()) { // Start clean across tests - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); var mi = await CreateManagedIdentityAsync(httpManager, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -1352,7 +1359,7 @@ public async Task mTLSPop_CachedCertIsPerIdentity_OnRefresh_Identity1UsesCache_I using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Identity 1 – first acquire (mint) var mi1 = await CreateManagedIdentityAsync(httpManager, userAssignedIdentityId, userAssignedId1, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false); @@ -1412,7 +1419,7 @@ public async Task mTLSPopTokenHappyPath_LongLivedCert_IdentityMapping( using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Force KeyGuard so the PoP path is taken var managedIdentityApp = await CreateManagedIdentityAsync( @@ -1480,7 +1487,7 @@ public async Task mTLSPop_LongLivedCerts_SamiVsUami_DistinctAndCached( using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Create the two test certs (20-year) from the SAME RSA as CSR (XmlPrivateKey) string rawCertSami = CreateRawCertFromXml("CN=SAMI-20Y", notAfterUtc: DateTimeOffset.UtcNow.AddYears(20)); @@ -1545,7 +1552,7 @@ public async Task mTLSPop_LongLivedCerts_SamiAndUami_ThumbprintsDiffer_AndEachCa using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Make two long-lived certs **from the CSR key** so AttachPrivateKey succeeds string rawCertSami = CreateRawCertForCsrKey("CN=SAMI-20Y", DateTimeOffset.UtcNow.AddYears(20)); @@ -1643,7 +1650,7 @@ public async Task mTLSPop_SubjectCnDc_MatchesMetadata_AndCaches( using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // Expected mapping (mirrors your live logs) string expectedCn = isUami ? TestConstants.ClientId : Constants.ManagedIdentityDefaultClientId; @@ -1680,7 +1687,7 @@ public async Task mTLSPoP_Uami_ClientIdThenObjectId_MintsThenCaches_SubjectCNIsC using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); string expectedCn = TestConstants.ClientId; string expectedDc = TestConstants.TenantId; @@ -1756,7 +1763,7 @@ public async Task mTLSPoP_Uami_ClientIdThenAlias_MintsThenCaches_SubjectCNIsClie using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); string expectedCn = TestConstants.ClientId; string expectedDc = TestConstants.TenantId; @@ -1834,7 +1841,7 @@ public async Task mTLSPop_ShortLivedCert_LessThan24h_NotCached_ReMints( using (new EnvVariableContext()) using (var httpManager = new MockHttpManager()) { - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // short-lived cert #1: < 24h => must NOT be cached var rawShort1 = CreateRawCertForCsrKeyWithCnDc( @@ -1889,7 +1896,7 @@ public async Task mTLSPop_CertAtLeast24h_IsCached_ReusedOnSecondAcquire( using (var httpManager = new MockHttpManager()) { ManagedIdentityClient.ResetSourceForTest(); - SetEnvironmentVariables(ManagedIdentitySource.ImdsV2, TestConstants.ImdsEndpoint); + SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint); // NotAfter >= 24h + 1min ? should be cached and reused var rawLong = CreateRawCertForCsrKeyWithCnDc( diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs index 9d85275562..cabe371ad7 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs @@ -13,13 +13,16 @@ using Microsoft.Identity.Client.AppConfig; using Microsoft.Identity.Client.Internal; using Microsoft.Identity.Client.ManagedIdentity; +using Microsoft.Identity.Client.ManagedIdentity.KeyProviders; using Microsoft.Identity.Client.ManagedIdentity.V2; +using Microsoft.Identity.Client.PlatformsCommon.Interfaces; using Microsoft.Identity.Client.TelemetryCore.Internal.Events; using Microsoft.Identity.Test.Common; using Microsoft.Identity.Test.Common.Core.Helpers; using Microsoft.Identity.Test.Common.Core.Mocks; using Microsoft.Identity.Test.Unit.Helpers; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; using static Microsoft.Identity.Test.Common.Core.Helpers.ManagedIdentityTestUtil; namespace Microsoft.Identity.Test.Unit.ManagedIdentityTests @@ -44,13 +47,21 @@ public class ManagedIdentityTests : TestBase private readonly TestRetryPolicyFactory _testRetryPolicyFactory = new TestRetryPolicyFactory(); + // Injects a test platform proxy that returns the supplied key provider, so that IMDSv2 + // capability discovery deterministically resolves the host's binding strength (KeyGuard vs + // software) instead of probing the real platform key provider. + private static void InjectKeyProvider(ManagedIdentityApplication mi, IManagedIdentityKeyProvider keyProvider) + { + var platformProxy = Substitute.For(); + platformProxy.ManagedIdentityKeyProvider.Returns(keyProvider); + mi.ServiceBundle.SetPlatformProxyForTest(platformProxy); + } + [TestMethod] [DataRow("http://127.0.0.1:41564/msi/token/", ManagedIdentitySource.AppService)] [DataRow(AppServiceEndpoint, ManagedIdentitySource.AppService)] [DataRow(ImdsEndpoint, ManagedIdentitySource.Imds)] [DataRow(null, ManagedIdentitySource.Imds)] - [DataRow(ImdsEndpoint, ManagedIdentitySource.ImdsV2)] - [DataRow(null, ManagedIdentitySource.ImdsV2)] [DataRow(AzureArcEndpoint, ManagedIdentitySource.AzureArc)] [DataRow(CloudShellEndpoint, ManagedIdentitySource.CloudShell)] [DataRow(ServiceFabricEndpoint, ManagedIdentitySource.ServiceFabric)] @@ -69,20 +80,94 @@ public async Task GetManagedIdentityTests( ManagedIdentityApplication mi = miBuilder.Build() as ManagedIdentityApplication; - if (managedIdentitySource == ManagedIdentitySource.ImdsV2) - { - // Discovery order: V2 probed first (succeeds) - httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V2)); - } - else if (managedIdentitySource == ManagedIdentitySource.Imds) + if (managedIdentitySource == ManagedIdentitySource.Imds) { - // Discovery order: V2 probed first (fails), then V1 (succeeds) + // Discovery order: V2 probed first (fails), then V1 (succeeds), then compute metadata for capability. httpManager.AddMockHandler(MockHelpers.MockImdsProbeFailure(ImdsVersion.V2)); httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1)); + httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata()); } - var miSourceResult = await mi.GetManagedIdentitySourceAsync(ImdsProbesCancellationToken).ConfigureAwait(false); - Assert.AreEqual(managedIdentitySource, miSourceResult.Source); + var caps = await mi.GetManagedIdentityCapabilitiesAsync(ImdsProbesCancellationToken).ConfigureAwait(false); + Assert.AreEqual(managedIdentitySource, caps.Source); + } + } + + [TestMethod] + public async Task GetManagedIdentityCapabilities_ImdsV2Detected_ReportsSoftwarePoPAsync() + { + using (new EnvVariableContext()) + using (var httpManager = new MockHttpManager()) + { + SetEnvironmentVariables(ManagedIdentitySource.Imds, ImdsEndpoint); + + var mi = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned) + .WithHttpManager(httpManager) + .Build() as ManagedIdentityApplication; + + // A software-backed key provider (no VBS/KeyGuard) caps the host at the Software tier. + InjectKeyProvider(mi, new InMemoryManagedIdentityKeyProvider()); + + // Discovery order: V2 probed first and succeeds, signalling host PoP capability. + httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V2)); + + var caps = await mi.GetManagedIdentityCapabilitiesAsync(ImdsProbesCancellationToken).ConfigureAwait(false); + + Assert.AreEqual(ManagedIdentitySource.Imds, caps.Source); + Assert.AreEqual(MtlsBindingStrength.Software, caps.MaxSupportedBindingStrength); + Assert.IsTrue(caps.IsMtlsPopSupportedByHost); + } + } + + [TestMethod] + public async Task GetManagedIdentityCapabilities_ImdsV2KeyGuard_ReportsKeyGuardAsync() + { + using (new EnvVariableContext()) + using (var httpManager = new MockHttpManager()) + { + SetEnvironmentVariables(ManagedIdentitySource.Imds, ImdsEndpoint); + + var mi = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned) + .WithHttpManager(httpManager) + .Build() as ManagedIdentityApplication; + + // A KeyGuard-capable key provider (VBS-isolated key) upgrades the host to the + // attested KeyGuard tier. + InjectKeyProvider(mi, new TestKeyGuardManagedIdentityKeyProvider()); + + // Discovery order: V2 probed first and succeeds, signalling host PoP capability. + httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V2)); + + var caps = await mi.GetManagedIdentityCapabilitiesAsync(ImdsProbesCancellationToken).ConfigureAwait(false); + + Assert.AreEqual(ManagedIdentitySource.Imds, caps.Source); + Assert.AreEqual(MtlsBindingStrength.KeyGuard, caps.MaxSupportedBindingStrength); + Assert.IsTrue(caps.IsMtlsPopSupportedByHost); + } + } + + [TestMethod] + public async Task GetManagedIdentityCapabilities_ImdsV1NonTvm_ReportsBearerAsync() + { + using (new EnvVariableContext()) + using (var httpManager = new MockHttpManager()) + { + SetEnvironmentVariables(ManagedIdentitySource.Imds, ImdsEndpoint); + + var mi = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned) + .WithHttpManager(httpManager) + .Build() as ManagedIdentityApplication; + + // V2 fails, V1 succeeds, compute metadata reports a non-TVM/CVM host (no security profile). + httpManager.AddMockHandler(MockHelpers.MockImdsProbeFailure(ImdsVersion.V2)); + httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1)); + httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata(osType: "Linux", securityType: null)); + + var caps = await mi.GetManagedIdentityCapabilitiesAsync(ImdsProbesCancellationToken).ConfigureAwait(false); + + Assert.AreEqual(ManagedIdentitySource.Imds, caps.Source); + Assert.AreEqual(MtlsBindingStrength.None, caps.MaxSupportedBindingStrength); + Assert.IsFalse(caps.IsMtlsPopSupportedByHost); } } @@ -1132,7 +1217,7 @@ public async Task UnavailableManagedIdentitySource_ThrowsExceptionDuringTokenAcq httpManager.AddMockHandler(MockHelpers.MockImdsProbeFailure(ImdsVersion.V2)); httpManager.AddMockHandler(MockHelpers.MockImdsProbeFailure(ImdsVersion.V1)); - var sourceResult = await mi.GetManagedIdentitySourceAsync(ImdsProbesCancellationToken).ConfigureAwait(false); + var sourceResult = await mi.GetManagedIdentityCapabilitiesAsync(ImdsProbesCancellationToken).ConfigureAwait(false); Assert.AreEqual(ManagedIdentitySource.None, sourceResult.Source); // Token acquisition uses cached NoneFound and throws AllSourcesUnavailable @@ -1370,7 +1455,6 @@ public async Task ManagedIdentityWithCapabilitiesTestAsync( [DataRow(ManagedIdentitySource.AzureArc)] [DataRow(ManagedIdentitySource.CloudShell)] [DataRow(ManagedIdentitySource.Imds)] - [DataRow(ManagedIdentitySource.ImdsV2)] [DataRow(ManagedIdentitySource.ServiceFabric)] [DataRow(ManagedIdentitySource.MachineLearning)] public void ValidateServerCertificate_OnlySetForServiceFabric(ManagedIdentitySource managedIdentitySource) @@ -1407,6 +1491,26 @@ public void ValidateServerCertificate_OnlySetForServiceFabric(ManagedIdentitySou } } + [TestMethod] + public void ValidateServerCertificate_NotSetForImdsV2() + { + using (new EnvVariableContext()) + using (var httpManager = new MockHttpManager()) + { + SetEnvironmentVariables(ManagedIdentitySource.Imds, "https://identity.endpoint.com"); + + var managedIdentityApp = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned) + .WithHttpManager(httpManager) + .BuildConcrete(); + RequestContext requestContext = new RequestContext(managedIdentityApp.ServiceBundle, Guid.NewGuid(), null); + + AbstractManagedIdentity managedIdentity = new ImdsV2ManagedIdentitySource(requestContext); + + Assert.IsNull(managedIdentity.GetValidationCallback(), + "IMDSv2 source should not have ValidateServerCertificate set"); + } + } + private AbstractManagedIdentity CreateManagedIdentitySource(ManagedIdentitySource sourceType, MockHttpManager httpManager) { string endpoint = "https://identity.endpoint.com"; @@ -1437,9 +1541,6 @@ private AbstractManagedIdentity CreateManagedIdentitySource(ManagedIdentitySourc case ManagedIdentitySource.Imds: managedIdentity = new ImdsManagedIdentitySource(requestContext); break; - case ManagedIdentitySource.ImdsV2: - managedIdentity = new ImdsV2ManagedIdentitySource(requestContext); - break; case ManagedIdentitySource.MachineLearning: managedIdentity = MachineLearningManagedIdentitySource.Create(requestContext); break;