How To Copy Certificates and Secrets Between Key Vaults - Azure Bicep

Certificates and secrets are often necessary for correct functioning of an application or service. In cases, when the application/service is distributed across multiple regions or geos, we might need an ability to copy certificates/secrets between the regions where our application is deployed.

Azure Bicep (and ARM templates) allows copying secrets and certificates between key vaults by retrieving secret values from the source key vault and passing them as parameters into a template. Then the passed values can be used to create or update entries in the destination key vault.

In this post, we will cover not only how to copy individual certs and secrets but also discuss how to organize our template and parameter files to make it extensible for handling an arbitrary number of certificates or secrets. Stay tuned.

Contents:

Overview

Azure Key Vault is a service in Azure which is commonly used for storing keys, secrets, and certificates. Almost every infrastructure or service deployment involves Key Vault with some secrets.

For better understanding of Azure Key Vault, consider reading Key Vault & Secrets Management With Azure Bicep - Create, Reference, Output Examples.

In this post, we will talk about handling secrets and certificates, these are child resources of Key Vault. You can read more about Child Resources In Azure Bicep - 3 Ways To Declare, Loops, Conditions.

We start with exploring how to copy a secret from one key vault to another. There we look at two ways of passing a secret value, either using parameter file or module inputs.

Next, we reuse the same idea as for secrets and apply it to copying a certificate between vaults. Luckily, the code is almost identical, so it is easy to understand after reading the section about copying a secret.

At the end, we cover how to handle a more advanced use case where we want to copy many secrets and/or certificates between different key vaults, perhaps in a case of multi-region deployment.

Copy Secret From One Key Vault To Another

To clone a secret between key vaults, we need to perform two steps:

  1. Retrieve/export the secret value from the source key vault.
  2. Import this value into the destination key vault.

For step #1, we are going to pass the value of the secret stored in the source key vault through a parameter. It can be done both for template and module parameters. This approach is described in detail in Using Key Vault Secrets As Secure Parameters In Azure Bicep - Template & Module Inputs.

For step #2, we will use Microsoft.KeyVault/vaults/secrets resource type to create or update the secret. You can read more about secrets in Key Vault & Secrets Management With Azure Bicep.

[Step #2] Template To Create Or Update Secret

Below, we define a Bicep template which creates or updates the secret based on the values passed via parameters.

NOTE: In the template below, we assume that the destKeyVaultName Key Vault already exists in the same resource group where the template is deployed. So, we just work with the secret, a child resource of that key vault.

Also, on line 5 pay attention to the @secure() decorator for secretValue parameter. This is needed to properly handle sensitive values. Read more in Parameter With @secure() Decorator.

File create-or-update-secret.bicep:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ========== create-or-update-secret.bicep ==========

param destKeyVaultName string
param secretName string
@secure()
param secretValue string

resource secret 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
  name: '${destKeyVaultName}/${secretName}'
  properties: {
    value: secretValue
  }
}

[Step #1] Passing Secret Value To The Template

Given we already have a template to create or update the secret, now the only thing left is to extract the value from the source key vault and pass it to the template. We will cover two ways of doing that: using a parameter file and modules functionality.

In both cases, under the hood the underlying infrastructure (ARM) retrieves the secret value from the source key vault and passes it as a parameter during deployment. This is what makes it seamless for us, users.

IMPORTANT: Key vault must be enabled for template deployment, otherwise, deployment will fail. See how to enable it in this note.

[Option A] Through Parameter File

While parameter file is familiar to anyone who worked with ARM templates, it also made its way into Azure Bicep without any modifications. To make use of a parameter file to pass the key vault secret value, we need the following:

NOTE: This approach of using a parameter file is explained in Key Vault Secret Through Parameter File.

Notes about the parameters in the code snippet below:

File create-or-update-secret.parameters.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "destKeyVaultName": {
      "value": "kv-contoso-dest"
    },
    "secretName": {
      "value": "destSecret"
    },
    "secretValue": {
      "reference": {
        "keyVault": {
          "id": "/subscriptions/ec53095c-1445-4402-a7b7-f7c0d021e8fc/resourceGroups/rg-contoso/providers/Microsoft.KeyVault/vaults/kv-contoso-src"
        },
        "secretName": "srcSecret"
      }
    }
  }
}

[Option B] Through Module Inputs

Great thing about Bicep files is that any file can be used as a module template. This means that we can deploy our create-or-update-secret.bicep file as a module inside of another template. Also, feel free to read about Modules In Azure Bicep if needed.

And during the module deployment, we can use getSecret function to specify which key vault secret we want to pass as a value for the corresponding parameter.

Read more about secrets and module inputs in another post Passing Key Vault Secret into Module.

The following code snippet is our parent file: main-copy-secret.bicep. A few notes about the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ========== main-copy-secret.bicep ==========

param srcKeyVaultName string = 'kv-contoso-src'
param srcSecretName string = 'srcSecret'
param destKeyVaultName string = 'kv-contoso-dest'
param destSecretName string = 'destSecret'

resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
  name: srcKeyVaultName
}

