Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ private AcquireTokenForManagedIdentityParameterBuilder WithResource(string resou
{
Parameters.Resource = ScopeHelper.RemoveDefaultSuffixIfPresent(resource);
CommonParameters.Scopes = new string[] { Parameters.Resource };

if (resource.Equals("api://AzureFMITokenExchange/.default"))
{
Parameters.IsFmiServiceFabric = true;
ValidateUseOfExperimentalFeature();
}

return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ internal class AcquireTokenForManagedIdentityParameters : IAcquireTokenParameter

public string Resource { get; set; }

public bool IsFmiServiceFabric { get; set; }

public void LogParameters(ILoggerAdapter logger)
{
if (logger.IsLoggingEnabled(LogLevel.Info))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ private async Task<AuthenticationResult> SendTokenRequestForManagedIdentityAsync
await ResolveAuthorityAsync().ConfigureAwait(false);

ManagedIdentityClient managedIdentityClient =
new ManagedIdentityClient(AuthenticationRequestParameters.RequestContext);
new ManagedIdentityClient(AuthenticationRequestParameters.RequestContext, _managedIdentityParameters);

ManagedIdentityResponse managedIdentityResponse =
await managedIdentityClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace Microsoft.Identity.Client.ManagedIdentity
internal class EnvironmentVariables
{
public static string IdentityEndpoint => Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT");
public static string FmiServiceFabricEndpoint => Environment.GetEnvironmentVariable("APP_IDENTITY_ENDPOINT");
public static string FmiServiceFabricApiVersion => Environment.GetEnvironmentVariable("IDENTITY_API_VERSION");
public static string IdentityHeader => Environment.GetEnvironmentVariable("IDENTITY_HEADER");
public static string PodIdentityEndpoint => Environment.GetEnvironmentVariable("AZURE_POD_IDENTITY_AUTHORITY_HOST");
public static string ImdsEndpoint => Environment.GetEnvironmentVariable("IMDS_ENDPOINT");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ internal class ManagedIdentityClient
private const string LinuxHimdsFilePath = "/opt/azcmagent/bin/himds";
private readonly AbstractManagedIdentity _identitySource;

public ManagedIdentityClient(RequestContext requestContext)
public ManagedIdentityClient(RequestContext requestContext, AcquireTokenForManagedIdentityParameters acquireTokenForManagedIdentityParameters)
{
using (requestContext.Logger.LogMethodDuration())
{
_identitySource = SelectManagedIdentitySource(requestContext);
_identitySource = SelectManagedIdentitySource(requestContext, acquireTokenForManagedIdentityParameters);
}
}

Expand All @@ -35,8 +35,13 @@ internal Task<ManagedIdentityResponse> SendTokenRequestForManagedIdentityAsync(A
}

// This method tries to create managed identity source for different sources, if none is created then defaults to IMDS.
private static AbstractManagedIdentity SelectManagedIdentitySource(RequestContext requestContext)
private static AbstractManagedIdentity SelectManagedIdentitySource(RequestContext requestContext, AcquireTokenForManagedIdentityParameters acquireTokenForManagedIdentityParameters)
{
if (acquireTokenForManagedIdentityParameters.IsFmiServiceFabric)
{
return ServiceFabricFederatedManagedIdentitySource.Create(requestContext);
}

return GetManagedIdentitySource(requestContext.Logger) switch
{
ManagedIdentitySource.ServiceFabric => ServiceFabricManagedIdentitySource.Create(requestContext),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ public enum ManagedIdentitySource
/// <summary>
/// The source to acquire token for managed identity is Machine Learning Service.
/// </summary>
MachineLearning
MachineLearning,

/// <summary>
/// The source to acquire token for managed identity is Service Fabric Federated.
/// </summary>
ServiceFabricFederated
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Globalization;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Internal;

namespace Microsoft.Identity.Client.ManagedIdentity
{
internal class ServiceFabricFederatedManagedIdentitySource : AbstractManagedIdentity
{
private string _serviceFabricMsiApiVersion = EnvironmentVariables.FmiServiceFabricApiVersion;
private readonly Uri _endpoint;
private readonly string _identityHeaderValue;
private static string _mitsEndpointFmiPath => "/metadata/identity/oauth2/fmi/credential";

internal static Lazy<HttpClient> _httpClientLazy;

public static AbstractManagedIdentity Create(RequestContext requestContext)
{
VerifyEnvVariablesAreAvailable();

Uri endpointUri;
string identityEndpoint = EnvironmentVariables.IdentityEndpoint;

requestContext.Logger.Info(() => "[Managed Identity] Service fabric federated managed identity is available.");
identityEndpoint = EnvironmentVariables.FmiServiceFabricEndpoint;
requestContext.Logger.Info(() => "[Managed Identity] Using FMI Service fabric endpoint.");

if (!Uri.TryCreate(identityEndpoint + _mitsEndpointFmiPath, UriKind.Absolute, out endpointUri))
{
string errorMessage = string.Format(CultureInfo.InvariantCulture, MsalErrorMessage.ManagedIdentityEndpointInvalidUriError,
"APP_IDENTITY_ENDPOINT", identityEndpoint, "FMI Service Fabric");

// Use the factory to create and throw the exception
var exception = MsalServiceExceptionFactory.CreateManagedIdentityException(
MsalError.InvalidManagedIdentityEndpoint,
errorMessage,
null,
ManagedIdentitySource.ServiceFabricFederated,
null);

throw exception;
}

requestContext.Logger.Verbose(() => "[Managed Identity] Creating Service Fabric federated managed identity. Endpoint URI: " + identityEndpoint);

return new ServiceFabricFederatedManagedIdentitySource(requestContext, endpointUri, EnvironmentVariables.IdentityHeader);
}

private static void VerifyEnvVariablesAreAvailable()
{
if (string.IsNullOrEmpty(EnvironmentVariables.IdentityServerThumbprint))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, MsalErrorMessage.ManagedIdentityFmiInvalidEnvVariableError,
"IDENTITY_SERVER_THUMBPRINT"));
}
if (string.IsNullOrEmpty(EnvironmentVariables.FmiServiceFabricEndpoint))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, MsalErrorMessage.ManagedIdentityFmiInvalidEnvVariableError,
"APP_IDENTITY_ENDPOINT"));
}
if (string.IsNullOrEmpty(EnvironmentVariables.IdentityHeader))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, MsalErrorMessage.ManagedIdentityFmiInvalidEnvVariableError,
"IDENTITY_HEADER"));
}
if (string.IsNullOrEmpty(EnvironmentVariables.FmiServiceFabricApiVersion))
{
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, MsalErrorMessage.ManagedIdentityFmiInvalidEnvVariableError,
"IDENTITY_API_VERSION", "FMI Service Fabric"));
}
}

