Reading secrets from KeyVault in your Azure Web App

Azure KeyVault WebAppAzure KeyVault is a great Azure offerring that allows you to store for example secrets or certificates. You are currently looking at the first post out of a series of posts on how to grab secrets or certificates from KeyVault in your web applications. This post wil focus on creating the KeyVault, giving access to you Azure Web App and retrieving a secret in two different ways. I will be using an ASP.NET full framework app in this demo but this will work for .NET Core as well.

Create the KeyVault and Web App using an ARM template When you want to create a resource in Azure there are multiple ways to do that. I prefer ARM templates over the portal or the Azure CLI. So, in this post you will see ARM templates. Our web app will authenticate to KeyVault using a managed Identity that we will enable in the template.

First, we need a KeyVault to store our secrets. Here’s the template to create a KeyVault and add a secret to it named ‘secret’.

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "keyVaultName": {
      "type": "string"
    },
    "AzureAdTenantId": {
      "type": "string"
    }
  },
  "variables": {},
  "resources": [
    {
      "type": "Microsoft.KeyVault/vaults",
      "apiVersion": "2016-10-01",
      "name": "[parameters('keyVaultName')]",
      "location": "[resourceGroup().location]",
      "properties": {
        "sku": {
          "family": "A",
          "name": "standard"
        },
        "accessPolicies": [],
        "tenantId": "[parameters('AzureAdTenantId')]",
        "enabledForDeployment": false,
        "enabledForDiskEncryption": false,
        "enabledForTemplateDeployment": true
      }
    },
    {
      "type": "Microsoft.KeyVault/vaults/secrets",
      "apiVersion": "2016-10-01",
      "name": "[concat(parameters('keyVaultName'), '/secret')]",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
      ],
      "properties": {
        "attributes": {
          "enabled": true
        },
        "value": "super secret thingy"
      }
    }
  ]
}

Now we need to deploy our web app. Here’s the template to do that. It will create the web app and enable the system-assigned managed identity. To allow this app access to the secrets in KeyVault, we will need to assign an access policy. That is done in the last resource in this template.

{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "hostingPlanName": {
      "type": "string",
      "minLength": 1
    },
    "skuName": {
      "type": "string",
      "defaultValue": "S1",
      "allowedValues": [
        "F1",
        "D1",
        "B1",
        "B2",
        "B3",
        "S1",
        "S2",
        "S3",
        "P1",
        "P2",
        "P3",
        "P4"
      ],
      "metadata": {
        "description": "Describes plan's pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/"
      }
    },
    "skuCapacity": {
      "type": "int",
      "defaultValue": 1,
      "minValue": 1,
      "metadata": {
        "description": "Describes plan's instance count"
      }
    },
    "webSiteName": {
      "type": "string"
    },
    "azureAdTenantId": {
      "type": "string"
    },
    "keyVaultName": {
      "type": "string"
    }
  },
  "variables": { },
  "resources": [
    {
      "apiVersion": "2015-08-01",
      "name": "[parameters('hostingPlanName')]",
      "type": "Microsoft.Web/serverfarms",
      "location": "[resourceGroup().location]",
      "sku": {
        "name": "[parameters('skuName')]",
        "capacity": "[parameters('skuCapacity')]"
      },
      "properties": {
        "name": "[parameters('hostingPlanName')]"
      }
    },
    {
      "apiVersion": "2015-08-01",
      "name": "[parameters('webSiteName')]",
      "type": "Microsoft.Web/sites",
      "location": "[resourceGroup().location]",
      "identity": {
        "type": "SystemAssigned"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]"
      ],
      "properties": {
        "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
      },
      "resources": [ ]
    },
    {
      "type": "Microsoft.KeyVault/vaults/accessPolicies",
      "name": "[concat(parameters('keyVaultName'), '/add')]",
      "apiVersion": "2018-02-14",
      "properties": {
        "accessPolicies": [
          {
            "tenantId": "[parameters('azureAdTenantId')]",
            "objectId": "[reference(concat('Microsoft.Web/Sites/', parameters('webSiteName')), '2018-11-01', 'Full').identity.principalId]",
            "permissions": {
              "keys": [],
              "secrets": [
                "Get",
                "List"
              ],
              "certificates": []
            }
          }
        ]
      }
    }
  ]
}

Reading a secret in your app

There are multiple ways to read a secret from KeyVault in your app. I’ll show you two.

Code

The first one will use a few lines of code to read the secret we created earlier. This might come in handy when you have your own way of loading configuration instead of the default app settings in the web.config. Here is an example:

AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
 
var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
 
var secret = await keyVaultClient.GetSecretAsync("https://MyDemoVault.vault.azure.net/secrets/secret").ConfigureAwait(false);
 
Console.WriteLine("Your secret: {0}", secret);

To connect with the KeyVault, this code will use the ‘AzureServiceTokenProvider’ to get you a valid access token. When you run this code in your Azure Web App, it will use the apps Managed Identity. When you debug locally, you need to be signed in with your Azure subscription. Use ‘Az login’ to do so. For this to work you must make sure that your account has the ‘Get’ and ‘List’ access policy on the KeyVault like we assigned to our Managed Identity in the ARM template.

KeyVault Config Builder

Since version 4.7.1 of the .NET Framework and .NET Core 2.0 you have the ability to use config builders. This is a mechanism to get config from external resources. One of the available config builder is the one for KeyVault.

We first need to install a NuGet package:

Install-Package Microsoft.Configuration.ConfigurationBuilders.Azure

Now we need to add the following section to our web.config above your app settings.

<configSections>
    <section name="configBuilders" type="System.Configuration.ConfigurationBuildersSection, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" restartOnExternalChanges="false" requirePermission="false" />
  </configSections>
<configBuilders>
    <builders>
        <add name="Secrets" type="Microsoft.Configuration.ConfigurationBuilders.AzureKeyVaultConfigBuilder, Microsoft.Configuration.ConfigurationBuilders.Azure, Version=1.0.0.0, Culture=neutral"
 vaultName="MyDemoVault" />
    </builders>
</configBuilders>

The last step is to edit your appSettings to look like mine; add ‘configBuilders=”Secrets”‘ and add the name of your secret with a dummy value:

<appSettings configBuilders="Secrets">
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="secret" value="" />
</appSettings>

Now read this setting like it was a normal setting you would read from your appSettings:

var secret = ConfigurationManager.AppSettings["secret"];