Protecting Secrets using VSTS and Azure Key Vault

Context

 

If you are building a modern application and are following modern design principles, there is a good chance your application is composed of a number of layers and services.  Also, your services might be communicating with one or more databases to persist their data.  For services to communicate with one another and to be able to talk to databases, they need to leverage information that is considered sensitive.  Hackers know too well that this is one area that many teams don’t properly protect and they are always on the lookout to get hold of that information to leverage for attacks.  Sensitive information in this case include:

  • Database username and password or connection strings
  • API keys
  • Authentication (i.e. OAuth) tokens
  • Third party service username and password
  • Any other sensitive information your app might need

A common practice to handle this kind of information is to put it in a configuration file somewhere.  Although the file will be replaced when it goes to production, this still not recommended for at least the following reasons:

  • After the config file is checked in to the version control system, the sensitive information would be exposed
  • When information changes, a deploy might be required for the new changes to take effect
  • Secrets for production environment will need to be stored somewhere where they are properly protected. Sometimes that’s left to the judgement of the secrets maintainer, which might result in secrets being compromised if they are not kept in a secured location
  • Maintaining lifecycle of these secrets might not be easy since they might be scattered all over the place

In the remainder of this blog, I will detail an approach that not only help properly store and maintain the lifecycle of your secrets but also how your application can get access to those secrets without being exposed outside of the application.

 

Approach Overview

 

In this walkthrough, I will show how secrets can be securely stored in Azure using a capability provided by Azure called Azure Key Vault.  I will also show how those secrets can be accessed from an application.  The application I will use for this example would be a .NET MVC Web Application.  Finally, I will create a build in VSTS that would include a deploy step that will deploy the application and supply it with the sensitive information – in this case database connection string.

 

Enable your web app to communicate with Azure Key Vault

Create a sample project

In this example, we will use the application created by one of Visual Studio Enterprise 2015 templates.  Here are the steps to create a sample project:

  1. From Visual Studio go to File => New => Project
  2. Select Template => Web => NET Web Application (.NET Framework) and click Ok
  3. Select MVC and click Ok

Register app with Azure Active Directory

In  order for your web application to communicate with the Azure Key Vault, it will need to be registered with Azure Active directory.

Follow the steps described in this link to register your app with Azure Active Directory.  The relevant section is titled “Register an application with Azure Active Directory

 

Enable Application to Communicate with Azure Active Directory and Key Vault

To enable our sample application to communicate with Azure AD and Key Vault we will need to install two Nuget Packages:

  1. Microsoft.IdentityModel.Clients.ActiveDirectory
  2. Microsoft.Azure.KeyVault

 

You can install those by right clicking on your project an then Manage NuGet Packages or by using Install-Package command from Package Manager Console

Once done, open Web.config file and add the following keys

 <add key ="ClientId" value=""/>
<add key="ClientSecret" value="" />
<add key="SecretUri" value="" />

Because the database connection string will be retrieved from the Azure Key Vault, <connectionStrings> is no longer needed.  Go ahead and remove that section from Web.config file.

Create a Util folder in your project and create a class named KeyVaultHelper

 

 using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Threading.Tasks;
using System.Web.Configuration;

namespace WebApplication1.Util
{
   public class KeyVaultHelper{
      public static string MySecret { get; set; }
      public static async Task<string> GetToken(string authority, string resource, string scope){
         var authContext = new AuthenticationContext(authority);
         ClientCredential clientCred = new ClientCredential(WebConfigurationManager.AppSettings["ClientId"],
         WebConfigurationManager.AppSettings["ClientSecret"]);
         AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);

         if (result == null)
            throw new InvalidOperationException("Failed to obtain the JWT token");
         return result.AccessToken;
      }
   }
}

 

Open Global.asax.cs and add the following snippet at the bottom of Application_Start()

 

 var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(KeyVaultHelper.GetToken));
