Skip to content

Adding FMI source to MI app #5299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public string ClientVersion
public string CertificateIdToAssociateWithToken { get; set; }

public Func<AppTokenProviderParameters, Task<AppTokenProviderResult>> AppTokenProvider;
public bool IsFmiServiceFabric { get; set; }

#region ClientCredentials

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ public ManagedIdentityApplicationBuilder WithClientCapabilities(IEnumerable<stri
return this;
}

/// <summary>
/// Configures the application to use the FMI service fabric managed identity endpoint.
/// </summary>
/// <remarks>
/// This is used only for Service Fabric applications that are using the FMI managed identity endpoint.
/// </remarks>
public ManagedIdentityApplicationBuilder WithServiceFabricFmi()
{
Config.IsFmiServiceFabric = true;
return this;
}

/// <summary>
/// Builds an instance of <see cref="IManagedIdentityApplication"/>
/// from the parameters set in the <see cref="ManagedIdentityApplicationBuilder"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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 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,33 @@ 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 (requestContext.ServiceBundle.Config.IsFmiServiceFabric)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was considering letting MSAL autodetect the correct env variable, but I am not sure if it is a good idea. Both env variables may be set (IDENTITY_ENDPOINT and APP_IDENTITY_ENDPOINT), so I don't want to introduce bugs. Adding an api for this that will only be used from MISE will make this more robust.

Copy link
Member

@bgavrilMS bgavrilMS May 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's take a step back and think how we'd go about doing this in MSAL if we were to productize getting the FMI credential.

The FMI credential is just a token with a specific audience. And it can use different endpoints.

Can we use the existing MSI APIs, and have different logic based on the audience? i.e. if app developer requests token for api://AzureFMITokenExchange (or the GUID format) - then add your custom logic?

Also, let's add the "experimental features" to this?

Copy link
Member Author

@trwalke trwalke May 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bgavrilMS I understand what you are saying, but since this scenario isnt intended to use a resource and is simply acquiring the token from MITS, all of the additional application logic for resources/scopes/caching is not needed. I would also would be concerned if someone wanted to use MI to exchange an FMI credential for an access token using api://AzureFMITokenExchange, but instead they just get another FMI Credential. so, maybe instead of using the fmi token exchange resource, we can create some string like "FmiMitsAcquisition" or something so that no one will trigger this logic in MSAL by accident? But if you feel like no one will ever use api://AzureFMITokenExchange with a MI app, this this will work.

Copy link
Member

@bgavrilMS bgavrilMS May 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with an experimental API mi.AcquireFmiCredential(). Not sure I see a scenario where api://AzureFMITokenExchange can be mis-used either.

@rayluo - thoughts on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trwalke - can you put toghter a small design doc for this and let's discuss with the team?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bgavrilMS Added here
#5309

{
identityEndpoint = EnvironmentVariables.FmiServiceFabricEndpoint;
requestContext.Logger.Info(() => "[Managed Identity] Using FMI Service fabric endpoint.");

if (!Uri.TryCreate(identityEndpoint, 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.ServiceFabric,
null);

throw exception;
}
}
else 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 +125,10 @@ protected override ManagedIdentityRequest CreateRequest(string resource)

return request;
}

internal string GetEndpointForTesting()
{
return _endpoint.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithServiceFabricFmi() -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithServiceFabricFmi() -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithServiceFabricFmi() -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithServiceFabricFmi() -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithServiceFabricFmi() -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithServiceFabricFmi() -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ 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 fmiEndpoint = "")
{
switch (managedIdentitySource)
{
Expand All @@ -56,6 +56,7 @@ public static void SetEnvironmentVariables(ManagedIdentitySource managedIdentity

case ManagedIdentitySource.ServiceFabric:
Environment.SetEnvironmentVariable("IDENTITY_ENDPOINT", endpoint);
Environment.SetEnvironmentVariable("APP_IDENTITY_ENDPOINT", fmiEndpoint);
Environment.SetEnvironmentVariable("IDENTITY_HEADER", secret);
Environment.SetEnvironmentVariable("IDENTITY_SERVER_THUMBPRINT", thumbprint);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,29 @@ public void ValidateServerCertificateCallback_ServerCertificateValidationCallbac
}
}

[TestMethod]
public void ValidateThatFmiEndpointIsUsed()
{
using (new EnvVariableContext())
using (var httpManager = new MockHttpManager())
{
SetEnvironmentVariables(ManagedIdentitySource.ServiceFabric, "http://localhost:40342/metadata/identity/oauth2/token", fmiEndpoint: "http://localhost:40343/metadata/identity/oauth2/token");

var miBuilder = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned)
.WithServiceFabricFmi()
.WithHttpManager(httpManager);

var mi = miBuilder.BuildConcrete();

RequestContext requestContext = new RequestContext(mi.ServiceBundle, Guid.NewGuid(), null);

ServiceFabricManagedIdentitySource sf = ServiceFabricManagedIdentitySource.Create(requestContext) as ServiceFabricManagedIdentitySource;

Assert.IsInstanceOfType(sf, typeof(ServiceFabricManagedIdentitySource));
Assert.AreEqual("http://localhost:40343/metadata/identity/oauth2/token", sf.GetEndpointForTesting());
}
}

[TestMethod]
public async Task SFThrowsWhenGetHttpClientWithValidationIsNotImplementedAsync()
{
Expand Down
Loading