Securing Azure Web Job Secrets with Azure Key Vault

12/01/2015

Last week the Azure Key Vault went into preview. The Azure Key Vault is a service for securely saving passwords and certificate for use in your applications. By using Azure Key Vault, you can avoid having e.g. username and passwords written directly in your configuration files, that way you can ensure that this type of information is not available to developers but only to people in operation. Azure Key Vault will also some time in future provide an audit log, so you can monitor who is accessing it. With Azure Key Vault also possible to gain access to Hardware security modules (HSM), but if you want to play around with that you will pay around 50 cent per key, whereas the software keys/secrets are billed per 10000 REST operation you perform against the service.

Enough chit chat, lets see how we secure a password we indend to use in a Azure Web Job. I have choosen this example as it will lead us through many of the principles of the Key Vault, and I feel that web jobs are particular prone to bad security habits – after all they are just console applications :).

Create a new Key Vault

For the moment there’s no GUI to mange the Key Vault in any of the Azure portals, so you are stuck with the REST API or the PowerShell commandlets. I went with PowerShell, so first step is to make sure you are on Azure PowerShell released on January the 8th 2015, you can check this in the Web Platform installer.
webplatforminstaller

After that you need to download the Azure Key Vault commandlets, you can grab them from here http://go.microsoft.com/fwlink/?linkid=521539, be sure to unblock the file.

I extracted the files to C:\Azure Key Vault Powershell scripts\KeyVaultManager, so in order to import them type the following in the Azure Powershell:
[powershell]
import-module ‘C:\Azure Key Vault Powershell scripts\KeyVaultManager’
[/powershell]

Now the Key Vault commands only work in the Azure Resource Manager mode, so when the modules are loaded, switch mode with
[powershell]
Switch-AzureMode AzureResourceManager
[/powershell]

Now you are ready to create you Key Vault. You can have several key vaults in your subscription, a key vault act as a container that can hold multiple certificates/keys or secrets. If you are using resource groups it would probably make sense with a Key Vault per resource group, especially since there’s no cost associated with having the vault.

You create a new key vault with
[powershell]
New-AzureKeyVault -VaultName ‘sjkpvault’ -ResourceGroupName ‘sjkpresources’ -Location ‘North Europe’
[/powershell]
If you specify a none existing resource group it will be created.
In case you are not logged in, you might have to run Add-AzureAccount.

When the process is complete you should see something like this
newkeyvault
A few things are worth noticing here. The vaultUri in my case https://sjkpvault.vault.azure.net/ should be noted down, as we will need it when we configure the Azure Web Job. Also taking a look at the accessPolicies is interesting. What we can see is that some objectId d28caac4-7ba6-43e1-b529-5aca672878dc in my tenantId 37597dd5-5816-4d7a-99e8-b2e6c3f4d0c1 have been granted the all permission to secrets and a bounch of different permissions to the keys. If you run
[powershell]
Get-AzureADUser -ObjectId d28caac4-7ba6-43e1-b529-5aca672878dc
[/powershell]
You will see that the object that have been granted full control to the key vault is my user, so that should be okay. We will get back to granting permissions to the Key Vault in a later section.

Adding Keys/Secrets to the Vault

So now we have a key vault, but we need to add something highly valuable to it (right Sony :)). The vault can contain either keys or secrets. Keys are certificates and secrets can be any small piece of information that you want to keep secure, most likely passwords or AccessTokens to e.g. other Azure Services like storage accounts.