internal override Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> GetValidationCallback()
{
return ValidateServerCertificateCallback;
}

private bool ValidateServerCertificateCallback(HttpRequestMessage message, X509Certificate2 certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}

return string.Equals(certificate.GetCertHashString(), EnvironmentVariables.IdentityServerThumbprint, StringComparison.OrdinalIgnoreCase);
}

private ServiceFabricFederatedManagedIdentitySource(RequestContext requestContext, Uri endpoint, string identityHeaderValue) :
base(requestContext, ManagedIdentitySource.ServiceFabric)
{
_endpoint = endpoint;
_identityHeaderValue = identityHeaderValue;

if (requestContext.ServiceBundle.Config.ManagedIdentityId.IsUserAssigned)
{
requestContext.Logger.Warning(MsalErrorMessage.ManagedIdentityUserAssignedNotConfigurableAtRuntime);
}
}

protected override ManagedIdentityRequest CreateRequest(string resource)
{
ManagedIdentityRequest request = new ManagedIdentityRequest(HttpMethod.Get, _endpoint);

request.Headers["secret"] = _identityHeaderValue;
_requestContext.Logger.Info("[Managed Identity] Request is for FMI, no ids or resource will be added to the request.");
request.QueryParameters["api-version"] = _serviceFabricMsiApiVersion;
return request;
}

internal string GetEndpointForTesting()
{
return _endpoint.ToString();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ internal class ServiceFabricManagedIdentitySource : AbstractManagedIdentity

public static AbstractManagedIdentity Create(RequestContext requestContext)
{
Uri endpointUri;
string identityEndpoint = EnvironmentVariables.IdentityEndpoint;

requestContext.Logger.Info(() => "[Managed Identity] Service fabric managed identity is available.");

if (!Uri.TryCreate(identityEndpoint, UriKind.Absolute, out Uri endpointUri))
if (!Uri.TryCreate(identityEndpoint, UriKind.Absolute, out endpointUri))
{
string errorMessage = string.Format(CultureInfo.InvariantCulture, MsalErrorMessage.ManagedIdentityEndpointInvalidUriError,
"IDENTITY_ENDPOINT", identityEndpoint, "Service Fabric");
Expand Down Expand Up @@ -103,5 +104,10 @@ protected override ManagedIdentityRequest CreateRequest(string resource)

return request;
}

internal string GetEndpointForTesting()
{
return _endpoint.ToString();
}
}
}
1 change: 1 addition & 0 deletions src/client/Microsoft.Identity.Client/MsalErrorMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ public static string InvalidTokenProviderResponseValue(string invalidValueName)
public const string ManagedIdentityUnexpectedErrorResponse = "[Managed Identity] The error response was either empty or could not be parsed.";