module secret 'create-or-update-secret.bicep' = {
  name: '${srcSecretName}-deployment'  // Arbitrary name for the module deployment
  params: {
    destKeyVaultName: destKeyVaultName
    secretName: destSecretName
    secretValue: keyVault.getSecret(srcSecretName)
  }
}

Copy Certificate Between Key Vaults

The cool thing is that copying certificates between key vaults is literally the same as copying secrets. The reason is that any certificate in a key vault is also exposed through secrets API.

We can treat certificates as secrets because retrieving a certificate through secrets API returns the certificate along with its private key (if present). This greatly simplifies our task, however, it has a downside.

Important notes about this approach:

NOTE: If you want to import certificate as a proper certificate (and not secret), then consider using Azure PowerShell or Azure CLI to perform export/import operations. However, this is beyond the scope of this blog post.

Bicep Template and Parameter File

As already mentioned, the copying a certificate between key vaults requires the same template as copying secrets. The only difference is that we will specify content type for the secret resource:

The rest of the template and parameter file are identical to the secret’s case, only parameter names are different to reflect that we are dealing with a certificate.

NOTE: We can also use module deployment and getSecret function as discussed in Option B of section about copying secrets. In this section, we use a parameter file for simplicity.

By deploying the template and parameter file below, we will copy srcCert certificate from kv-contoso-src into destCert secret in kv-contoso-dest.

File create-or-update-certificate.bicep:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ========== create-or-update-certificate.bicep ==========

param destKeyVaultName string
param certName string
@secure()
param certValue string

resource cert 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
  name: '${destKeyVaultName}/${certName}'
  properties: {
    value: certValue
    contentType: 'application/x-pkcs12'
  }
}

File create-or-update-certificate.parameters.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "destKeyVaultName": {
      "value": "kv-contoso-dest"
    },
    "certName": {
      "value": "destCert"
    },
    "certValue": {
      "reference": {
        "keyVault": {
          "id": "/subscriptions/ec53095c-1445-4402-a7b7-f7c0d021e8fc/resourceGroups/rg-contoso/providers/Microsoft.KeyVault/vaults/kv-contoso-src"
        },
        "secretName": "srcCert"
      }
    }
  }
}

Copy Multiple Certificates/Secrets Between Different Key Vaults

In the previous sections, we discussed how to copy one certificate or one secret between two different key vaults. However, it is unlikely that we will create this automation to handle only one secret or certificate, since in this case we can just copy it manually.

NOTE: In this section, examples are for the certificates use case, but dealing with secrets is very similar, please refer to sections related to secrets and certificates to see the differences.

Now, imagine that we want to automate copying many certificates/secrets during an infrastructure deployment, for example, between key vaults in different Azure regions. Here is an approach we can take:

In this section, we make extensive use of modules, you can read more about them in the post Learn Modules In Azure Bicep - Basics To Advanced, How It Works, Nested Modules, Outputs, Scopes.

Next, let’s look at how to implement step by step the approach described above.

1. Copy Certs Between Two Key Vaults

As a first step, let’s introduce a new Bicep file which will copy multiple certificates from one key vault to another. This template leverages create-or-update-certificate.bicep which we defined previously.

A few notes about the following copy-multiple-certificates-same-keyvaults.bicep template:

NOTE: The template will import the certificate into the destination key vault under the same name as it is in the source key vault. However, you can easily extend the template to import cert under different name if needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ========== copy-multiple-certificates-same-keyvaults.bicep ==========

param srcKeyVaultName string
param destKeyVaultName string
param certsToCopy array

resource srckeyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
  name: srcKeyVaultName
}

@batchSize(1)
module certificates 'create-or-update-certificate.bicep' = [for certName in certsToCopy: {
  name: '${destKeyVaultName}-${certName}'
  params: {
    destKeyVaultName: destKeyVaultName
    certName: certName
    certValue: srckeyVault.getSecret(certName)
  }
}]

2. Copy Certs Between Many Key Vaults

By going one step further, we can leverage step #1 and extend it to copy certificates between multiple source-destination key vault pairs.

For this, we are going to add a new template copy-multiple-certificates-different-keyvaults.bicep which will reuse a template from the previous section:

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
// ========== copy-multiple-certificates-different-keyvaults.bicep ==========

param copyConfig array = [
  {
    srcKeyVaultName: 'kv-contoso-src-1'
    destKeyVaultName: 'kv-contoso-dest-1'
    certificates: [
      'certA'
      'certB'
    ]
  }
  {
    srcKeyVaultName: 'kv-contoso-src-2'
    destKeyVaultName: 'kv-contoso-dest-2'
    certificates: [
      'certC'
      'certD'
    ]
  }
]

module kvLevelCertCopy 'copy-multiple-certificates-same-keyVaults.bicep' = [for config in copyConfig: {
  name: '${config.srcKeyVaultName}-${config.destKeyVaultName}'
  params: {
    srcKeyVaultName: config.srcKeyVaultName
    destKeyVaultName: config.destKeyVaultName
    certsToCopy: config.certificates
  }
}]

Summary

In the previous subsections, we used three Bicep files to handle copying of multiple certificates between different key vaults. To summarize, we have the following template files: