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 to standard.
  • 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.

Note
The access policies can be an array of 0 to 16 identities. Each identity can be a user, group, or service principal. In total, you can have up to 1024 access policies per Key Vault. For more information, see Azure Key Vault access policies.

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.
Note
The objectId can be found in the Azure portal by navigating to the user, group, or service principal and selecting the Object ID property.

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!

Subscribe to my newsletter

What’s new with PäksTech? Subscribe to receive occasional emails where I will sum up stuff that has happened at the blog and what may be coming next.

powered by TinyLetter | Privacy Policy