This is the fifth part of a series of blog posts related to Azure AD best practices. They are all related to a talk I gave at Tech Days Finland as well as in the Microsoft Identity Developer Community Office Hours.

The topic for this time is keeping secrets out of version control. You can find the two sample applications related to this on GitHub: https://github.com/juunas11/7-deadly-sins-in-azure-ad-app-development/tree/master/SecretsInVcs. The SecretsInVcs project shows the wrong approach, and the SecretsInVcsDoneBetter project shows an approach we will discuss here.

The problem we are trying to solve

Why do people put secrets in shared code repositories? Mostly to make work easier. Maybe the application needs to use a shared service that requires an access key? Having secrets in a configuration file in version control often means getting started with the project is a very low effort operation. Clone the repository to the local machine and run it. That's an excellent property to have in any project for sure.

But this approach has downsides. If the repository is public, e.g. in GitHub, anyone can get those keys. Which might be obvious to you, but it happens a lot. Usually they are published by mistake.

You might be thinking well our repository is not public so it's fine. Okay, let's say it's your organization's own repository. The problem there is that the number of people with access to the repository is still often higher than the number of people who are supposed to have access to those keys.

Now that you understand the issue, let's look at some ways of solving this.

User secrets

One potential solution would be to use user secrets. These were introduced as a way of keeping development environment-specific configuration settings out of the project folder, and thus out of version control. The only issue I have with user secrets is that they need to be setup for every developer. Which means we lose the property of easily getting started with developing the application. This option can be scripted though, and can be a good option.

It should be remembered though that user secrets are not encrypted, they are only kept in a special folder under your user profile so you can't accidentally publish them.

Azure Key Vault

If you want to store secrets on Azure, Key Vault is the standard way for it. You can even add it as a configuration provider in the Microsoft.Extensions.Configuration pipeline. This means secrets defined in a Key Vault can be merged as part of the configuration built from files and user secrets.

That's all fine and good but we still need to authenticate to Key Vault. We are left with the bootstrapping problem: we need a secret to get the secrets. Luckily there is a solution.

We setup the configuration in the sample app like this:

private static IConfiguration BuildConfig()
{
    var builder = new ConfigurationBuilder()
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

    // Build config so we can get KeyVault URL from config
    var config = builder.Build();
    // Add KeyVault as configuration source
    var keyVaultUrl = config["KeyVaultUrl"];
    builder.AddAzureKeyVault(keyVaultUrl);
    return builder.Build();
}

Note the only thing we define to the configuration provider is where to find the Key Vault. So how does it authenticate? The answer is that it uses AzureServiceTokenProvider internally. This class attempts to use various authentication methods. There are two in particular that it can use here:

  1. Visual Studio authentication
  2. AZ CLI authentication

The first option uses an account setup in your Visual Studio to authenticate. You can choose which account is used by going to Tools -> Options -> Azure Service Authentication. The only issue with this is that it only works in VS on Windows.

The second option works anywhere. Ensure you are logged in to the cross-platform AZ command line interface, and it'll be used in the background to authenticate.

Now we can put the shared secrets in the Key Vault. We can create a security group in Azure AD for this project's developers, and assign an access policy that gives at least read access to the secrets.

With this the project setup does require that the user is added to the security group, and that the authentication is setup on their machine. But that is still relatively low effort. And now the secrets are kept in a secure location.

This has the additional good property of being able to use this same approach in production as well. If you run the app on Azure, and you have a managed identity setup, the same exact code will work with no changes.

One of the problems of this approach is that Key Vault is called every time the app is run, which slows it down.

Summary

User secrets can be used as a basic method of keeping settings out of version control. But they are not encrypted, which could be an issue. Setting them up manually is also a bit tedious. But they do work on all platforms, and don't require you to use Azure.

Using secrets from Key Vault has some nice properties, for example, the secrets are only in memory when running the app. It also enforces access requirements every time the app is run locally. But it does require that outbound call to Key Vault every time the app is run. It also requires a little bit of setup, and I have had some issues with the local token provider in the past.

I cannot really say what is the better option from these two. They both have pros and cons.

There is also a hybrid approach where you run a script that uses the AZ CLI to load all secrets from a shared Key Vault and sets them up as user secrets. Now the secrets are stored on disk unencrypted, but they are out of source control. It provides a relatively easy setup, and doesn't require constant access to Key Vault.

No matter which approach you take, I wish you keep secrets out of your shared code repository.

What do you think? Which approach do you like? Feel free to give your opinion in the comments :)

Until next time, when we will discuss putting secrets in native apps and why it is bad!

Links