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..b45d1e23ca
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ImdsComputeMetadataManager.cs
@@ -0,0 +1,99 @@
+// 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 (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ 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/ManagedIdentityClient.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs
index ede1bc50cc..0deb2d5e5b 100644
--- a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs
+++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityClient.cs
@@ -156,7 +156,21 @@ internal async Task GetManagedIdentitySourceAsync(
if (imdsV1Success)
{
requestContext.Logger.Info("[Managed Identity] IMDS detected via v1 probe.");
- return CacheDiscoveryResult(new ManagedIdentitySourceResult(ManagedIdentitySource.Imds));
+
+ var result = new ManagedIdentitySourceResult(ManagedIdentitySource.Imds);
+
+#if !NET462
+ // Fetch compute metadata to determine mTLS PoP support (not available on .NET Framework 4.6.2)
+ var computeMetadata = await ImdsComputeMetadataManager.GetComputeMetadataAsync(
+ requestContext.ServiceBundle.HttpManager,
+ requestContext.Logger,
+ cancellationToken).ConfigureAwait(false);
+
+ result.IsMtlsPopSupportedByHost = ImdsComputeMetadataManager.IsMtlsPopSupported(computeMetadata);
+ requestContext.Logger.Info($"[Managed Identity] mTLS PoP supported by host: {result.IsMtlsPopSupportedByHost}");
+#endif
+
+ return CacheDiscoveryResult(result);
}
requestContext.Logger.Info($"[Managed Identity] {MsalErrorMessage.ManagedIdentityAllSourcesUnavailable}");
diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySourceResult.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySourceResult.cs
index a7f1da2872..0c13f1c9f2 100644
--- a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySourceResult.cs
+++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentitySourceResult.cs
@@ -31,6 +31,17 @@ public class ManagedIdentitySourceResult
///
public string ImdsFailureReason { get; set; }
+ ///
+ /// Gets a value indicating whether the host VM supports mTLS Proof-of-Possession (PoP) tokens.
+ ///
+ ///
+ /// true if the VM runs Windows and has a security profile indicating a Trusted VM (TVM)
+ /// or Confidential VM (CVM); false if the compute metadata could not be retrieved,
+ /// the VM is not Windows, or the VM does not have a TVM/CVM security profile.
+ /// This property is only meaningful when is .
+ ///
+ public bool IsMtlsPopSupportedByHost { get; internal set; }
+
///
/// Initializes a new instance of the class.
///
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.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
index c9221575fb..3cc3d0b463 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
@@ -11,3 +11,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsFailur
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void
+Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.IsMtlsPopSupportedByHost.get -> bool
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 c9221575fb..3cc3d0b463 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
@@ -11,3 +11,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsFailur
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void
+Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.IsMtlsPopSupportedByHost.get -> bool
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 c9221575fb..3cc3d0b463 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
@@ -11,3 +11,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsFailur
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void
+Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.IsMtlsPopSupportedByHost.get -> bool
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 c9221575fb..3cc3d0b463 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
@@ -11,3 +11,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsFailur
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void
+Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.IsMtlsPopSupportedByHost.get -> bool
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 c9221575fb..3cc3d0b463 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
@@ -11,3 +11,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsFailur
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void
+Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.IsMtlsPopSupportedByHost.get -> bool
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 c9221575fb..3cc3d0b463 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
@@ -11,3 +11,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsFailur
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV1FailureReason.set -> void
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.get -> string
*REMOVED*Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.ImdsV2FailureReason.set -> void
+Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySourceResult.IsMtlsPopSupportedByHost.get -> bool
diff --git a/src/client/Microsoft.Identity.Lab.Api/Http/MockHelpers.cs b/src/client/Microsoft.Identity.Lab.Api/Http/MockHelpers.cs
index ecc4643f96..59d55f3eae 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 response handler.
+ ///
+ /// The OS type to return (e.g., "Windows", "Linux").
+ /// The security profile type (e.g., "TrustedLaunch", "ConfidentialVM"), or null.
+ /// 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/ImdsV2Tests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs
index 71686fe209..5b06ae3f1a 100644
--- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs
@@ -128,6 +128,7 @@ private async Task CreateManagedIdentityAsync(
{
// Discovery probes V1 (succeeds) → Imds cached
httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1, userAssignedIdentityId, userAssignedId));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata());
if (addSourceCheck)
{
@@ -142,6 +143,7 @@ private async Task CreateManagedIdentityAsync(
{
// Discovery probes V1 (succeeds) → Imds cached; mTLS PoP requests are routed to IMDSv2 automatically
httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1, userAssignedIdentityId, userAssignedId));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata());
}
if (addSourceCheck)
@@ -377,10 +379,12 @@ public async Task ImdsV1Cached_MtlsPopRequested_RoutesToImdsV2(
// Regression test for issue #6024:
// Azure SDK calls GetManagedIdentitySourceAsync first, which probes IMDSv1 and caches "Imds".
httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1, userAssignedIdentityId, userAssignedId));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata());
var sourceResult = await (managedIdentityApp as ManagedIdentityApplication)
.GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken)
.ConfigureAwait(false);
Assert.AreEqual(ManagedIdentitySource.Imds, sourceResult.Source);
+ Assert.IsTrue(sourceResult.IsMtlsPopSupportedByHost);
// Now an mTLS PoP request should route to IMDSv2 despite v1 being cached.
AddMocksToGetEntraToken(httpManager, userAssignedIdentityId, userAssignedId);
@@ -461,8 +465,9 @@ public async Task ProbeImdsEndpointAsyncSucceeds()
{
SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint);
- // Discovery probes V1 (succeeds)
+ // Discovery probes V1 (succeeds), then fetches compute metadata
httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata());
await CreateManagedIdentityAsync(httpManager, addProbeMock: false).ConfigureAwait(false);
}
@@ -515,6 +520,113 @@ await Assert.ThrowsAsync(async () =>
}
#endregion Probe Tests
+ #region IsMtlsPopSupportedByHost Tests
+ [TestMethod]
+ public async Task IsMtlsPopSupportedByHost_WindowsTvm_ReturnsTrue()
+ {
+ using (new EnvVariableContext())
+ using (var httpManager = new MockHttpManager())
+ {
+ SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint);
+
+ httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata(osType: "Windows", securityType: "TrustedLaunch"));
+
+ var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, addProbeMock: false, addSourceCheck: false).ConfigureAwait(false);
+
+ var result = await (managedIdentityApp as ManagedIdentityApplication)
+ .GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false);
+
+ Assert.AreEqual(ManagedIdentitySource.Imds, result.Source);
+ Assert.IsTrue(result.IsMtlsPopSupportedByHost);
+ }
+ }
+
+ [TestMethod]
+ public async Task IsMtlsPopSupportedByHost_WindowsCvm_ReturnsTrue()
+ {
+ using (new EnvVariableContext())
+ using (var httpManager = new MockHttpManager())
+ {
+ SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint);
+
+ httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata(osType: "Windows", securityType: "ConfidentialVM"));
+
+ var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, addProbeMock: false, addSourceCheck: false).ConfigureAwait(false);
+
+ var result = await (managedIdentityApp as ManagedIdentityApplication)
+ .GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false);
+
+ Assert.AreEqual(ManagedIdentitySource.Imds, result.Source);
+ Assert.IsTrue(result.IsMtlsPopSupportedByHost);
+ }
+ }
+
+ [TestMethod]
+ public async Task IsMtlsPopSupportedByHost_Linux_ReturnsFalse()
+ {
+ using (new EnvVariableContext())
+ using (var httpManager = new MockHttpManager())
+ {
+ SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint);
+
+ httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata(osType: "Linux", securityType: "TrustedLaunch"));
+
+ var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, addProbeMock: false, addSourceCheck: false).ConfigureAwait(false);
+
+ var result = await (managedIdentityApp as ManagedIdentityApplication)
+ .GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false);
+
+ Assert.AreEqual(ManagedIdentitySource.Imds, result.Source);
+ Assert.IsFalse(result.IsMtlsPopSupportedByHost);
+ }
+ }
+
+ [TestMethod]
+ public async Task IsMtlsPopSupportedByHost_WindowsNoSecurityProfile_ReturnsFalse()
+ {
+ using (new EnvVariableContext())
+ using (var httpManager = new MockHttpManager())
+ {
+ SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint);
+
+ httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata(osType: "Windows", securityType: null));
+
+ var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, addProbeMock: false, addSourceCheck: false).ConfigureAwait(false);
+
+ var result = await (managedIdentityApp as ManagedIdentityApplication)
+ .GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false);
+
+ Assert.AreEqual(ManagedIdentitySource.Imds, result.Source);
+ Assert.IsFalse(result.IsMtlsPopSupportedByHost);
+ }
+ }
+
+ [TestMethod]
+ public async Task IsMtlsPopSupportedByHost_ComputeMetadata404_ReturnsFalse()
+ {
+ using (new EnvVariableContext())
+ using (var httpManager = new MockHttpManager())
+ {
+ SetEnvironmentVariables(ManagedIdentitySource.Imds, TestConstants.ImdsEndpoint);
+
+ httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadataNotFound());
+
+ var managedIdentityApp = await CreateManagedIdentityAsync(httpManager, addProbeMock: false, addSourceCheck: false).ConfigureAwait(false);
+
+ var result = await (managedIdentityApp as ManagedIdentityApplication)
+ .GetManagedIdentitySourceAsync(ManagedIdentityTests.ImdsProbesCancellationToken).ConfigureAwait(false);
+
+ Assert.AreEqual(ManagedIdentitySource.Imds, result.Source);
+ Assert.IsFalse(result.IsMtlsPopSupportedByHost);
+ }
+ }
+ #endregion IsMtlsPopSupportedByHost Tests
+
#region Fallback Behavior Tests
// Verifies non-mTLS request after IMDS detection uses IMDSv1 (Bearer),
[TestMethod]
diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs
index 400280577d..0c07fd4d80 100644
--- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs
@@ -69,8 +69,9 @@ public async Task GetManagedIdentityTests(
if (managedIdentitySource == ManagedIdentitySource.Imds)
{
- // Discovery probes V1 only
+ // Discovery probes V1 only, then fetches compute metadata
httpManager.AddMockHandler(MockHelpers.MockImdsProbe(ImdsVersion.V1));
+ httpManager.AddMockHandler(MockHelpers.MockImdsComputeMetadata());
}
var miSourceResult = await mi.GetManagedIdentitySourceAsync(ImdsProbesCancellationToken).ConfigureAwait(false);