var task = kv.GetSecretAsync(WebConfigurationManager.AppSettings["connectionString"]);
task.Wait();
KeyVaultHelper.connectionStringSecret = task.Result.Value;

 

To read the connection string dynamically, open IdentityModels.cs inside Models folder and replace ApplicationDbContext() with this:

 

 public class ApplicationDbContext : IdentityDbContext
{
   public ApplicationDbContext()
      : base(dbConnectionString(), throwIfV1Schema: false)
   {

   }
   
   public static string dbConnectionString(){
      return KeyVaultHelper.EncryptSecret;
   }

   public static ApplicationDbContext Create()
   {
      return new ApplicationDbContext();
   }
}

 

Finally, we will need to add a PowerShell script that would populate the config values during deploy time.

  1. Right click on your solution the click Add => New Project => Cloud => Azure Resource Group and give it a name
  2. Right click on Scripts folder and select New Item then create a file called SetAzureWebsite.ps1 and set its content to the following:

 

 param (
   [string] $AzureWebsiteName,
   [string] $slot,
   [hashtable] $appsettings
)
Set-AzureWebsite -Name $AzureWebsiteName -Slot $slot -AppSettings $appsettings

You can remove anything else inside that project

 

To deploy the app in Azure, login to Azure and create the following components:

  • Web App
  • Database Server
  • Database

Using Key Vault to Store Secrets

 

In this section we will create an Azure Key Vault and create a secret called connectionString.  To create an Azure Key Vault:

  1. Navigate to https://portal.azure.com
  2. Create resource group called mydemoRG
  3. Go to mydemoRG and create Key Vault and give a unique name. In my case I named it
  4. Once created, click on it and go to Overview => Secrets => Add
  5. for upload option choose Manual and for name enter connectionstring and for value field set it to the connection string of the database created earlier . To obtain the connectionString from database created earlier, click on the database and then go to Overview and then click on Show database connection strings.   Make sure {your_username} and {your_password} are replaced with the actual values for your environment before copying to the value of the secret. Once done, you click create.
  6. Go back to the key vault you created and click Overview then Principals
  7. Click Add New
  8. In Select principal type the name you registered earlier with Azure Active Directory
  9. For Secret Permissions, check Get
  10. Click Ok and then Save

Build and Deploy app using VSTS

 

To build your app using VSTS, you will need to create a Team Project and then push your code to VSTS repository.  Once code is pushed, you can create a build using Visual Studio template with the following steps:

vsts

In Build Solution step, ensure that MSBuild Arguments field is set to:

 /p:PackageLocation="$(build.sourcesdirectory)" /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true

 

In Azure App Service Deploy, make sure that the step is pointing to a valid Azure subscription by properly setting Azure Subscription field.  App Service name should be set to the name of the web app created in the previous section.

In Azure PowerShell Script step make sure that Script Path field is pointing to SetAzureWebsite.ps1.   In Script Arguments field enter:

 -AzureWebsiteName yourwebappname -Slot production -appsettings @{"ClientId" = "your_client_id"; "ClientSecret" = "your_client_secret"; "connectionString" = "your_keyVault_connectionString_url"}

Where:

your_client_id and your_client_secret are the values you obtained when registering your application with Azure Active Directory

your_keyVault_connectionString_url can be obtained by going to the Key Vault we created earlier, then clicking on Secrets then connectionstring secret we created earlier then select current version and then Secret Identifier.   Here is an example of this value:"https://myappvault.vault.azure.net/secrets/connectionstring/b546a57aa8eb454f8713007063c2f12f">https://myappvault.vault.azure.net/secrets/connectionstring/b546a57aa8eb454f8713007063c2guid"

Once done, queue a new build.  When the build is done, navigate to the web app and ensure that application is running correctly.

You can find the sample app here: https://github.com/nzarrari/keyVaultSample

I hope this was helpful.  Please give us feedback and suggestions to improve this post.