Managing Azure Key Vault using Azure Resource Manager (ARM) Templates

Rahul Pulikkot Nath
Rahul Pulikkot Nath

Table of Contents

Creating and managing Azure Key Vault was mostly supported through PowerShell cmdlets initially, but there are multiple ways of achieving this now - REST API, PowerShell, CLI or ARM templates. In this post, we will look into how we can use Azure Resource Manager (ARM) templates to create and manage a Key Vault.

Azure Resource Manager and Templates

Simply put, the Azure Resource Manager(ARM) allows to group different resources in your solution that form a logical unit and manage them together. It allows to spin up all the resources required for your system and deploy them as and when required. You can achieve this using custom PowerShell scripts or creating a template (in JSON format) - Azure Resource Manager Template.

Within the ARM template, you define the infrastructure for your app, how to configure that infrastructure, and how to publish your app code to that infrastructure.

You can export a template from existing resources for a starting point and then work off that. But in this post, I will start with an empty template as it helps to understand all the template parts. A template is nothing but a JSON file with a specific schema. All templates have the below format where a few of the elements are not mandatory. If you are not familiar with the template format and the different elements that make it, I'll wait while you read more about the Template format

{
  "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "",
  "parameters": {},
  "variables": {},
  "resources": [],
  "outputs": {}
}

Key Vault ARM Template

The Key Vault schema is authored here and is part of the root schema URL that we had seen above. Though it might not be able to fully understand the schema details, it helps to understand at a high level what are the different parameters that are allowed when defining a Key Vault. At present, the schema allows only creating Secrets within a Key Vault and Keys have to be created separately.

Like we did using the REST API, with this ARM template I want to create or update a Key Vault with a specified set of properties (like Vault Name, tenant etc), the access policies to specify the AD objects (applications/users) that have access to the Vault and create a few secrets.

Create a new JSON file with any name you like (azuredeploy.json) and copy the above template structure into it. For the content version, you can use any value that you like for e.g. 1.0.0. Next, we need to define the parameters that we need, that are specific to each Key Vault deployment. Without parameters, we will be always deploying the resources with the same name and properties, so it is a good practice to externalize it and use it as required. Parameters have a defined structure and allows to have basic validation for the input values. All parameters that does not have a defaultValue needs to be passed in while using the template.

Parameters

Let's see a few of the different parameter types that we use in this template. The keyVaultName parameter is a simple string value and is required to be passed in as it does not have a default value specified, where as the enableVaultForVolumeEncryption is an optional parameter and defaults to false. The parameters accessPolicies and secrets are of type array and takes in any valid JSON array. But in this specific case, I want it to be in a specific format but I am yet not sure if I can specify a format structure for the JSON input. Sound off in the comments if you know of a way.

"parameters": {
    "keyVaultName": {
        "type": "string",
        "metadata": {
            "description": "Name of the Key Vault"
        }
    },
    "accessPolicies": {
        "type": "array",
        "defaultValue": "{}",
        "metadata": {
            "description": "Access policies object {"tenantId":"","objectId":"",
                    "permissions":{"keys":[""],"secrets":[""]}}"
        }
    },
    "enableVaultForVolumeEncryption": {
        "type": "bool",
        "defaultValue": false,
        "metadata": {
            "description": "Specifies if the vault is enabled for volume encryption"
        }
    },
    "secrets": {
        "type": "array",
        "defaultValue": "{}",
        "metadata": {
            "description": "all secrets {"secretName":"","secretValue":""}"
        }
    }
    ...
}

Resources

