ASP.NET Core's configuration system is pretty awesome. Way better than what we had in "classic" MVC at least.

I wrote an article back in the 1.0 days on all things configuration: Configuration Deep Dive. Then later I wrote on Azure AD Managed Service Identity.

ASP.NET Core supports Azure Key Vault as a configuration source. But I would not want to put a client id and secret in the configuration somewhere. It would kind of defeat the purpose of using Key Vault.

So why don't we use Azure AD Managed Service Identity to get tokens for Key Vault, and get the configuration that way?

Desired end result

What we would like to achieve is storage of sensitive settings like database connection strings in an environment-specific Azure Key Vault. General advice is that you should not use a Key Vault for multiple environments.

These settings should be gotten from Key Vault when the app starts up, and be injected into the standard configuration of ASP.NET Core.

This way none of the application code needs to be concerned where the settings came from. The application can simply use the standard configuration system to access them.

We would also like to use Azure AD Managed Service Identity to access Key Vault from all environments without storing any credentials in the app.

Setup

What we need to setup in Azure:

  1. Two Key Vaults (for Production and Development environments)
    • Basically it is a good idea to have a vault per environment
    • You should also allow your user to get secrets from the Development vault
  2. Web App for the Production environment
  3. Enable Managed Service Identity on the Web App
  4. Allow the generated Service Principal access to the Production Key Vault

If you want more details on how to enable MSI and use it in your Development environment, you can refer to my earlier article: Azure AD Managed Service Identity.

The only setting our app then needs is the Key Vault base URL.

For example, we can specify an appsettings.Development.json file like this:

{
  "KeyVault": {
    "BaseUrl": "https://mydevvaultname.vault.azure.net/"
  }
}

Then we can set an App setting on the Web App:

  • Key: KeyVault:BaseUrl
    • Or KeyVault__BaseUrl if your environment does not allow colons in environment variables
  • Value: https://myprodvault.vault.azure.net/

This way we use the Production Key Vault when we run the app in the Web App.

Now we also need to add our secrets to both Key Vaults. You can try specifying a secret with a name like SecretSettings:MySecret, but it won't work.

Key Vault secret names are part of a URL, so they cannot contain certain characters like colons.

We must replace colons with double dashes, so the earlier setting's valid key is SecretSettings--MySecret.

Installing necessary dependencies

Your app will need at least the MSI library from NuGet: Microsoft.Azure.Services.AppAuthentication.

If you are running on ASP.NET Core 2.0, and using the Microsoft.AspNetCore.All meta-package, you already have the Key Vault configuration provider.

2.1's meta-package does not contain it.

If you need the Key Vault configuration provider, install Microsoft.Extensions.Configuration.AzureKeyVault.

Setting up the configuration provider

Now we get to the fun part :)

In ASP.NET Core 2.0, we can use ConfigureAppConfiguration on the WebHostBuilder in Program.cs to add additional configuration sources.

Here is what we need to do:

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureAppConfiguration((ctx, builder) =>
        {
            //Build the config from sources we have
            var config = builder.Build();
            //Create Managed Service Identity token provider
            var tokenProvider = new AzureServiceTokenProvider();
            //Create the Key Vault client
            var kvClient = new KeyVaultClient((authority, resource, scope) => tokenProvider.KeyVaultTokenCallback(authority, resource, scope));
            //Add Key Vault to configuration pipeline
            builder.AddAzureKeyVault(config["KeyVault:BaseUrl"], kvClient, new DefaultKeyVaultSecretManager());
        })
        .Build();

First we build the configuration object based on the default sources. This allows us to access the Key Vault base URL we set up in appsettings.Development.json.

Then we create the token provider which will acquire an access token to Key Vault by using Managed Service Identity.

Then we must create a KeyVaultClient which uses the token provider.

Finally we can add the configuration provider using the base URL, KeyVaultClient, and the DefaultKeyVaultSecretManager.

And that's all folks! If we now run the app, we will get settings from the Dev Key Vault added to the settings. The best part is that the rest of the app can be completely oblivious where the settings came from.

If you do not want to use Key Vault in Development, you can access the IHostingEnvironment via the ctx parameter and check if the environment is not Development.

Summary and links

Thanks for reading!

All in all, there are quite a few things you must do to achieve this secure configuration storage. But once it is done, it is easy to add settings to the Key Vaults.

By using Managed Service Identity, our app has no secrets in configuration files. And it does not have any in the Web App's configuration either. Which is pretty awesome.

Links: