Key Vault & Secrets Management With Azure Bicep - Create, Reference, Output Examples

Key Vault is one of the core Azure services which comes in handy in many software solutions. It is the preferred service to manage secrets, certificates, and keys in the Azure environment.

Azure Key Vault Microsoft.KeyVault/vaults and Secrets Microsoft.KeyVault/vaults/secrets resources can be managed using Azure Bicep. Leveraging infrastructure as code (IaC) approach helps simplify resource deployment and increase maintainability of the overall system.

Azure Bicep is a great alternative to well-known ARM templates, it provides superior development experience and also feature parity when compared to ARM templates.

In this post, we will discuss how to deploy a key vault and secret using Azure Bicep. We also cover use cases such as how to add a secret to an existing key vault, reference it in another template, and more.

Contents:

Overview

In the first part of the post, we talk about the key vault resource type.

It contains the discussion of key vault resource properties, differences between permission models, and provides an annotated example of a Bicep template which deploys a key vault.

The second part is devoted to secrets management and related use cases.

Creating KV Secrets section shows how to create a secret using Bicep. We cover deployment of a secret together with its parent key vault, as well as adding a secret to an existing vault.

In the Using KV Secret’s Value section, we see how to use an existing key vault secret as an input to another template deployment, and also provide a quick example of outputting secret value if you want to do this.

Key Vault

Creating a key vault using Bicep is quite easy, the template is not that long and takes little time to get familiar with. However, there are some subtleties that one should be aware of.

We are not going to provide a comprehensive overview of the key vault capabilities in this post, but rather highlight features that are important when working with key vaults using Bicep and infrastructure as code approach. Read more about Key Vault in the documentation.

Access Policies vs Role-Based Access Control (RBAC)

Let’s say, we have a key vault with secrets, certificates, or keys in it. Now, we need to manage which applications or users are allowed, for example, to view, modify, or delete secrets.

Azure Key Vault has two alternative models of managing permissions to secrets, certificates, and keys:

The bottom line is that RBAC enables more fine-grained control over individual secrets, certificates, or keys compared to Access Policies. For example, using RBAC we can enforce that an application can read secret A, but cannot read any other secret in this vault.

Read detailed comparison of the models in Vault access policies to Azure RBAC migration guide.

Example Template Using Azure Bicep

Below is a simple Bicep file which declares a key vault resource. Feel free to use it as a base for your own key vault deployment.

Please note that this example shows many properties available for a Key Vault, but definitely not all, for example, we don’t cover networking part. For the full list please visit Microsoft.KeyVault/vaults.

The next section Vault Properties Explained describes these properties in detail.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// ========== keyvault.bicep ==========

resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
  name: 'kv-contoso3'
  location: 'westus2'
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId

    enableRbacAuthorization: false      // Using Access Policies model
    accessPolicies: [
      {
        objectId: 'd89101d9-cf97-4b6c-9656-c6da457d8add'
        tenantId: tenantId
        permissions: {
          secrets: [
            'all'
          ]
          certificates: [
            'all'
          ]
          keys: [
            'all'
          ]
        }
      }
    ]

    enabledForDeployment: true          // VMs can retrieve certificates
    enabledForTemplateDeployment: true  // ARM can retrieve values

    enablePurgeProtection: true         // Not allowing to purge key vault or its objects after deletion
    enableSoftDelete: true
    softDeleteRetentionInDays: 7
    createMode: 'default'               // Creating or updating the key vault (not recovering)
  }
}

Vault Properties Explained

In this section, we briefly cover what certain properties from the template above are responsible for. This can help understand whether we need them in our template or not.

Secrets

In this section, we discuss how to work with key vault secrets. In the first part, we cover creation and updates of secrets, and in the second part, we look at a couple of ways how to one might want to consume or use these secrets.

Creating KV Secrets

Secret is a child resource of a key vault, in other words, this means that secret cannot exist on its own and has to be created in the context of some key vault. The type of the secret resource is Microsoft.KeyVault/vaults/secrets which suggests that it’s located inside of a key vault.

To learn more about child resources, please check out the post Child Resources In Azure Bicep - 3 Ways To Declare, Loops, Conditions.

The schema of the secret resource is quite simple and doesn’t have many properties, in the following examples we will only use value property in addition to the secret name.

To learn more about additional properties such as content type, not before and expiration time, please refer to Microsoft.KeyVault/vaults/secrets bicep template reference.

Create/Set Key Vault Secret

In the following examples, we are declaring a basic key vault along with a secret deployed inside of it. We show two slightly different approaches both of which are described in 3 Ways To Declare Child Resources.

NOTE: The key vault in the snippets below has only the bare minimum set of properties just to illustrate the point, your key vault template will likely be more complicated.

[Option 1]

The first option is to declare the key vault and then pass its symbolic name to the parent property of the secret resource definition, this is an implementation of declare child resource outside parent using “parent” property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ========== create-kv-secret-1.bicep ==========

// Create some simple key vault
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
  name: 'kv-contoso'
  location: resourceGroup().location
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    accessPolicies: []
  }
}