The Resources section of the template defines the resources to be deployed or updated and takes in an array of values. Resource manager supports two modes of deployment - Incremental and Complete deployment - and the way you define the resources here will affect what and how things get deployed. The template supports the use of certain Expressions and Functions, to enable dynamic creation of values. Expressions are enclosed in square brackets ([]) and can appear anywhere is a JSON string value and evaluated when the template is deployed. To use a literal string that starts with a bracket [, use two brackets [[.

The parameter function is used to get the value of a parameter that is passed in. I use this to access the Vault name, accessPolicies and other parameters that we had defined earlier. Since accessPolicies in the template expects an array I pass in the parameter object as is to it.

Secrets are defined as nested resources within the Key Vault and can be defined as a nested property within the Key Vault resource as shown in the example here. Since here we are interested in dynamically generating the Secrets based on the array value passed in as secrets parameter, I use the copy, copyIndex and length function to iterate through the array and generate the required template. The copy function iterates and produces the same template structure with different values as specified by the parameter values.

Since the Secret is defined as a separate resource, name property needs to indicate that it is a nested resource, hence we are concatenating the Vault Name with it. Without that I was getting the error :

A nested resource type must have an identical number of segments as its resource name. A root resource type must have segment length one greater than its resource name..

The dependsOn element specifies that the Secret resource is dependent on the Key Vault resource.

 "resources": [
    {
        "type": "Microsoft.KeyVault/vaults",
        "name": "[parameters('keyVaultName')]",
        "accessPolicies": "[parameters('accessPolicies')]",
        ...
    },
    {
        "type": "Microsoft.KeyVault/vaults/secrets",
        "name": "[concat(parameters('keyVaultName'), '/', parameters('secrets')[copyIndex()].secretName)]",
        "properties": {
            "value": "[parameters('secrets')[copyIndex()].secretValue]"
        },
        "dependsOn": [
            "[concat('Microsoft.KeyVault/vaults/', parameters('keyVaultName'))]"
        ],
        "copy": {
            "name": "secretsCopy",
            "count": "[length(parameters('secrets'))]"
        }
    }
]

Deploying with ARM Templates

To deploy the ARM template we need to pass in the required parameters and run the template.

Parameter File

Parameters can be passed in individually or as a Parameter File. Parameter file (azuredeploy.parameters.json) is a JSON file with a specific format. Below is a sample parameter file for our Key Vault ARM template. We can have different such templates for each of our deployment environments with values specific for the environment.

    {
        "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
        "contentVersion": "1.0.0.0",
        "parameters": {
            "keyVaultName": {
                "value": "NewARMVaultP"
            },
            "tenantId": {
                "value": ""
            },
            "accessPolicies": {
                "value": [
                    {
                        "tenantId": "<TENANT ID>",
                        "objectId": "<AD OBJECT ID>",
                        "permissions": {
                            "keys": ["all"],
                            "secrets": ["all"]
                        }
                    },
                    { ... }
                ]
            },
            "secrets": {
                "value": [
                    {
                        "secretName": "ConnectionString",
                        "secretValue": "SecureString1"
                    },
                    { ... }
                ]
            }
        }
    }

Deployment

The ARM template along with the parameter file can be deployed in different ways - PowerShell, Azure CLI, REST API, Visual Studio or from Azure Portal. Using PowerShell we can deploy as below. The Test-AzureRmResourceGroupDeployment cmdlet tests if the template file and parameter file are in correct format. This is mostly useful when authoring the template. New-AzureRmResourceGroupDeployment deploys using the given template file and parameters.

Test-AzureRmResourceGroupDeployment -ResourceGroupName SharedGroup -TemplateFile .\azuredeploy.json
        -TemplateParameterFile .\azuredeploy.parameters.json -Verbose
New-AzureRmResourceGroupDeployment -ResourceGroupName SharedGroup -TemplateFile .\azuredeploy.json
         -TemplateParameterFile .\azuredeploy.parameters.json -Verbose

ARM Template Visualizer (ArmViz) is a visual way of editing and managing ARM templates and can be useful when dealing with a large number of resource deployments in a template. Also check out some Good practices for designing templates. Most of the template code above is based off this example here. I will try to push this template into the Azure Quickstart Templates but meanwhile it is available here. The ARM template is available with the Azure Quickstart Templates

Azure Key Vault