Azure Key Vault is one of those services that must be included in every Azure solution. Having the devops and infrastructure-as-code principles in mind, Key Vault must be automatically provisioned during releases. This is where Key Vault comes with a caveat, of which I want to address a workaround in this blog post.
The caveat
Key Vault can be easily provisoned through an ARM template. This is an example of an ARM template for Key Vault:
[ { "apiVersion": "2016-10-01", "location": "[parameters('location')]", "name": "[variables('keyVaultName')]", "properties": { "enabledForDeployment": false, "enabledForDiskEncryption": false, "enabledForTemplateDeployment": true, "tenantId": "[subscription().tenantId]", "accessPolicies": [], "sku": { "name": "standard", "family": "A" } }, "tags": { "displayName": "Key Vault" }, "type": "Microsoft.KeyVault/vaults" } ]
The caveat is all about the accessPolicies property. Currently, this behaviour is observed:
- Although accessPolicies is considered an optional property, the exception “An invalid value was provided for ‘accessPolicies’” is thrown in case this property is missing.
- In case an empty array is provided for the accessPolicies property, all existing access policies get removed.
This means that one needs to be able to provide all access policies already at the creation time of the Key Vault, which is impossible, for sure if you’re leveraging Managed Service Identities.
The workaround
My first aim was to come up with a solution, purely within the ARM template, but this seemed impossible. Mostly because the reference function is not supported within ARM parameters and variables. My second – successful – attempt was to have a solution based on ARM template, combined with some PowerShell within an Azure DevOps release pipeline.
Azure PowerShell script
Add to your release pipeline an Azure PowerShell task, which executes the following script. This script takes the Key Vault Name as a parameter. It looks up any existing access policies on the Key Vault and transforms these access policies into their ARM JSON notation. The result is outputted to the Azure DevOps variable Infra.KeyVault.AccessPolicies.
param( [string][parameter(Mandatory = $true)] $keyVaultName ) $keyVaultAccessPolicies = (Get-AzureRMKeyVault -VaultName $keyVaultName).accessPolicies $armAccessPolicies = @() if($keyVaultAccessPolicies) { foreach($keyVaultAccessPolicy in $keyVaultAccessPolicies) { $armAccessPolicy = [pscustomobject]@{ tenantId = $keyVaultAccessPolicy.TenantId objectId = $keyVaultAccessPolicy.ObjectId } $armAccessPolicyPermissions = [pscustomobject]@{ keys = $keyVaultAccessPolicy.PermissionsToKeys secrets = $keyVaultAccessPolicy.PermissionsToSecrets certificates = $keyVaultAccessPolicy.PermissionsToCertificates storage = $keyVaultAccessPolicy.PermissionsToStorage } $armAccessPolicy | Add-Member -MemberType NoteProperty -Name permissions -Value $armAccessPolicyPermissions $armAccessPolicies += $armAccessPolicy } } $armAccessPoliciesParameter = [pscustomobject]@{ list = $armAccessPolicies } $armAccessPoliciesParameter = $armAccessPoliciesParameter | ConvertTo-Json -Depth 5 -Compress Write-Host ("##vso[task.setvariable variable=Infra.KeyVault.AccessPolicies;]$armAccessPoliciesParameter")
Key Vault ARM deployment
After the previous release task, deploy the Key Vault via an ARM deployment, using the following template:
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "accessPolicies": { "defaultValue": { "list": [] }, "type": "object" } }, "variables": { "keyVaultName": "mytest-keyVault" }, "resources": [ { "apiVersion": "2016-10-01", "location": "[resourceGroup().location]", "name": "[variables('keyVaultName')]", "properties": { "enabledForDeployment": false, "enabledForDiskEncryption": false, "enabledForTemplateDeployment": true, "tenantId": "[subscription().tenantId]", "accessPolicies": "[parameters('accessPolicies').list]", "sku": { "name": "standard", "family": "A" } }, "tags": { "displayName": "Key Vault" }, "type": "Microsoft.KeyVault/vaults" } ], "outputs": {} }
Remark that the access policies is feeded through an ARM parameter. This parameter can be set on the ARM deployment task, using the previously created Infra.KeyVault.AccessPolicies variable:
This ensures that all existing access policies are preserved in case the Key Vault gets redeployed.
Conclusion
With some plumbing, we managed to find a workaround for the undesired behaviour of Key Vault deployments. I hope the product group will address this issue in later releases and will make access policies a child resource of the Key Vault resource.
Cheers,
Toon