Azure Key Vault and Azure App Service / Azure Function Apps are like cherries and cream - delicious individually, but so much better when you work with them together. The first of these services provides a secure mechanism for managing our passwords, secrets, encryption keys, and certificates within the cloud as part of a fully managed service. In contrast, the latter is our go-to tool for any software application or code that we want to run in the cloud where, again, we don’t want to concern ourselves with spinning up and managing any physical infrastructure. A common mechanism through which developers may use the services together is by having, for example, an application setting that links to the URL of a Key Vault secret. The Azure Resource Manager (RM) template snippet below demonstrates how we could do this. Because we’re leveraging Key Vault access policies and a system-assigned managed identity on our Function App, we can also ensure that only our Function App can see and work with the secret that we’ve defined:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"Parameters": {
"kv_MySecretValue": {
"type": "securestring",
"metadata": {
"description": "Value for the MySecret Key Vault Secret"
}
}
},
"Variables": {},
"resources": [
{
"apiVersion": "2019-08-01",
"type": "Microsoft.Web/sites",
"name": "MyFunctionApp",
"location": "[resourceGroup().location]",
"kind": "functionapp",
"Identity": {
"type": "SystemAssigned"
},
"properties": {
"siteConfig": {
"appSettings": [
{
"name": "AzureWebJobsStorage",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=mystorageaccount;EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', 'mystorageaccount'), '2019-06-01').keys[0].value)]"
},
{
"name": "AzureWebJobsDashboard",
"value": "[concat('DefaultEndpointsProtocol=https;AccountName=mystorageaccount;EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', 'mystorageaccount'), '2019-06-01').keys[0].value)]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~1"
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "~10"
},
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "dotnet"
},
{
"name": "MyKeyVaultSecretURL",
"value": "https://mykeyvault.vault.azure.net/secrets/MySecret?api-version=2016-10-01"
}
],
"httpsOnly": true
}
},
"resources": [],
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', 'mystorageaccount')]"
]
},
{
"type": "Microsoft.Storage/storageAccounts",
"name": "mystorageaccount",
"apiVersion": "2019-06-01",
"location": "[resourceGroup().location]",
"kind": "StorageV2",
"sku": {
"name": "Standard_LRS"
}
},
{
"type": "Microsoft.KeyVault/vaults",
"apiVersion": "2020-04-01-preview",
"name": "MyKeyVault",
"location": "[resourceGroup().location]",
"properties": {
"sku": {
"family": "A",
"name": "standard"
},
"tenantId": "[subscription().tenantId]",
"accessPolicies": [
{
"tenantId": "[subscription().tenantId]",
"objectId": "[reference(resourceId('Microsoft.Web/sites', 'MyFunctionApp'), '2019-08-01', 'full').identity.principalId]",
"permissions": {
"keys": [],
"secrets": [
"Get"
],
"certificates": [],
"storage": []
}
}
],
"enabledForDeployment": false,
"enabledForDiskEncryption": false,
"enabledForTemplateDeployment": false,
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"enableRbacAuthorization": false,
"vaultUri": "https://mykeyvault.vault.azure.net/"
},
"resources": [
{
"type": "Microsoft.KeyVault/vaults/secrets",
"apiVersion": "2020-04-01-preview",
"name": "MyKeyVault/MySecret",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', 'MyKeyVault')]"
],
"properties": {
"value": "[parameters('kv_MySecretValue')]",
"contentType": "MySecret Value",
"Attributes": {
"enabled": true,
"exp": 1681858800
}
}
}
],
"dependsOn": [
"[resourceId('Microsoft.Web/sites', 'MyFunctionApp')]"
]
}
],
"outputs": {}
}
From there, we can then reference the URL from within our code to retrieve our secret value. Pretty handy, right? But there’s a better way available to us to handle the retrieval action for us automatically. Enter stage left Key Vault References, a handy pseudo-function that we can leverage as part of any application setting to return the value of a secret we may be interested in, without any additional retrieval operation required. Using it is straightforward, and all we need to do is modify our appSetting value as indicated below:
{
"name": "MyKeyVaultSecretURL",
"value": "@Microsoft.KeyVault(VaultName=MyKeyVault;SecretName=MySecret)"
}
Once deployed, we can verify that the reference is working and resolving back our secret value by checking it in the portal:
Lovely Jubbly! One small thing I’ve seen with them “in the field” is that if you go in and update the value of your secret (i.e. create a new version), this sometimes does not immediately reflect itself. IÂ believe some process behind the scenes does a rotation on the App Service itself, but this will not occur straightaway after a deployment. Answers on a postcard if you know how to force this but, as a workaround, you can add in the secret version, which would look something like this:
@Microsoft.KeyVault(VaultName=MyKeyVault;SecretName=MySecret;SecretVersion=ec96f02080254f109c51a1f14cdb1931)
You can easily determine the current, valid secret version to use by checking the Key Vault in question:
As I said to a developer colleague not long ago, Key Vault References are perhaps the most useful thing I’ve ever learned from studying for an Azure exam. 🤣🤣 Hopefully, you’ll agree that they are a nifty feature that can save us some aggro as we work with these services in tandem.