Kirk Evans Blog

.NET From a Markup Perspective

Using Azure KeyVault to Store Secrets

This post will show a very simple KeyVault demo application.

Background

Working with a customer, I needed to show them the simplest Azure KeyVault sample possible so they could easily understand what it does.  There is a really good sample published to GitHub (Azure KeyVault .NET Sample Code),  but it was still quite a bit verbose for someone seeking a simple Hello World type example.  I threw this Console application together to show the bare minimum application.

The scenario is an application that interacts with multiple Azure services such as Redis, DocumentDB, and Storage.  Each service has its own key to access the service.  We obviously need our application to access the keys, but we don’t want developers to have access to the keys.  We also want the ability to centrally update the keys on a regular basis.  This is a perfect scenario for KeyVault because you can restrict access to keys and secrets. 

To show this, I have a storage account named “kirkedemo”.  Once the account is created, I can access its keys.

image

I copied the key1 value for my storage account and will store that as a secret in KeyVault. 

The source code for this project is available at https://github.com/kaevans/keyvaultdemo.

Create a Vault and Secret

I often show people how to start using the portal because it provides a nice visual way to get started.  Of course you can do this using scripting and ARM templates, and you should do that for production to ensure consistent provisioning with no configuration drift.  I created a vault and named it “demo”.  Once the vault is created, I click on Secrets in the portal and create a new secret using the storage account key.

image

In order to secure access for my application, I need to create a service principal in Azure AD.

Register the Application in Azure AD

In the Azure AD blade, select App Registrations and click the Add button to register a new application.  Give it a name, select “Native” as the application type, and provide a URL (doesn’t have to be a real endpoint, just a URL).

image

Once created, click on your application, view Settings, and click Properties.  Copy the Application ID value, this will be the clientID value used to authenticate to KeyVault.

image

Now click Keys.  Create a new key and click Save, then copy the value that was generated.

image

The key you copied will be the clientSecret value used to authenticate to KeyVault. 

Important step! Registering an application using the Azure portal doesn’t (at the time of this writing) create a service principal.  Open PowerShell and run the following commands, replacing the ApplicationID value with the value you just created.

Create Service Principal
  1. Login-AzureRmAccount
  2. New-AzureRmADServicePrincipal -ApplicationId d80cc6d4-6037-49c5-9c3b-8b304626f3ee


Create the Project

In Visual Studio, create a new Console application.  Add the following NuGet packages:

  • Microsoft.Azure.KeyVault
  • Microsoft.IdentityModel.Clients.ActiveDirectory
  • WindowsAzure.Storage

Add a reference to System.Configuration.

Edit the app.config file and add the following keys along with the values that you created above.

appSettings
  1. <appSettings>    
  2.     <add key="VaultUrl" value="URL to your vault (ex: https://demo.vault.azure.net/)" />
  3.     <add key="storageAccountName" value="Storage account name (ex: kirkedemo)" />
  4.     <add key="clientId" value="Application ID from Azure AD (ex: d80cc6d4-6037-49c5-9c3b-8b304626f3ee)" />
  5.     <add key="clientSecret" value="Generated key from Azure AD (ex: Ova+O90sdfk8435908YKIKGF48395jhdfg=" />
  6.   </appSettings>

Here is my configuration file for reference.

image

Show Me the Code

My favorite part.  If you don’t want to read and just want a copy, the source is available at https://github.com/kaevans/keyvaultdemo.

