Key Vault deployment removes access policies

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.

arm2

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:

ARM1

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

ABOUT

MEET THE YOUR AZURE COACH TEAM

Your Azure Coach is specialized in organizing Azure trainings that are infused with real-life experience. All our coaches are active consultants, who are very passionate and who love to share their Azure expertise with you.