Securing serverless applications in Azure – Part 3/4 Store application secrets in Key Vault

This is the third in a four-part series of posts on securing serverless application in Azure using bicep. In this series, we take a look at how you can secure serverless Function Apps in Azure. We start with a sample Azure Function App, deploy it to Azure and then progressively enable each of these security features, validating along the way that our changes have been successful and our app is secure. We configure (nearly) all of this using Azure Bicep and the AZ CLI. If you’d like to skip to code it’s all available on GitHub here

All of the commands in this blog post are expected to be run using Powershell.

This blog post expects that you have completed the setup and configuration in parts 1 and 2.

Securing serverless applications in Azure – Part 1/4 Enable Azure AD authentication

Securing serverless applications in Azure – Part 2/4 Configure Managed Identity

Tip 3 – Store application secrets in Key Vault

Another thing you may want to consider doing to secure a serverless applications in Azure is to store application secrets in an Azure Key Vault. To do this you will need to deploy an Azure Key Vault, store application secrets in it and grant a Function App access to retrieve those secrets using Key Vault references.

Let’s jump right in and deploy a Key Vault through our bicep template. To start with we need to add an Azure Key Vault resource to our main.bicep.

resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' = {
  name: keyVaultName
  location: resourceGroup().location
  properties: {
    sku: {
      name: 'standard'
      family: 'A'
    }
    tenantId: subscription().tenantId
    enableRbacAuthorization: true
  }
}

We also need to define a new keyVaultName variable. Let’s do this at the top of the file under the storageAccountName variable.

var keyVaultName = 'secure${uniqueAppName}'

Now we need to grant our function app access to retrieve secrets from the Key Vault. To do this we will grant the Function App the Key Vault Secret User role. This role has a GUID value of 4633458b-17de-408a-b874-0445c86b69e6 which we can see in the Key Vault RBAC documentation here.

We need to create two new variables at the top of our main.bicep file. The first variable keyVaultSecretsUserRoleDefinitionGuid is the GUID of the Key Vault Secret User role and the second keyVaultSecretsUserRoleDefinitionId is a resource ID referencing the Key Vault Secret User role.

var keyVaultSecretsUserRoleDefinitionGuid = '4633458b-17de-408a-b874-0445c86b69e6'
var keyVaultSecretsUserRoleDefinitionId = '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${keyVaultSecretsUserRoleDefinitionGuid}'

Next we will add a role assignment resource granting the Function App the Key Vault Secret User for the Key Vault we are deploying. Place this resource below the Key Vault resource at the root level of the bicep template.

resource keyVaultRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = {
  name: guid(keyVault.id, keyVaultSecretsUserRoleDefinitionGuid, functionApp.name)
  scope: keyVault
  properties: {
    principalId: functionApp.identity.principalId
    principalType: 'ServicePrincipal'
    roleDefinitionId: keyVaultSecretsUserRoleDefinitionId
  }
}

Now let’s move some of our sensitive values from our Function App configuration to our Key Vault and update their values to Key Vault references.

Let’s start by creating a couple of new variables for the names of our Key Vault secrets. Place these with the other variables at the top of your bicep file.

var msProviderAuthSecretName = 'msProviderAuthSecret'
var storageAccountConnectionStringSecretName = 'storageAccountConnectionStringSecret'

Next let’s create Key Vault secret resources and assign these value. Place the following snippet inside your Key Vault resource definition.

resource msProviderAuthSecret 'secrets@2021-04-01-preview' = {
  name: msProviderAuthSecretName
  properties: {
    value: authClientSecret
  }
}

resource storageAccountConnectionStringSecret 'secrets@2021-04-01-preview' = {
  name: storageAccountConnectionStringSecretName
  properties: {
    value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${listKeys(storageAccount.id, '2019-06-01').keys[0].value}'
  }
}

Finally we need to update our Function App configuration with Key Vault references. This will enable the Function App to retrieve the values from the Key Vault at runtime. We need to make the following three updates in the appSettings resource definition.

MICROSOFT_PROVIDER_AUTHENTICATION_SECRET

MICROSOFT_PROVIDER_AUTHENTICATION_SECRET: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=${msProviderAuthSecretName})'


AzureWebJobsStorage

AzureWebJobsStorage: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=${storageAccountConnectionStringSecretName})'

WEBSITE_CONTENTAZUREFILECONNECTIONSTRING

WEBSITE_CONTENTAZUREFILECONNECTIONSTRING: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=${storageAccountConnectionStringSecretName})'

Note: in order for WEBSITE_CONTENTAZUREFILECONNECTIONSTRING to be a Key Vault reference we need to set an additional value in our configuration WEBSITE_SKIP_CONTENTSHARE_VALIDATION to 1.

We also need to ensure the content share is created in our Storage Account. Without this configuration value set and the content share created in our Storage Account our Function App will fail to start.

WEBSITE_SKIP_CONTENTSHARE_VALIDATION: '1'

We can now deploy our main.bicep file again to apply this configuration. We’ll be prompted for authClientId and authClientSecret values, these are the appId and password values respectively that were noted down earlier.

az deployment group create --resource-group secure-rg --template-file main.bicep --query properties.outputs

Now we can test our API directly from AZ CLI again. Replacing {appId} and {functionAppName} with their respective values.

$appId="{appId}"
$functionAppName="{functionAppName}"
az rest -m get --header "Accept=application/json" -u "https://$functionAppName.azurewebsites.net/api/TopFiveProducts" --resource "api://$appId"

You should be returned a JSON response with the top 5 products from the API.

Conclusion

In this blog post we looked at how you can store application secrets in Key Vault for an Azure Function App. Join me in the next blog post of this series where we look at how to deploy Private Endpoints for your Azure resources.

Read more recent blogs

Get started on the right path to cloud success today. Our Crew are standing by to answer your questions and get you up and running.