Introduction
Let’s take the Qualys Vulnerability Management data connector as an example.
Reviewing the template we find that it doesn’t set up a key vault - it just stores variables, such as the workspaceKey
, apiUserName
and apiPassword
in the configuration of the Azure Function.
Adding a key vault is set up as an optional step, one that perhaps many people will miss. Just below the text on the image it links to some guidance that can help you add a key vault - I recommend giving it a read.
In this post we will go through all the steps required to add a key vault to a data connector on an actual data connector template and end up with something you could deploy yourself.
Adding the Key Vault
We will add our key vault at the bottom, below the last resource block.
For the key vault we will add three values into the keyvault:
workspaceKey
APIUsername
APIPassword
The resource block for the key vault itself will look like this:
{
"type": "Microsoft.KeyVault/vaults",
"apiVersion": "2022-07-01",
"name": "[variables('FunctionName')]",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('FunctionName'))]"
],
"properties": {
"accessPolicies": [
{
"objectId": "[reference(resourceId('Microsoft.Web/sites', variables('FunctionName')), '2019-08-01', 'full').identity.principalId]",
"permissions": {
"secrets": [ "get",
"list"
]
},
"tenantId": "[subscription().tenantId]"
}
],
"enabledForDeployment": "false",
"enabledForDiskEncryption": "false",
"enabledForTemplateDeployment": "true",
"enableSoftDelete": "true",
"sku": {
"family": "A",
"name": "Standard"
},
"tenantId": "[subscription().tenantId]",
}
}
This is as basic as it probably can get for key vaults, it creates a key vault dependant on the Microsoft.Web/sites
created earlier.
It also adds an access policy to the Managed Identity of the Azure Function itself, so that it can retrieve the secrets stored in the key vault. For this to work we just need to make sure that the following is set under the Microsoft.Web/sites
resource block:
"identity": {
"type": "SystemAssigned"
},
Adding secrets to the vault
Moving on, we need to add the secrets themselves. Secret blocks are added as resources under the key vault block:
...
},
"tenantId": "[subscription().tenantId]",
},
"resources": [
{
"type": "Microsoft.KeyVault/vaults/secrets",
"apiVersion": "2021-04-01-preview",
"name": "[variables('workspaceKey')]",
"properties": {
"value": "[parameters('workspaceKey')]"
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
]
},
{
"type": "Microsoft.KeyVault/vaults/secrets",
"apiVersion": "2021-04-01-preview",
"name": "[variables('APIUsername')]",
"properties": {
"value": "[parameters('APIUsername')]"
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
]
},
{
"type": "Microsoft.KeyVault/vaults/secrets",
"apiVersion": "2021-04-01-preview",
"name": "[variables('APIPassword')]",
"properties": {
"value": "[parameters('APIPassword')]"
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
]
}
]
Adding secrets to the function app
First, we need to add dependencies to the config
resource block under the Microsoft.Web/sites
block:
"dependsOn": [
"[concat('Microsoft.Web/sites/', variables('FunctionName'))]",
"[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]",
"[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), variables('workspaceKey'))]",
"[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), variables('APIUsername'))]",
"[resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), variables('APIPassword'))]"
],
Next step is change the static variables of the parameters we chose into dynamic key vault references.
This is how it currently looks:
"workspaceKey": "[parameters('WorkspaceKey')]",
"apiUsername": "[parameters('APIUsername')]",
"apiPassword": "[parameters('APIPassword')]",
When referencing key vault secrets in ARM-templates, we can use the following syntax:
"workspaceKey": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('WorkspaceKey')).secretUriWithVersion, ')')]",
"apiUsername": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('APIUsername')).secretUriWithVersion, ')')]",
"apiPassword": "[concat('@Microsoft.KeyVault(SecretUri=', reference(variables('APIPassword')).secretUriWithVersion, ')')]",
We also need to add the variable KeyVaultName
, which uses the function name to create a name for the key vault in line with the other naming schemes:
"variables": {
"KeyVaultName": "[concat(substring(variables('FunctionName'), 0, 20), 'kv')]"
}
Modifying input from string to securestring
Modyfing the input parameters to be SecureString
in place of String
will give the same result, but the input will be shielded and the value of the parameter won’t be saved to deployment history or logged.
We’ll modify two of the three parameters we put into the key vault:
workspaceKey
APIPassword
"WorkspaceKey": {
"type": "securestring",
"defaultValue": "<workspaceKey>"
},
"APIUsername": {
"type": "string",
"defaultValue": "<apiUsername>"
},
"APIPassword": {
"type": "securestring",
"defaultValue": "<apiPassword>"
},
Full key vault resource block
{
"type": "Microsoft.KeyVault/vaults",
"apiVersion": "2022-07-01",
"name": "[variables('FunctionName')]",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/sites', variables('FunctionName'))]"
],
"properties": {
"accessPolicies": [
{
"objectId": "[reference(resourceId('Microsoft.Web/sites', variables('FunctionName')), '2019-08-01', 'full').identity.principalId]",
"permissions": {
"secrets": [ "get",
"list"
]
},
"tenantId": "[subscription().tenantId]"
}
],
"enabledForDeployment": "false",
"enabledForDiskEncryption": "false",
"enabledForTemplateDeployment": "true",
"enableSoftDelete": "true",
"sku": {
"family": "A",
"name": "Standard"
},
"tenantId": "[subscription().tenantId]"
},
"resources": [
{
"type": "secrets",
"apiVersion": "2021-04-01-preview",
"name": "[variables('workspaceKey')]",
"properties": {
"value": "[parameters('workspaceKey')]"
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
]
},
{
"type": "secrets",
"apiVersion": "2021-04-01-preview",
"name": "[variables('APIUsername')]",
"properties": {
"value": "[parameters('APIUsername')]"
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
]
},
{
"type": "secrets",
"apiVersion": "2021-04-01-preview",
"name": "[variables('APIPassword')]",
"properties": {
"value": "[parameters('APIPassword')]"
},
"dependsOn": [
"[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
]
}
]
}
Putting it all together
Using this resource block you can add a key vault to any function app deployment. The steps you will need to take to make sure this works is:
- Add the block
- Make sure the
Microsoft.Web/sites
block has a Managed Identity configured - Add dependencies for the key vault and secrets to the
config
block of theMicrosoft.Web/sites
block - Change the references to the variables you wish to add to the key vault in the
config
block - Add the
KeyVaultName
-variable - [Optional] Change input type from
String
toSecureString
for all applicable parameters
Full template
The full template can be found here, on my Github.