Coding Stephan

Emulate Managed Identities during development

Ever used Managed Identities in Azure? You should, it’s a great way to get rid off passwords in your code and configuration. In this post I’ll show you how managed identities actually work. And how I built a small app that should help you use the ManagedIdentityCredential in Docker and or during development.

App running in docker using Managed Identity

TL:DR

Don’t want to be bothered with all the technical details of how the Managed Identity Client actually works, but just want to see how to use it in docker? Jump to Emulating the token endpoint.

What are Managed Identities?

I won’t go into what managed identities are or how to use it. Check out these resources if you want to know more:

Managed identities are a way for applications running in Azure to “magically” get tokens for all resources in Azure that the provisioned identity has access to.

Different Token Credentials

Microsoft has several Token Credentials, I’ll explain some of them briefly.

ChainedTokenCredential

The ChainedTokenCredential allows you to chain several Token credentials together. They will be used in the order you added them, and the first token credential that returns a token will be used. Since you can pick the order this can be configured exactly the way you want.

DefaultAzureCredential

You can see the DefaultAzureCredential as a ChainedTokenCredential with some useful defaults set. If you use it without any options it will use the following credentials (in this order). Using the options in the constructor you can exclude some of them. Once you start excluding several of them, you might want to look into the ChainedTokenCredential since that might speedup your code as it won’t have to do the discovery of the right source.

  • EnvironmentCredential
  • WorkloadIdentityCredential
  • ManagedIdentityCredential
  • AzureDeveloperCliCredential
  • SharedTokenCacheCredential
  • VisualStudioCredential
  • VisualStudioCodeCredential
  • AzureCliCredential
  • AzurePowerShellCredential
  • InteractiveBrowserCredential

ManagedIdentityCredential

The ManagedIdentityCredential, is one of the credentials in the DefaultAzureCredential and it’s one used when you’re using Managed Identities in Azure. It allows you to get a token from one of the methods available in Azure, and it will pick the first one available.

You can check the source of the ManagedIdentityCredential here. Which will then call the ManagedIdentityClient, which will then automatically select any of the available ManagedIdentitySources.

CloudShell Managed Identity Source

I went through all the sources and found that the CloudShellManagedIdentitySource just uses one environment variable, namely MSI_ENDPOINT. And the cloud shell is the only point where I can directly access that endpoint to see what it actually does.

# Show the value of the environment variable
$env:MSI_ENDPOINT
# the output was http://localhost:50342/oauth2/token

In the source code of the CloudShellManagedIdentitySource I found that it then does a http request that looks like:

POST http://localhost:50342/oauth2/token
Metadata: true
Content-Type: application/x-www-form-urlencoded
...

resource=http%3A%2F%2Fmanagement.azure.com

I must admit at this point I also tried setting the MSI_ENDPOINT in my test app to some requestinspector url, just to see what happens. And as expected my local app tried to get a token from this external endpoint.

So I tried to get a token in the CloudShell to see what the response should look like.

# Change the endpoint to the result from the message above
$r = iwr http://localhost:50342/oauth2/token -Method 'POST' -Headers @{'Metadata' = 'true'} -Body 'resource=https%3A%2F%2Fmanagement.azure.com'
$r.Content

This results in a message like the one below, my guess is that all managed identity endpoints respond with a similar message.

{
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ii1LSTNROW5OUjdiUm9meG1lWm9YcWJIWkdldyIsImtpZCI6Ii1LSTNROW5OUjdiUm9meG1lWm9YcWJIWkdldyJ9.eyJhdWQiOiJodHRwczov___remove__for__security___.XvOBGhCw8Qr___remove__for__security___w",
    "refresh_token": "",
    "expires_in": "3822",
    "expires_on": "1691056808",
    "not_before": "1691052614",
    "resource": "https://management.core.windows.net/",
    "token_type": "Bearer"
}

So to summerize the working of the Managed Identity Client, it seems to work like this:

  1. Find first available Managed Identity Source based on environment variables.
  2. If just MSI_ENDPOINT is found, it will pick the CloudShellManagedIdentitySource
  3. This source uses the endpoint and the resource you want a token for, which is just the bare application uri you can set for all your app registrations in the Azure Portal.
  4. The resource is posted, url form endcoded, to the url specified in MSI_ENDPOINT together with the Metadata: true header (this header is important or it does not work).
  5. The MSI Endpoint returns a json message with the resource and the access token.

