Manage Azure Key Vaults using Bicep
Janne Kemppainen |In a previous post, we explored the power of Bicep in defining and managing Azure Functions. Now, let’s dive into the world of Azure Key Vault and see how we can manage one using Bicep.
Introduction
Azure Key Vault is a service that allows you to store and manage cryptographic keys, secrets, certificates, and other sensitive data. With access policies, you can control who can access the data stored in the vault.
In this post, we will create a Key Vault and grant users access to it using Bicep.
We will use the legacy access policies instead of Azure role-based access control (Azure RBAC) because they can be created without having Owner
or User Access Administrator
permissions on the subscription. If you need RBAC based policies you can check the official documentation for more information.
Prerequisites
The Bicep language is available for Azure CLI version 2.20.0 or later. Bicep parameter files are available from version 2.47.0 or newer. You can find the current version of Azure CLI by running the following command:
az --version
To upgrade to the latest version, run this command:
az upgrade
Naturally, if you wish to follow along, you’ll also need an Azure subscription. If you don’t have one, you can create a free account to get started.
Key Vault template
Unlike Azure Functions, Key Vaults are self-contained resources. This means that we can create a Key Vault without having to worry about other resources.
Let’s start by creating a new Bicep file called key-vault.bicep
and adding the following code:
@description('Key Vault name')
param keyVaultName string = 'my-key-vault'
@description('Access policies for the Key Vault')
param accessPolicies array = []
@description('Key Vault tags')
param tags object = {}
@description('Key Vault SKU')
@allowed([
'standard'
'premium'
])
param skuName string = 'standard'
@description('Are you creating a new Key Vault?')
param createKeyVault bool = false
@description('Resource group name, use default value')
param resourceGroupName string = resourceGroup().name
@description('Resource location, use default value from resource group location')
param location string = resourceGroup().location
@description('Specifies the Azure Active Directory tenant ID that should be used for authenticating requests to the key vault.')
param tenantId string = subscription().tenantId
var createMode = createKeyVault ? 'default' : 'recover'
resource kv 'Microsoft.KeyVault/vaults@2023-02-01' = {
name: keyVaultName
location: location
properties: {
sku: {
family: 'A'
name: skuName
}
createMode: createMode
tenantId: tenantId
accessPolicies: []
}
tags: tags
}
resource kvPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2023-02-01' = {
name: 'add'
parent: kv
properties: {
accessPolicies: accessPolicies
}
}
This template takes in a few parameters:
keyVaultName
- The name of the Key Vault to create.accessPolicies
- An array of access policies to add to the Key Vault.tags
- A set of tags to add to the Key Vault.createKeyVault
- A boolean value that indicates whether the Key Vault should be created or recovered.skuName
- The SKU name of the Key Vault. Defaults tostandard
.resourceGroupName
- The name of the resource group to create the Key Vault in. Defaults to the current resource group.location
- The location of the resource group to create the Key Vault in. Defaults to the current resource group location.tenantId
- The Azure Active Directory tenant ID that should be used for authenticating requests to the Key Vault. Defaults to the current subscription tenant ID.
The value for the createMode
variable is chosen based on the value of the createKeyVault
parameter. If the parameter is true
, the createMode
is set to default
. Otherwise, it is set to recover
.
This logic is needed because trying to create a Key Vault with the same name as an existing Key Vault will result in an error. To avoid this, we can use the recover
mode to recover the existing Key Vault and update it with the new parameters.
The kv
resource defines the Key Vault. It passes the parameters that were defined at the top of the file and sets the accessPolicies
property to an empty array. This is because we will be adding the access policies in the kvPolicies
resource.
When creating a new Key Vault, the accessPolicies
property is required. However, when recovering an existing Key Vault, the accessPolicies
property is ignored.
The kvPolicies
resource defines the access policies for the Key Vault. Since it is separate from the kv
resource, we can add or remove access policies without having to define them all in the kv
resource. This is useful when we later want to add access policies to a function app with a system-assigned identity.
One caveat to this approach is that any existing policies won’t be removed automatically when your infrastructure changes. Therefore, you’ll need to manually remove any policies that are no longer needed.
Key Vault parameters
Now that we have a template for creating a Key Vault, let’s create a parameter file to make it easier to deploy.
Traditionally, we’ve been able to use parameter files written in JSON. However, these files are difficult to read and write, and they lack support for expressions and functions.
Bicep parameter files are written in the same language as Bicep files which makes them much easier to read and write. The support for expressions and functions also makes them much more powerful.
Create a new file called key-vault.bicepparam
and add the following code:
using 'key-vault.bicep'
param keyVaultName string = 'my-parameterized-key-vault'
param accessPolicies = [
{
tenantId: subscription().tenantId
objectId: '00000000-0000-0000-0000-000000000000'
permissions: {
keys: [
'all'
]
secrets: [
'get'
]
certificates: [
'list'
]
}
}
]
param tags = {
environment: 'dev'
}
The using
statement binds this parameter file to the key-vault.bicep
file. Each Bicep file can have multiple parameter files but each parameter file can only be bound to a single Bicep file.
This parameter file changes the default keyVaultName
. It also sets the accessPolicies
parameter to an array with a single access policy. You can add additional policies by adding more objects to the array.
The access policy object has the following properties:
tenantId
- The Azure Active Directory tenant ID. In this case, we’re using the current subscription tenant ID.objectId
- The object ID of the user, group, or service principal.permissions
- The permissions to the Key Vault.
The permissions
property object contains the granted permissions for all keys, secrets, and certificates in the Key Vault. The permissions can be set to all
or an array of specific permissions.
Refer to the Azure Key Vault access policies documentation for more information about the permissions and their values.
Finally, the parameter file sets the tags
that we want to add to the Key Vault.
Deploy the Key Vault
Now that we have a template and parameter file, we can deploy the Key Vault.
This can be done using the az deployment group create
command:
az deployment group create \
--resource-group my-resource-group \
--template-file key-vault.bicep \
--parameters key-vault.bicepparam
It’ll take a short while for the deployment to complete. Once it does, you should see the new Key Vault in the Azure portal with the access policy that was defined in the parameter file.
Next steps
In this article, you learned how to create a Key Vault using Bicep. You also learned how to use parameter files to make it easier to deploy the Key Vault.
In the next post in this series, you’ll learn how to create a function app with a system-assigned identity and grant it access to the Key Vault. Then, you’ll learn how to configure the function app to fetch a secret from the Key Vault.
See you in the next post!
Previous post
Deploy Azure Function Apps with Bicep