02 May, 2019

Dev Secrets and the ASP.NET Core Secret Manager

by Rich Grimes

When performing an application security assessment, one of the things we look for are sensitive secrets committed to source control. Most web applications rely on secrets to perform security operations. Those secrets could include API keys, database credentials, third-party service credentials, encryption keys, and more. The disclosure of these secrets could lead to unauthorized third-party service access or even complete system compromise.

In ASP.NET projects application secrets are often stored within configuration files such as web.config or appsettings.json. Once the sensitive data is added, the configuration files get committed to the source code repository. Now there is potential for the sensitive data to be exposed to unauthorized individuals.

In this post, I will demonstrate how to protect sensitive development configuration data within your ASP.NET Core projects using the Secret Manager tool. Before we get started discussing the Secret Manager, keep in mind to never store sensitive data in the source code.

Secret Manager Overview

The Secret Manager tool allows developers to store and retrieve sensitive data during the development of an ASP.NET Core application. The sensitive data is stored in a separate location from the application source code. Because the Secret Manager stores the secrets separately from the source code, the sensitive data isn’t committed to the source code repository. This approach also allows developers to have their own set of secrets.

The Secret Manager does not encrypt the stored sensitive data and should never be considered as a trusted store. The sensitive data is stored in a JSON file as a key-value pair. It is also good practice to never use production secrets within your development and testing environments.

For the duration of this post, we will be using the MacOS console to demonstrate the features of the Secret Manager tool. However, if you are running Visual Studio within Windows, you have the option to modify the secrets storage from within the IDE. This can be achieved by right-clicking on the project in Solution Explorer, and selecting Manage User Secrets from the context menu.

Now that you have an understanding of what the Secret Manager does, let’s take a look at where the secrets are stored along with how to add, list, and remove secrets for our ASP.NET Core projects.

If you would like to follow along with the proceeding examples, you will need to create an ASP.NET Core version 2 or higher Web App project called DonutShop. You can create the project in Visual Studio or by using the following console commands:

mkdir DonutShop
cd DonutShop
dotnet new mvc --auth Individual

JSON Configuration File Location

Application secrets are stored in a JSON file located in the system protected user profile directory on the local machine.

The operating system type, as noted in the table below, determines location of the application secrets.json file:

OS Location
MacOS ~/.microsoft/usersecrets/{UserSecretsId}/secrets.json
Windows %APPDATA%\Microsoft\UserSecrets\{UserSecretsId}\secrets.json
Linux ~/.microsoft/usersecrets/{UserSecretsId}/secrets.json

In the above file paths, the {UserSecretsId} corresponds to the UserSecretsId value specified in the application’s .csproj file. For example, the path on a MacOS would look like this /.microsoft/usersecrets/aspnet-DonutShop-F20820B3-818A-4BF4-9852-802E04B9A12E/secrets.json.

Adding a Secret

To add a new secret, open a console window and run the following command from the directory in which the ASP.NET Core .csproj resides:

dotnet user-secrets set DonutShop:DatabasePwd BostonCreme

In the previous example, we added an application secret consisting of a key and its value. The DonutShop is defined as the object with a property named DatabasePwd and the property value BostonCreme.

Listing the Secrets

Using the example from above, view the contents of the secrets.json file.

Within your console, run the following command from the directory in which the ASP.NET Core .csproj resides:

dotnet user-secrets list 

The following output should appear in your console window:

DonutShop:DatabasePwd = BostonCreme

Removing a Secret

Now let’s move onto removing a secret from our project. Because we all practice good code hygiene, correct? We want to remove the code that is no longer in use. In this case, no longer used secrets.

First, let’s create a new secret.

Within your console run the following command from the directory in which the ASP.NET Core .csproj resides:

dotnet user-secrets set DonutShop:ApiKey ChocolateGlazed

Instead of running the command dotnet user-secrets list to view our secrets, let’s open the secrets.json file in a text editor and take a look at the contents. Reviewing the file, we can see the newly added secret:

{
  “DonutShop:DatabasePwd”: “BostonCreme”,
  “DonutShop:ApiKey”: “ChocolateGlazed”
}

Now, remove the ApiKey:

dotnet user-secrets remove DonutShop:ApiKey

Reviewing the secrets.json file again we see the DonutShop:ApiKey secret was removed:

{
  “DonutShop:DatabasePwd”: “BostonCreme”
}

If there is a scenario where all the application secrets need to be removed, run the following command:

dotnet user-secrets clear

The preceding command will remove all the application secrets from the secrets.json file.

Accessing the Secrets via Code

Now that we have discussed the console commands to manage our secrets, let’s take a look at how to use those application secrets within our code. First, we will look at how to retrieve a single secret directly from the secrets.json file. Then we will examine how to load the contents of the secrets.json file into an object.

Before we get started, lets setup our secrets.json file to look like the following:

{
  “DonutShop:DatabasePwd”: “BostonCreme”,
  “DonutShop:SecretIngredient”: “Chocolate”
}

Now that we have our secrets stored in the secrets.json file, we can construct our code to utilize the secrets. The Microsoft.Extensions.Configuration namespace provides access to the secrets.json file. Include the following namespace within your class:

using Microsoft.Extensions.Configuration;

Utilizing the Configuration API you can retrieve a secret directly from the secrets.json file like so:

public class DonutShopController : Controller
{
  public IConfiguration Configuration { get; }

  public DonutShopController(IConfiguration configuration)
  {
    Configuration = configuration;
  }

  public IActionResult Index()
  {
    if (env.IsDevelopment())
    {
      var secretIngredient = Configuration["DonutShop:SecretIngredient"];
    }
    // code snipped ....
  }
}

Two things to point out in the above controller example; the first being the public IConfiguration Configuration { get; } property implementation, which allows the code to use the Configuration API. The second line of code to point out is var secretIngredient = Configuration["DonutShop:SecretIngredient"];, this performs the retrieval of the SecretIngredient from the secrets file.

You can retrieve a single secret, like the previous example, or you can bind the secrets to an object that represents the related secrets. The following code examples will describe how to do this.

First, let’s create the model that will store our secrets. Create the following class within the Models directory of the project:

public class AppSecrets
{
  public string DatabasePassword { get; set; }
  public string SecretIngredient { get; set; }
}

Next, modify the Index action of DonutShopController from the previous example to reflect the following:

public IActionResult Index()
{
  if (env.IsDevelopment())
  {
    var appSecrets = Configuration.GetSection("DonutShop").Get<AppSecrets>();
    var secretIngredient = appSecrets.SecretIngredient;
    var databasePassword = appSecrets.DatabasePassword;
  }
  // code snipped ....
}

In the preceding example, the Configuration.GetSection("DonutShop").Get<AppSecrets>() line of code calls GetSection with the DonutShop key. Utilizing the IConfigurationSection Interface the DonutShop key values are bound to the AppSecrets object. Allowing us to utilize the AppSecrets object properties to access the application secrets.

Conclusion

Managing application development secrets is a critical aspect for development teams. Proper development of secret management allows us to safely and securely store secrets associated with our development projects. Ensuring only authorized individuals have access to the appropriate secrets.

As you can see from the examples in this post, the ASP.NET Core Secret Manager tool makes this process fairly easy with minimal effort. So go ahead and start moving those ASP.NET Core secrets to the Secret Manager tool. Happy coding!