For those watching closely, the resource in the response is different than the resource I requested. Even though the Azure portal UI does not allow this, it’s perfectly possible to se multiple App Uri’s for a single app through the manifest editor in the portal and/or the api. My guess is that https://management.core.windows.net/ is the primary App Uri and https://management.azure.com is one of the secondary App Uri’s.

Emulating the token endpoint

The MSI token endpoint is just a pre-defined way to get a token for some api. Which API, is send as an FORM encoded message.

I say emulating the token endpoint because this method will use some sort of credentials to get the token. Which credentials is up to you.

We now know which endpoints we need, I added them to my Assertion.Proxy. You can choose which credentials you wish to use by picking the correct endpoint.

Install and run this global tool

dotnet tool install --global Smartersoft.Identity.Client.Assertion.Proxy --version 0.7.0
az-kv-proxy

This will start a web api on you local machine, with swagger support. Open the documentation at http://localhost:5616/swagger/index.html as specified in the console output.

Pick any of the MSI endpoints, which are modeled to emulate the token endpoint as expected by the CloudShellManagedIdentitySource.

Use DefaultAzureCredential in Docker

We want to build and deploy all our apps as docker containers to not be bound to specific deployment methods. To not have any surprises we like to debug our applications in docker as well. None of the credentials seem to work in docker, not even the DefaultAzureCredential. This is an issue for us, since we don’t like passwords in our development environments.

For simplicity I’ll be using the /api/Msi/Forward endpoint, that just forwards the request to Azure using the DefaultAzureCredential, but since the assertion proxy is running as your local user, it does have access to some credentials like VisualStudioCredential or AzureCliCredential and even InteractiveBrowserCredential.

Just use the ManagedIdentityCredential or the DefaultAzureCredential in your application that you want to run in docker and set the environment variable MSI_ENDPOINT to http://host.docker.internal:5616/api/msi/forward.

You can do this in Visual Studio by editing the launch settings, if you want use this during debugging.

{
  "profiles": {
    "ConsoleApp1": {
      "commandName": "Project",
      "environmentVariables": {
        "MSI_ENDPOINT_External": "https://requestinspector.com/inspect/01h6rc1cjvr9ktkca8n31nvfp5",
        "MSI_ENDPOINT": "http://localhost:5616/api/Msi/forward"
      }
    },
    "Docker": {
      "commandName": "Docker",
      "environmentVariables": {
        "MSI_ENDPOINT": "http://host.docker.internal:5616/api/Msi/forward"
      }
    }
  }
}

Sample app running in Docker

You don’t have to believe me that this actually works, just create a new console app in Visual studio, change the code to the code below (install Azure.Identity). And right click the project to add docker support.

Then you only have to change the environment variable.

using Azure.Identity;

namespace ConsoleApp1;

internal class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("Hello, World from Docker!");
        var credentials = new ManagedIdentityCredential();

        var tokenResult = await credentials.GetTokenAsync(new Azure.Core.TokenRequestContext(new[] { "https://management.azure.com/.default" }));
        Console.WriteLine("Token Length: " + tokenResult.Token.Length);
        var splitToken = tokenResult.Token.Split('.');
        Console.WriteLine("Token header: {0}", splitToken[0]);
        Console.WriteLine("Token payload: {0}", splitToken[1]);
        Console.WriteLine();
        Console.WriteLine("This is an actual token from Azure.Identity.ManagedIdentityCredential.");
       
    }
}

App running in docker using Managed Identity

Emulate token endpoint in Azure Functions

I recon it would not be very hard to emulate the MSI endpoint in Azure Functions. That would allow you to actually use a managed identity (the one provisioned for the Azure Functions App) during development. And truly test your app as if it was using managed identities.

Then you would be able to set the MSI_ENDPOINT variable to the address of your function, hopefully at least protected with a functions key and some IP restriction. I won’t be providing the exact code, if you start messing with these kind of solutions there is bound to be a security issue. And I don’t want to be responsible.

This might also enable multi tenant scenarios, which happens to be the only thing missing in building applications for multiple tenants. Something like provision this Azure Functions app with a managed identity, and send us the url you can get for this specific function. Again, not recommending this!

Series: Managed Identity

Conclusion

This post shows you how to trick the Azure.Identity SDK into getting a token from a different endpoint then the actual token endpoint used for .

The Assertion.Proxy is built for DEVELOPMENT PURPOSES only! Please DO NOT start using it in production! It isn’t a good idea to expose an endpoint that allows getting tokens for various Api’s if you do not manage access to it correctly.