KeyVault Sample
  1. using Microsoft.Azure.KeyVault;
  2. using Microsoft.IdentityModel.Clients.ActiveDirectory;
  3. using Microsoft.WindowsAzure.Storage;
  4. using Microsoft.WindowsAzure.Storage.Auth;
  5. using Microsoft.WindowsAzure.Storage.Queue;
  6. using System;
  7. using System.Configuration;
  8. using System.Threading.Tasks;
  9.  
  10. namespace mykeyvault
  11. {
  12.     class Program
  13.     {
  14.         
  15.  
  16.         static void Main(string[] args)
  17.         {
  18.             MainAsync(args).GetAwaiter().GetResult();
  19.         }
  20.  
  21.         private static async Task MainAsync(string[] args)
  22.         {
  23.             //Get the storage key as a secret in KeyVault
  24.             var storageKey = await GetStorageKey();
  25.             string storageAccountName = ConfigurationManager.AppSettings["storageAccountName"];
  26.             var creds = new StorageCredentials(storageAccountName, storageKey);
  27.             var storageAccount = new CloudStorageAccount(creds, true);
  28.             var queueClient = storageAccount.CreateCloudQueueClient();
  29.             var queue = queueClient.GetQueueReference("samplequeue");
  30.             await queue.CreateIfNotExistsAsync();
  31.             await queue.AddMessageAsync(new CloudQueueMessage("Hello keyvault"));
  32.         }
  33.  
  34.         private static async Task<string> GetStorageKey()
  35.         {
  36.  
  37.             var client = new KeyVaultClient(
  38.                 new KeyVaultClient.AuthenticationCallback(GetAccessTokenAsync),
  39.                 new System.Net.Http.HttpClient());
  40.  
  41.             var vaultUrl = ConfigurationManager.AppSettings["vaultUrl"];
  42.  
  43.             var secret = await client.GetSecretAsync(vaultUrl, "storageAccountKey");
  44.  
  45.             return secret.Value;
  46.         }
  47.  
  48.         
  49.         private static async Task<string> GetAccessTokenAsync(
  50.             string authority,
  51.             string resource,
  52.             string scope)
  53.         {
  54.             //clientID and clientSecret are obtained by registering
  55.             //the application in Azure AD
  56.             var clientId = ConfigurationManager.AppSettings["clientId"];
  57.             var clientSecret = ConfigurationManager.AppSettings["clientSecret"];
  58.  
  59.             var clientCredential = new ClientCredential(
  60.                 clientId,
  61.                 clientSecret);
  62.  
  63.             var context = new AuthenticationContext(
  64.                 authority,
  65.                 TokenCache.DefaultShared);
  66.  
  67.             var result = await context.AcquireTokenAsync(
  68.                 resource,
  69.                 clientCredential);
  70.  
  71.             return result.AccessToken;
  72.         }       
  73.     }
  74. }


The one weird thing about this code was the KeyVaultClient.AuthenticationCallback function, it expects a function with three string values.  The authority is the Azure AD instance that you are working with (public cloud, govt cloud, Germany cloud, etc) plus your tenant ID, and the resource parameter has the value “https://vault.azure.net”.  You don’t provide those values, the callback function provides them.  You only need to provide the clientID and clientSecret values and the SDK does the rest for you. 

image

Now try hitting F5 to make sure it compiles and runs.

See It In Action

“Huh? I did everything you said, Kirk, and when I hit F5 I get an Access Denied exception from KeyVault!”  Good!  That means it’s working.  We created a secret in KeyVault, and created an application in Azure AD, but we never told KeyVault that the application was allowed to access the secret. 

Go back to your KeyVault in the portal and click Access policies.  Your application is not listed.  Click Add new to add your application. 

image

Note: if you don’t see your application, make sure to run the New-AzureRmADServicePrincipal cmdlet as mentioned above.

Once we’ve selected the principal, we then select the Secret permissions.  We will only allow our application get a secret, it cannot list, set, or delete a secret in the KeyVault.

image

Save your access policy.  Now go run the application again.  This time everything should work.  Go back to your storage account, and you should now have a new queue.

image

What Just Happened?

We were able to create an application that stores secrets in KeyVault.  An administrator would have the ability to set access policies for users and applications.  For example, I have a user, “kirkevans@blueskyabove.onmicrosoft.com”, I can set a policy for that user for keys and secrets.

image

If I log in as that user and try to view the secrets in my KeyVault, I will see a message telling me that I am not authorized to view the secrets in the vault.

image

This lets administrators configure which applications and users are able to work with secrets in the vault.  Users or applications that have the ability to Set secrets can now update secret values, such as updating the storage account key. 

The obvious point that someone will mention is that we have a client ID and client secret in our app.config, that is a secret that is not stored in KeyVault.  Great point!  What we could have done was to have a *user* log in, use the user’s identity to obtain the key from the keyvault.  Another option would be to use a client certificate for the application’s credentials instead of a client ID and client secret.  This is exactly what the demo at Azure KeyVault .NET Sample Code does, it uses a client certificate to authenticate the application. 

To see how to perform the operations using PowerShell instead of going to the Azure portal, see https://azure.microsoft.com/en-us/documentation/articles/key-vault-get-started/

For More Information

Azure KeyVault .NET Sample Code

Get started with Azure Key Vault

https://github.com/kaevans/keyvaultdemo