.Net, Azure and occasionally gamedev

Using Managed identities for Azure in .Net Core

2018/11/16

Managed Identity for Azure (previously MSI or Managed Service Identity) is slowly being rolled out to more and more services.

Before Managed Identity existed a lot of service principals where needed to handle access management between different services in Azure.

Since the service principals have to be created and managed manually they often linger in the AAD even if no longer needed and figuring out which ones are still required and which ones can be safely deleted is always a pain.

Luckily Managed Identity relieves that pain by automatically creating an identity for a resource and attaching it to the resource. The identity is then automatically deleted when the resource is removed!

Multiple configuration sources in .Net Core

.Net Core provides IConfiguration which allows access to variables from various sources (appsettings.json, Environment variables, ini, Keyvault, ..). You can even write your own provider if you really need to.

This makes it very convenient to load secrets from multiple sources.

Store secrets in a keyvault

The Microsoft recommended approach is to store secrets in a keyvault, however you still have the bootstrap problem as your appsettings must now contain: Keyvault url, client id and secret (and obviously none of these should be checked in).

A common approach to fix this "bootstrap problem" is to have the user set environment variables with the required values which the .Net Core webapp can then load when running locally.

However this is still tedious to set up in the beginning and you are still left with manually creating and managing the service principal for keyvault access.

Managed Identity for Azure to the rescue

With Managed Identity you no longer need service principals and don't have to manage the client id/secret for every keyvault.

This is now automatically handled by the AAD for you. By enabling MSI on a webapp an internal AAD application is created for the web app and you can then assign roles to said application (in our case: keyvault reader).

Initially this feature only worked when running from inside Azure since Managed Identity was only available for Azure resources.

This meant that you still needed the keyvault url, a service principal id and secret when running locally (kind of defeating the purpose).

However this has been streamlined to also work locally by using multiple sign in options as fallbacks (your Visual Studio account, azure CLI and AAD integration).

The easiest way to get it working is to sign into Visual Studio 2017 (v15.6+ required) and making sure that the correct account is selected under "Tools -> Options -> Azure Service Authentication".

Finally, all that's required is the code:

appsettings.json:

  "Keyvault": {
    "Url": "__KeyvaultUrl__"
  }

appsettings.Development.json:

  "Keyvault": {
    "Url": "https://mydevkeyvault.vault.azure.net/"
  }

I'm hardcoding the dev keyvault in the appsettings.Development.json file as it is only used for developers locally and can be overridden in the release per environment.

You are of course free to use different options (e.g. always loading the keyvault from an environment variable which requires every developer to set the environment variable first).

Startup.cs

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: false)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

        // build a temporary config
        // with the keyvault url from appsettings
        // to include the keyvault variables as an override
        var tmpConfig = builder.Build();
        var tokenProvider = new AzureServiceTokenProvider();
        var kvClient = new KeyVaultClient((authority, resource, scope)
            => tokenProvider.KeyVaultTokenCallback(authority, resource, scope));
        builder.AddAzureKeyVault(tmpConfig["KeyVault:Url"], kvClient,
            new DefaultKeyVaultSecretManager());

        // finally add environment variables as the
        // last override and build the full config
        builder.AddEnvironmentVariables();
        Configuration = builder.Build();
}

The code will create a temporary config from the appsettings*.json files and use the url it finds to add the keyvault as another source.

For local development this means it's now using the url from the appsettings.Development.json file, for actual releases it will use the (tokenized) variable from appsettings.json.

Finally it also adds environment variable overrides and builds the actual config.

Summary

Enable Managed Identity for Azure on your webapp (and/or slots) via the toggle in Azure or ARM deployment.

Add the Managed Identity to the keyvault access policies (list and read). The name of the Managed Identity is the name of your webapp (or webappname/slots/slotname) if it's a slot.

With the dev keyvault checked into appsettings.Devlopment.json any developer (with the correct access rights in Azure) can get started right away and the only requirement is that he/she is signed into Visual Studio with their account.

During release set the environment variable "Keyvault--Url" to the correct keyvault per environment and tokenize appsettings.json to ensure the correct keyvault is used.

Overall this drastically reduces the overhead of tokenization, streamlines the process for local development, removes the need for manually managing service principals and puts the access management right on the resource itself!

tagged as .Net Core, Azure and Keyvault