public const string ManagedIdentityEndpointInvalidUriError = "[Managed Identity] The environment variable {0} contains an invalid Uri {1} in {2} managed identity source.";
public const string ManagedIdentityFmiInvalidEnvVariableError = "[Managed Identity] The environment variable {0} is null or empty in {1} managed identity source.";
public const string ManagedIdentityNoChallengeError = "[Managed Identity] Did not receive expected WWW-Authenticate header in the response from Azure Arc Managed Identity Endpoint.";
public const string ManagedIdentityInvalidChallenge = "[Managed Identity] The WWW-Authenticate header in the response from Azure Arc Managed Identity Endpoint did not match the expected format.";
public const string ManagedIdentityInvalidFile = "[Managed Identity] The file on the file path in the WWW-Authenticate header is not secure.";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ServiceFabricFederated = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ServiceFabricFederated = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ServiceFabricFederated = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ServiceFabricFederated = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ServiceFabricFederated = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ServiceFabricFederated = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ public enum MsiAzureResource
ServiceFabric
}

public static void SetEnvironmentVariables(ManagedIdentitySource managedIdentitySource, string endpoint, string secret = "secret", string thumbprint = "thumbprint")
public static void SetEnvironmentVariables(
ManagedIdentitySource managedIdentitySource,
string endpoint,
string secret = "secret",
string thumbprint = "thumbprint",
string version = "version")
{
switch (managedIdentitySource)
{
Expand All @@ -59,6 +64,12 @@ public static void SetEnvironmentVariables(ManagedIdentitySource managedIdentity
Environment.SetEnvironmentVariable("IDENTITY_HEADER", secret);
Environment.SetEnvironmentVariable("IDENTITY_SERVER_THUMBPRINT", thumbprint);
break;
case ManagedIdentitySource.ServiceFabricFederated:
Environment.SetEnvironmentVariable("APP_IDENTITY_ENDPOINT", endpoint);
Environment.SetEnvironmentVariable("IDENTITY_HEADER", secret);
Environment.SetEnvironmentVariable("IDENTITY_SERVER_THUMBPRINT", thumbprint);
Environment.SetEnvironmentVariable("IDENTITY_API_VERSION", version);
break;
case ManagedIdentitySource.MachineLearning:
Environment.SetEnvironmentVariable("MSI_ENDPOINT", endpoint);
Environment.SetEnvironmentVariable("MSI_SECRET", secret);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ public static string GetMsiImdsErrorResponse()
"\"correlation_id\":\"77145480-bc5a-4ebe-ae4d-e4a8b7d727cf\",\"error_uri\":\"https://westus2.login.microsoft.com/error?code=500011\"}";
}

public static HttpResponseMessage CreateSuccessTokenResponseMessageForMits(
string accessToken = "some-access-token",
string expiresOn = "1744887386")
{
var stringContent = $"{{\"token_type\":\"Bearer\",\"access_token\":\"{accessToken}\",\"expires_on\":{expiresOn},\"resource\":\"api://AzureFMITokenExchange/.default\"}}";

return CreateSuccessResponseMessage(stringContent);
}

public static string CreateClientInfo(string uid = TestConstants.Uid, string utid = TestConstants.Utid)
{
return Base64UrlHelpers.Encode("{\"uid\":\"" + uid + "\",\"utid\":\"" + utid + "\"}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,43 @@ public static void AddManagedIdentityWSTrustMockHandler(
});
}

public static void CreateFmiCredentialForMitsHandler(
this MockHttpManager httpManager,
string secret = "secret",
string version = "version",
string requestUri = "SomeUri",
string accessToken = "header.payload.signature",
bool expiredResponse = false
)
{
string expiresOn;
DateTimeOffset dto = DateTimeOffset.UtcNow;

if (expiredResponse)
{
long unixTimeSeconds = dto.ToUnixTimeSeconds() - 3600;
expiresOn = unixTimeSeconds.ToString();
}
else
{
long unixTimeSeconds = dto.ToUnixTimeSeconds() + 3600;
expiresOn = unixTimeSeconds.ToString();
}

var handler = new MockHttpMessageHandler()
{
ExpectedUrl = requestUri,
ExpectedMethod = HttpMethod.Get,
ResponseMessage = MockHelpers.CreateSuccessTokenResponseMessageForMits(accessToken: accessToken, expiresOn: expiresOn),
ExpectedRequestHeaders = new Dictionary<string, string>
{
{ "Secret", secret },
},
};

httpManager.AddMockHandler(handler);
}

public static void AddRegionDiscoveryMockHandlerNotFound(
this MockHttpManager httpManager)
{
Expand Down
Loading