Azure ARM templates: Conditionally add an access policy to a Key Vault

Putting time and effort into developing Azure ARM templates for infrastructure deployment pays off in the long term, especially when all teams across an organisation adopt the same patterns. Recently at DrDoctor we’ve been developing an approach to generating boilerplate templates for new projects/service areas, but more about that in another post.

As part of the standard template there is an individual Key Vault (these are awesome, especially when being used with .NET Core web apps!). We often find that during development the values that are stored in them for our development environments are constantly in flux. This means having to add permissions to add/update/remove secrets. This is easy enough, but after each deployment of the ARM template by the CD Pipeline these permissions are wiped away, and we have to go and add them again.

So I thought there must be a way in the ARM template to conditionally add an access policy when running the ARM template in our test environment. A quick google search led me to this stack overflow answer. However, unfortunately the accepted answer didn’t actually work, so I set out to figure it out myself.

The desired outcome for the ARM template is this: an Azure web app is deployed along with a key vault. The key vault has an access policy configured for the web app to be able to access secrets. If the ARM template is running into the testing environment then an additional access policy for the DrDoctor Developers Azure AD group is automatically added.

1. Declare a Key Vault

The following snippet from the azuredeploy.json file (this is the ARM template), defines a key vault, a few things to note:

  • the name and location are coming from a variable
  • dependsOn refers to an azure web app, this is also defined in the same ARM template, and means that the web app will be created prior to the Key Vault
  • accessPolicies already has an entry, this allows the web app to be able to access secrets stored in the Key Vault (this access policy should be set for all environments)
  • the key vault and the web app use the same variable resource_name so they will end up named the same, but obviously be different resource types
{
   "resources":[
      {
         "type":"Microsoft.KeyVault/vaults",
         "apiVersion":"2016-10-01",
         "name":"[variables('resource_name')]",
         "location":"[variables('resource_location')]",
         "dependsOn":[
            "[resourceId('Microsoft.Web/sites', variables('resource_name'))]"
         ],
         "properties":{
            "sku":{
               "family":"A",
               "name":"Standard"
            },
            "tenantId":"[subscription().tenantId]",
            "accessPolicies":[
               {
                  "tenantId":"[reference(resourceId('Microsoft.Web/sites', variables('resource_name')), '2018-11-01', 'Full').identity.tenantId]",
                  "objectId":"[reference(resourceId('Microsoft.Web/sites', variables('resource_name')), '2018-11-01', 'Full').identity.principalId]",
                  "permissions":{
                     "keys":[],
                     "secrets":["Get", "List"],
                     "certificates":[]
                  }
               }
            ],
            "enabledForDeployment":false,
            "enabledForDiskEncryption":false,
            "enabledForTemplateDeployment":true,
            "enableSoftDelete":true
         }
      }
   ]
}

2. Define the add access policy

The following snippet of the azuredeploy.json file shows how to define an add access policy for the above Key Vault. This snipped of json is a child resource of the above key vault resource. It is in this snippet that a condition can be defined.

There are a few key things to note:

  • The name is made up of the key vault name (which is defined in a variable) and /add
  • The type is Microsoft.KeyVault/vaults/accessPolicies
  • dependsOn is the name of the key vault that we want to add this access policy to
  • condition is an expression which the ARM template evaluates to decided if it should deploy this resource or not
{
   "resources":[
      {
         "name":"[concat(variables('resource_name'), '/add')]",
         "type":"Microsoft.KeyVault/vaults/accessPolicies",
         "apiVersion":"2019-09-01",
         "condition":"[startsWith(parameters('environmentName'), 'uat')]",
         "dependsOn":[
            "[resourceId('Microsoft.KeyVault/vaults', variables('resource_name'))]"
         ],
         "properties":{
            "accessPolicies":[
               {
                  "tenantId":"[reference(resourceId('Microsoft.Web/sites', variables('resource_name')), '2018-11-01', 'Full').identity.tenantId]",
                  "objectId":"[variables('drdoctor_developers_group_id')]",
                  "permissions":{
                     "keys":[],
                     "secrets":["Get","List","Set"],
                     "certificates":[]
                  }
               }
            ]
         }
      }
   ]
}

With this in place when the ARM template is run in the test environment the access policy for the drdoctor_developers_group_id Azure AD group is created, but when the same ARM template is run in the subsequent QA or production environments it isn’t added.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s