// Create a secret outside of key vault definition
resource secret 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
  name: 'mySecret'
  parent: keyVault  // Pass key vault symbolic name as parent
  properties: {
    value: 'mySecretValue'
  }
}

[Option 2]

The second option is to declare secret resource inside of the key vault’s resource definition, this is an implementation of declare child resource within parent resource.

Note that the resource type of the secret is only secrets, the rest is inferred from the parent resource.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ========== create-kv-secret-2.bicep ==========

// Create some key vault
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
  name: 'kv-contoso'
  location: resourceGroup().location
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
    tenantId: subscription().tenantId
    accessPolicies: []
  }

  // Create secret within parent key vault
  resource secret 'secrets' = {  // No need for full type name
    name: 'mySecret'
    properties: {
      value: 'mySecretValue'
    }
  }
}

Add Secret To Existing Key Vault

It is possible to create a secret in an existing key vault without deploying the vault in the same template, this is due to the fact that secret is a child resource. So, even though it needs a reference to the parent, it can still be deployed separately.

The three options below are just different ways of creating a secret within a parent key vault, you can read more about 3 Ways To Declare Child Resources.

NOTE: In the code samples below, we assume that key vault kv-contoso already exists in the same resource group where the deployment takes place.

[Option 1]

The first option is to declare a secret resource and specify the key vault name as the first segment in the secret resource’s name. See the example below for details.

Note that the property name (line 6) consists of two segments separated by /, where the first part is key vault name, and the second part is secret name.

Read more about how to declare child resource outside parent without “parent” property.

1
2
3
4
5
6
7
8
9
10
// ========== add-secret-to-existing-kv-1.bicep ==========

param vaultName string = 'kv-contoso'

resource secret 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
  name: '${vaultName}/mySecret'  // The first part is KV's name
  properties: {
    value: 'mySecretValue'
  }
}

[Option 2]

Another option is to declare symbolic name (line 5) for the key vault (keyVault in the example below) and then pass it in the parent property of the secret resource (line 11).

Read more about ways how to reference existing resource in the post Reference New Or Existing Resource In Azure Bicep.

Read more about how to declare child resource outside parent with “parent” property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ========== add-secret-to-existing-kv-2.bicep ==========

param vaultName string = 'kv-contoso'

// Declaring a symbolic name for an existing key vault
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
  name: vaultName
}

resource secret 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
  name: 'mySecret'
  parent: keyVault  // Passing key vault symbolic name as a parent for the secret
  properties: {
    value: 'mySecretValue'
  }
}

[Option 3]

The idea here is similar to the previous option 2, but here we can put secret resource declaration inside of the parent key vault, this avoids the need to specify parent property.

Note the type of the secret resource, in this case it’s just secrets (line 7), this is because the rest is inferred from the parent including version.

Read more about how to declare child resource within parent resource.

1
2
3
4
5
6
7
8
9
10
11
12
13
param vaultName string = 'kv-contoso'

// Declaring a symbolic name for an existing KV
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
  name: vaultName
  
  resource secret 'secrets' = {  // Type of the resource is just "secret"
    name: 'mySecret'  // Secret name, only one segment
    properties: {
      value: 'mySecretValue'
    }
  }
}

Using KV Secret’s Value

Key vault is the recommended way to store secrets, certificates, and keys in the Azure environment. Moreover, key vault can be used to store secrets not only for services inside of Azure but also those outside of it.

Since almost all Azure services require some sort of sensitive values to provide useful capabilities, it is not surprising that many Azure services provide integration with Key Vault service to directly retrieve secrets without the need for user to touch the values.

Reference Key Vault Secret In a Parameter

One very common way of using key vault secrets is to pass them to an ARM or Bicep template as a parameter, and then use secret when creating some resource.

We can pass a secret as a secure parameter into a template or a module:

Output Key Vault Secret

This section describes a way to return the value of a secret from the template deployment using output capabilities.

NOTE: Outputting secrets in the template deployment is insecure and not recommended, the value is passed in plaintext and also logged in the deployment history. Only consider this for non-production, e.g. testing or experimentation, purposes.

You might have noticed that Microsoft.KeyVault/vaults/secrets resource has property named value which we use it to set the value of the secret. However, this property doesn’t allow us to retrieve the actual value, this behavior is by design.

The workaround is to first pass secret’s value as a secure parameter in the way it’s described in the previous section Reference Key Vault Secret In a Parameter, and then return it in the outputs of the template.

To implement the approach described above, we will need two files: main template and parameters file. Read more in the post Using Key Vault Secrets As Secure Parameters In Azure Bicep - Template & Module Inputs.

1
2
3
4
5
// ========== output-secret.bicep ==========
@secure()
param secretValue string

output secretValue string = secretValue

Here is output-secret.parameters.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "secretValue": {
      "reference": {
        "keyVault": {
          "id": "/subscriptions/3c6de895-4ccf-409f-8f18-ff8171a2a800/resourceGroups/rg-contoso/providers/Microsoft.KeyVault/vaults/kv-contoso"
        },
        "secretName": "mySecret"
      }
    }
  }
}