Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Identity.Client.AppConfig
{
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// This type is shared by managed identity and confidential client mTLS Proof-of-Possession
/// scenarios. A value greater than <see cref="None"/> means the host can bind a token to a
/// key; it does <b>not</b> by itself imply hardware attestation. Attestation corresponds to
/// the <see cref="KeyGuard"/> tier specifically.
/// </remarks>
public enum MtlsBindingStrength
{
/// <summary>
/// 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).
/// </summary>
None = 0,

/// <summary>
/// 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.
/// </summary>
Software = 1,
Comment thread
Robbie-Microsoft marked this conversation as resolved.

// 2 is reserved for a future tier (for example, TPM-backed keys).
Comment thread
gladjohn marked this conversation as resolved.

/// <summary>
/// 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.
/// </summary>
KeyGuard = 3
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ protected AbstractManagedIdentity(RequestContext requestContext, ManagedIdentity
_sourceType = sourceType;
}

// True only for the IMDSv1 source. IMDSv1 and IMDSv2 both report
// <see cref="ManagedIdentitySource.Imds"/> 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<ManagedIdentityResponse> AuthenticateAsync(
Expand Down Expand Up @@ -65,16 +70,16 @@ public virtual async Task<ManagedIdentityResponse> 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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Represents compute metadata retrieved from the Azure Instance Metadata Service (IMDS).
/// </summary>
[JsonObject]
[Preserve(AllMembers = true)]
internal class ComputeMetadataResponse
{
/// <summary>Operating system type (e.g., Windows, Linux).</summary>
[JsonProperty("osType")]
public string OsType { get; set; }

/// <summary>
/// Security profile indicating platform security posture. May be null when IMDS
/// does not return security profile information for the current VM.
/// </summary>
[JsonProperty("securityProfile")]
public ComputeSecurityProfile SecurityProfile { get; set; }
}

/// <summary>
/// Represents the security profile of an Azure VM from IMDS compute metadata.
/// </summary>
[JsonObject]
[Preserve(AllMembers = true)]
internal class ComputeSecurityProfile
{
/// <summary>Security type of the VM (e.g., TrustedLaunch, ConfidentialVM).</summary>
[JsonProperty("securityType")]
public string SecurityType { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Fetches compute metadata from the Azure Instance Metadata Service (IMDS)
/// to determine VM characteristics such as OS type and security profile.
/// </summary>
internal static class ImdsComputeMetadataManager
{
internal const string ImdsComputePath = "/metadata/instance/compute";
internal const string ImdsComputeApiVersion = "2021-02-01";

internal static async Task<ComputeMetadataResponse> GetComputeMetadataAsync(
IHttpManager httpManager,
ILoggerAdapter logger,
CancellationToken cancellationToken)
{
var headers = new Dictionary<string, string>
{
{ "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<ComputeMetadataResponse>(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;
}
}

/// <summary>
/// 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).
/// </summary>
internal static bool IsMtlsPopSupported(ComputeMetadataResponse metadata)
{
if (metadata is null)
{
return false;
Comment thread
Robbie-Microsoft marked this conversation as resolved.
}

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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ManagedIdentityRequest> CreateRequestAsync(string resource)
{
ManagedIdentityRequest request = new(HttpMethod.Get, _imdsEndpoint);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// This type is returned by <see cref="ManagedIdentityApplication.GetManagedIdentityCapabilitiesAsync"/>.
/// It is useful for credential chains such as <c>DefaultAzureCredential</c> to decide whether
/// managed identity is available and what binding strength the host supports.
/// </remarks>
public class ManagedIdentityCapabilities
{
/// <summary>
/// Gets the detected managed identity source.
/// </summary>
/// <value>
/// The <see cref="ManagedIdentitySource"/> detected on the environment, or
/// <see cref="ManagedIdentitySource.None"/> if no source was detected. The internal
/// IMDS v1/v2 distinction is not surfaced here; both report
/// <see cref="ManagedIdentitySource.Imds"/>.
/// </value>
public ManagedIdentitySource Source { get; }

/// <summary>
/// Gets the reason detection failed, if any.
/// </summary>
/// <value>
/// A single string describing why managed identity detection failed, or <c>null</c> when
/// a source was detected.
/// </value>
public string ErrorReason { get; }

/// <summary>
/// Gets the highest binding strength the current host is capable of producing.
/// </summary>
/// <value>
/// The strongest <see cref="MtlsBindingStrength"/> available on this host. This is the
/// primary capability signal; callers should branch on it rather than on the source label.
/// </value>
public MtlsBindingStrength MaxSupportedBindingStrength { get; }

/// <summary>
/// Gets a value indicating whether the host can bind a token to a key (mTLS
/// Proof-of-Possession).
/// </summary>
/// <value>
/// <c>true</c> when <see cref="MaxSupportedBindingStrength"/> is greater than
/// <see cref="MtlsBindingStrength.None"/>. This means the host can bind a token to a key;
/// it does <b>not</b> imply hardware attestation. Callers that require attestation must
/// check for the <see cref="MtlsBindingStrength.KeyGuard"/> tier.
/// </value>
public bool IsMtlsPopSupportedByHost => MaxSupportedBindingStrength > MtlsBindingStrength.None;

/// <summary>
/// Initializes a new instance of the <see cref="ManagedIdentityCapabilities"/> class.
/// </summary>
/// <param name="source">The detected managed identity source.</param>
/// <param name="maxSupportedBindingStrength">The highest binding strength the host supports.</param>
/// <param name="errorReason">The reason detection failed, or <c>null</c> on success.</param>
internal ManagedIdentityCapabilities(
ManagedIdentitySource source,
MtlsBindingStrength maxSupportedBindingStrength,
string errorReason = null)
{
Source = source;
MaxSupportedBindingStrength = maxSupportedBindingStrength;
ErrorReason = errorReason;
}
}
}
Loading
Loading