For our purpose we want to store a password, we do that by the following two powershell commands.
[powershell]
$secret = ConvertTo-SecureString ‘MySecretSauce’ -AsPlainText -Force
Set-AzureKeyVaultSecret -VaultName sjkpvault -Name MyPassword -SecretValue $secret
[/powershell]
The second command will return a few things, the most important here is the Id (https://sjkpvault.vault.azure.net/secrets/MyPassword/94fd5e8fe2eb447abc6be515e1e9d08c) which really is an REST endpoint where the current version of MyPassword is stored. The version of my password is indicated by the guid.

Establish a Secure way to Obtain the Password from the Key Vault

Okay, so far all we have done is to store a password somewhere in the cloud. Without being able to access it in a secure way from the Web Job it is pretty much useless.

Luckily the Azure Key Vault Team, wrote a client library for working with the Key Vault REST endpoints that we can use to obtain the password from the Vault in our Web Job. The library can be downloaded here http://www.microsoft.com/en-us/download/details.aspx?id=45343. I would assume that it sometime in the near feature will be turned into a Nuget package, but so far I haven’t been able to find it. So you have to download the zip and extract it and pull the following two projects out.

keyvault-vsproject.
I did have some trouble building the projects because of some Nuget targets in the csproj files, but I just removed that and then it worked for me.

The client library supports two methods for getting a key or a secret for a Vault. Both methods involve an Azure AD application. The reason why an Azure AD application is needed is that we configure the permission on our Key Vault to some object in Azure AD (either user or application). Then our Azure Web Job (or any other program), obtains an access token from the Azure AD application that we can pass along with our requests to the Key Vault, which then knows that we are infact who we claim to be, and thus will give us access.

I feel that the second approach is better, because it is common to keep the certificates out of reach for application developers. I will explain how to use that approach (it’s also slightly more complicated).

First we need to generate a certificate (or obtain one somehow). You can use openssl as explained in my other blog posts here. Or just use the commands provided in the readme for the client library.

openssl genrsa -des3 -out keyvault.key 2048
openssl req -new -key keyvault.key -out keyvault.csr
openssl x509 -req -days 3000 -in keyvault.csr -signkey keyvault.key -out keyvault.crt
openssl pkcs12 -export -out keyvault.pfx -inkey keyvault.key -in keyvault.crt

Each line is a separate command.

Once we have a certificate it is time to register an Azure AD Application, we must turn to Powershell when we are taking the certificate route, if you were doing the secret you could do it from the portal, in that case you have to fill out the redirectUrls with dummy data.

Before we try to create the Azure AD App, we should run Connect-AzureAD to select the Azure AD you want to create the App in.

Then we can create it with
[powershell]
$adapp = New-AzureADApplication -DisplayName sjkpvaultapp
[/powershell]
And to upload the certificate for authenticating with the app we can run
[powershell]
Add-AzureADApplicationCredential -ObjectId $adapp.objectId -FilePath C:\openssl\keyvault.crt
[/powershell]
I stored my certificate in C:\openssl.

Now our Azure AD App is ready to be consumed from our Azure Web Job. One final thing we have to do before we turn into Visual Studio is to grant our Azure AD App access to the Key Vault. We do that with the following command
[powershell]
Set-AzureKeyVaultAccessPolicy -VaultName sjkpvault –ServicePrincipalName $adapp.appId -PermissionsToSecrets get
[/powershell]
Here we specifically grant only the get access to our Azure AD App. If you were using the Secret method instead and you created the AD App from the portal, then the $adapp.appId should be replaced with the ClientId as listed in the portal.

Getting a Key Vault Password from Azure Web Job

Now we are finally at where we want to be. Everything is setup, and we are ready to consume the password from our Azure Web Job.

I created a project that I placed on github, that shows all the details, most of the code is copied form the samples provided by the Azure Key Vault team.

My Azure Web Job program looks like this, it’s installed as an ondemand webjob and as you can see it does nothing except loggin that we are able to obtain the password.
[csharp]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using SampleKeyVaultClientWebRole;
using System.Configuration;

namespace SJKP.KeyVault.AzureWebJob
{
class Program
{
// Please set the following connection strings in app.config for this WebJob to run:
// AzureWebJobsDashboard and AzureWebJobsStorage
static void Main()
{
Console.WriteLine("Hello from job");
var certThumbprint = ConfigurationManager.AppSettings[Constants.KeyVaultAuthCertThumbprintSetting];

Console.WriteLine("Cert thumbprint: " + certThumbprint);

var cert = CertificateHelper.FindCertificateByThumbprint(certThumbprint);
if (cert != null)
Console.WriteLine("Cert found");

//TODO the secret URL should go into appsettings too.
Console.WriteLine("Secret was: " + KeyVaultAccessor.GetSecret("https://schdovault.vault.azure.net/secrets/Password/1a3816985d5f40bf9abafe17a970c10c"));
}
}
}
[/csharp]

It uses a few helper classes, firstly the CertificateHelper that enables us to get the certificate from the Azure Website.
[csharp]
public static class CertificateHelper
{
public static X509Certificate2 FindCertificateByThumbprint(string findValue)
{
//Use currentUser as this is were certificates are stored in AzureWebSites
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
try
{
store.Open(OpenFlags.ReadOnly);
// Don’t validate certs, since the test root isn’t installed.
X509Certificate2Collection col = store.Certificates.Find(X509FindType.FindByThumbprint, findValue, false);
if (col == null || col.Count == 0)
return null;
return col[0];
}
finally
{
store.Close();
}
}
}
[/csharp]
In order to access a certificate from an azure website/azure web job, it must first be uploaded to the Configure tab in the portal, and then a reference to the certificate thumbprint must added under appsetting, otherwise the certificate won’t be loaded into the CurrentUser store.
upload-certificate
azure-website-certificate-appsetting.

Secondly I have reused the KeyVaultAccessor from the sample code, that helps me authenticate and get the secret from the KeyVault.
[csharp]
/// <summary>
/// This class uses Microsoft.KeyVault.Client library to call into Key Vault and retrieve a secret.
///
/// Authentication when calling Key Vault is done through the configured X509 ceritifcate.
/// </summary>
public class KeyVaultAccessor
{
private static KeyVaultClient keyVaultClient;
private static X509Certificate2 clientAssertionCertPfx;
static KeyVaultAccessor()
{
keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessToken));
clientAssertionCertPfx = CertificateHelper.FindCertificateByThumbprint(ConfigurationManager.AppSettings[Constants.KeyVaultAuthCertThumbprintSetting]);
}

/// <summary>
/// Get a secret from Key Vault
/// </summary>
/// <param name="secretId">ID of the secret</param>
/// <returns>secret value</returns>
public static string GetSecret(string secretId)
{
var secret = keyVaultClient.GetSecretAsync(secretId).Result;
return secret.Value;
}

/// <summary>
/// Authentication callback that gets a token using the X509 certificate
/// </summary>
/// <param name="authority">Address of the authority</param>
/// <param name="resource">Identifier of the target resource that is the recipient of the requested token</param>
/// <param name="scope">Scope</param>
/// <returns></returns>
public static string GetAccessToken(string authority, string resource, string scope)
{
var client_id = ConfigurationManager.AppSettings[Constants.KeyVaultAuthClientIdSetting];

var context = new AuthenticationContext(authority, null);

var assertionCert = new ClientAssertionCertificate(client_id, clientAssertionCertPfx);

var result = context.AcquireToken(resource, assertionCert);

return result.AccessToken;
}

}
[/csharp]

In my app.config I have added the following two app settings that links together our certificate with our Azure AD application.
[xml]
<appSettings>
<add key="KeyVaultAuthClientId" value="b71d3745-65e3-4ca0-bca9-1f5c09a16305" />
<add key="KeyVaultAuthCertThumbprint" value="e8629a11ea8e5c2174381eabb70d31adef45c97a" />
</appSettings>
[/xml]

With all this configured and the web job published to an Azure Web Site, you get the following nice green screen, when the job is run
azure-web-job-key-vault

As we can see the jobs logs my password: MySecretSauce so everything is to be working!