Using Key Vault Secrets As Secure Parameters In Azure Bicep - Template & Module Inputs
Many cloud resources expect secret values to be passed as part of their definition or configuration to function correctly. This could be passwords, keys, connection strings, certificates, etc. Keeping these values secure is necessary to prevent many sorts of security problems which easily arise if our secrets are not safe.
In Azure, using Key Vault is the preferred way of storing and managing secrets, certificates, and keys. When working with Azure Bicep, we often need to retrieve secrets stored in a key vault to later pass them into the definition of some resource.
In parameter files, key vault secret is referenced by specifying key vault resource id
, secretName
and (optionally) secretVersion
. When working with modules, Azure Bicep getSecret function should be used to pass secrets into the module (nested deployment).
Fortunately, ARM templates and Azure Bicep have built-in support for using key vault secrets inside of the templates. In this post, we will discuss how to work with secure parameters and key vault secrets in Azure Bicep.
Contents:
- Overview
- Parameter With @secure() Decorator
- Sample Template With Secure Parameter
- Key Vault Secret Through Parameter File
- Passing Key Vault Secret into Module
- How To Pass Dynamic Number Of Key Vault Secrets As Parameter Array
- Related Posts
- Useful Links
Overview
We start with a short introduction about defining secure parameters in Azure Bicep files followed by a sample template which expects a secret value, this template will be used in code samples throughout this post.
Next, our attention shifts to key vault secrets. The first use case is how to pass a secret stored in a key vault into a Bicep file using a parameter file. The second one is how to pass a key vault secret when consuming a module in a template. Additionally, we briefly cover how to specify a particular version of a secret, not only the latest one.
The last part of the post covers a more advanced use case where we want to pass many key vault secrets into a Bicep template in form of an array. This avoids defining individual secure parameters for each key vault secret and instead using array and loops to handle data.
Parameter With @secure() Decorator
By default, parameters that we pass are not protected, their values are logged during the deployment and saved to the deployment history. This is convenient and works fine in many cases, but sometimes we want values to be kept secret.
Often, templates require some secret values to be provided, for example, passwords. In this case, we shouldn’t store secrets in template in plain text, but should rather pass them as parameters. For this kind of parameters ARM templates have secureString
and secureObject
data types.
In Bicep, there are no secureString
and secureObject
data types, instead there’s a @secure()
decorator which can be applied to a parameter with a data type string
or object
, see the example below.
@secure()
param myPassword string
@secure()
param myConfig object
When transpiled into ARM template, the parameters above result into secureString
and secureObject
data types.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"myPassword": {
"type": "secureString"
},
"myConfig": {
"type": "secureObject"
}
},
"resources": []
}
Sample Template With Secure Parameter
A real world example can be a deployment of a SQL database where we pass admin password as a secure parameter. The filename of template below is sqldb.bicep
file and we’ll use this template in the next sections in our code examples.
// ============ sqldb.bicep ============
@secure()
param myPassword string
resource sqlserver 'Microsoft.Sql/servers@2021-02-01-preview' = {
name: 'ContosoSqlServer'
location: 'westus'
properties: {
administratorLogin: 'contosoadmin'
administratorLoginPassword: myPassword
}
resource sqldb 'databases' = {
name: 'contosodb'
location: 'westus'
}
}
Key Vault Secret Through Parameter File
In Bicep, we can use a parameter file to pass values, as we do it with ARM templates. And in a parameter file, we can reference secrets from a key vault by specifying the name of the vault and the name of the secret. Optionally, secret version can be also included, but by default the latest version is taken.
NOTE: Key Vault must be enabled for template deployment so that ARM has permissions to retrieve secrets during deployment. This can be enabled in Azure Portal under “Access policies” → “Azure Resource Manager for template deployment”, or enabledForTemplateDeployment
property in an ARM template of a Key Vault.
Passing Secret
Consider passing myPassword
parameter to the sqldb.bicep
template from the last section. Our parameter file will look the one shown below. This code will fetch the latest version of the secret because it is the default behavior.
Please note that a few basic things are assumed:
- Line 8: Key vault
kv-contoso
exists, it has the corresponding resource id, and it is enabled for template deployment - Line 10: Secret
mySqlPassword
exists in the key vault
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": {
"myPassword": {
"reference": {
"keyVault": {
"id": "/subscriptions/1a286532-7724-438b-97a0-68278053737a/resourceGroups/rg-contoso/providers/Microsoft.KeyVault/vaults/kv-contoso"
},
"secretName": "mySqlPassword"
}
}
}
}
Passing Specific Secret Version
Building on the previous example, if we want to take a specific version of the secret, then we should use secretVersion
property where we pass a particular secret version. Obviously, the secret version should be a valid one, otherwise the deployment will fail.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"myPassword": {
"reference": {
"keyVault": {
"id": "/subscriptions/1a286532-7724-438b-97a0-68278053737a/resourceGroups/rg-contoso/providers/Microsoft.KeyVault/vaults/kv-contoso"
},
"secretName": "mySqlPassword",
"secretVersion": "2cc1676124b77bc9a1bfd30d8f4b6225"
}
}
}
}
Deploying Template & Parameter File
There are multiple ways to deploy a bicep file with its parameter file. Here an example how to do that using Azure PowerShell. Read more about deploying Azure Bicep files in another post 5 Ways To Deploy Bicep File With Parameters - Azure DevOps, PowerShell, CLI, Portal, Cloud Shell.
New-AzResourceGroupDeployment `
-Name mySqlDatabaseDeployment1 `
-ResourceGroupName rg-contoso `
-TemplateFile sqldb.bicep `
-TemplateParameterFile sqldb.parameters.json
Passing Key Vault Secret into Module
In the last section, we explored how to pass key vault secrets into a template through secure parameters. But how to do this with modules? Assuming we have a Bicep module which expects some secret value, we want to pass a key vault secret there.
To learn more about modules, read Learn Modules In Azure Bicep post.
The idea is quite similar to the one discussed in the previous section, in a regular ARM template we’d have a nested deployment with a secure parameter, and the parameter value would be referenced from a key vault.
For Azure Bicep, there is a getSecret
function which makes simplifies passing key vault secrets into modules. Let’s take a look at it next.
getSecret function
The getSecret function is specifically designed to be used in situations where a key vault secret needs to be passed as a secure parameter into a module. A few notes regarding getSecret
function:
- Can only be invoked on
Microsoft.KeyVault/vaults
resource - Can be used only with a
@secure()
parameter - To get the latest secret version, omit
secretVersion
argument or pass an empty string
getSecret(secretName: string, [secretVersion: string])
// Usage: kv.getSecret('mySqlPassword', '2cc1676124b77bc9a1bfd30d8f4b6225')
Template To Deploy
Now, let’s use getSecret
function to pass a key vault secret into a module. For the example below, we have two Bicep files:
sqldb.bicep
- module file which is defined in Sample Template With Secure Parameter sectionmain.bicep
- file from where we consume the module, it is shown below
When examining the code below, pay attention to these points:
- Line 3 - creating a symbolic name for the key vault where our secret lives, note that we assume this vault already exists (keyword
existing
) - Line 5 - if the key vault is in a different resource group, we should use
scope
property to specify where it is, read more about Reference New Or Existing Resource In Azure Bicep - Line 9 -
name
is arbitrary, it will be used for a nested deploymentMicrosoft.Resources/deployments
resource - Line 11 and 12 - using symbolic name
keyVault
and functiongetSecret
to pass the secret
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ============ main.bicep ============
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: 'kv-contoso'
// scope: resourceGroup('rg-contoso') - if key vault is in a different resource group
}
module db './sqldb.bicep' = {
name: 'sqlDbDeployment1'
params: {
myPassword: keyVault.getSecret('mySqlPassword')
// myPassword: keyVault.getSecret('mySqlPassword', '2cc1676124b77bc9a1bfd30d8f4b6225')
}
}
Running bicep build main.bicep
produces the following ARM template where everything is compiled into one template. Note that at lines 15-21 it uses the same syntax to reference the key vault secret as we discussed in the Key Vault Secrets Through Parameter File 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2019-10-01",
"name": "sqlDbDeployment1",
"properties": {
"expressionEvaluationOptions": {
"scope": "inner"
},
"mode": "Incremental",
"parameters": {
"myPassword": {
"reference": {
"keyVault": {
"id": "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, 'rg-contoso'), 'Microsoft.KeyVault/vaults', 'kv-contoso')]"
},
"secretName": "mySqlPassword"
}
}
},
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"myPassword": {
"type": "secureString"
}
},
"resources": [
{
"type": "Microsoft.Sql/servers/databases",
"apiVersion": "2021-02-01-preview",
"name": "[format('{0}/{1}', 'ContosoSqlServer', 'contosodb')]",
"location": "westus",
"dependsOn": [
"[resourceId('Microsoft.Sql/servers', 'ContosoSqlServer')]"
]
},
{
"type": "Microsoft.Sql/servers",
"apiVersion": "2021-02-01-preview",
"name": "ContosoSqlServer",
"location": "westus",
"properties": {
"administratorLogin": "contosoadmin",
"administratorLoginPassword": "[parameters('myPassword')]"
}
}
]
}
}
}
]
}
How To Pass Dynamic Number Of Key Vault Secrets As Parameter Array
In the previous sections of this post, we discussed how to pass one key vault secret into a template or a module. This is of course useful, however, sometimes we want to pass multiple or even many key vault secrets into a template. Keeping each secret as a separate parameter will result in a lot of repetitive code which is better to avoid.
The approach we discuss in this section won’t work for all possible use cases, but if secrets usage can be split in smaller units, this problem can be elegantly resolved using the knowledge we already got from the previous sections. For example, it could be a deployment of multiple resources where each resource requires a few secrets. In such cases, it makes sense to pass resources as an array and use a loop in the template.
To pass a dynamic/large number of key vault secrets into a bicep file, secrets’ metadata (names, versions and their key vaults) should be passed into a template as an array. Then each key vault secret is passed in a loop into a module as a secure parameter using getSecret function.
Basically, we are going to leverage nested deployments (modules) to pass key vault secrets. We will discuss two cases: when all secrets come from the same or from different key vaults.
NOTE: Another less favorable but possible solution is to store multiple secrets in one key vault secret in a form of JSON, for example. Then in the template the value can be parsed into an object or array and used. But we are not discussing this approach.
Secrets From The Same Key Vault
When all secrets live in the same vault, it is a little bit easier compared when multiple key vaults involved. Here, we pass metadata about the key vault and secrets through three parameters:
keyVaultName
- quite obvious, required for finding the right key vaultkeyVaultResourceGroup
- not required if the deployment target scope is the same resource group where key vault lives, but in any case it won’t harm to specify the resource groupsecrets
- array of names and versions, note that version can be empty - this will take the latest secret version
The code below should illustrate the basic idea how to pass a dynamic number secrets into Azure Bicep deployment. Using this knowledge, we can explore more complex use cases, for example, how to combine secrets from multiple vaults.
NOTES:
- Lines 3-14: To keep code samples compact, default parameter values are used, but usually you’d want to use a parameter file to pass values
- Line 16: Creating a symbolic name for the existing key vault, more details in Reference New Or Existing Resource In Azure Bicep
- Line 24: Function getSecret is used in the loop to retrieve and pass secrets into the module
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
// ========== single-kv-secrets.bicep ==========
param keyVaultName string = 'kv-contoso'
param keyVaultResourceGroup string = 'rg-contoso'
param secrets array = [
{
name: 'secret1'
version: '44c79bd8ccee40c3b720e43d1a6df0ba'
}
{
name: 'secret2'
version: ''
}
]
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: keyVaultName
scope: resourceGroup(keyVaultResourceGroup)
}
module modules 'resource.bicep' = [for secret in secrets: {
name: 'moduleDeployment-${secret.name}'
params: {
mySecretValue: keyVault.getSecret(secret.name, secret.version)
}
}]
// ========== resource.bicep ==========
@secure()
param mySecretValue string
// ...
Secrets From Different Key Vaults
When our secrets live in different key vaults, the code becomes a bit more complicated compared to the last section because we cannot create symbolic names for each key vault inside of the for-loop. However, this can be solved by adding one more level of nested deployment (module).
In the following template, we are reusing single-kv-secrets.bicep
and resource.bicep
files from Secrets From The Same Key Vault section as is without any changes. What we do is we group secrets by their corresponding key vault and use them together in a module, see code below.
// ========== main.bicep ==========
param secretsConfig array = [
{
keyVaultName: 'kv-contoso1'
keyVaultResourceGroup: 'rg-contoso1'
secrets: [
{
name: 'secret1'
version: '44c79bd8ccee40c3b720e43d1a6df0ba'
}
]
}
{
keyVaultName: 'kv-contoso2'
keyVaultResourceGroup: 'rg-contoso2'
secrets: [
{
name: 'secret2'
version: ''
}
]
}
]
module singleKVs 'single-kv-secrets.bicep' = [for kvSecrets in secretsConfig: {
name: 'kvSecretsDeployment-${kvSecrets.keyVaultName}'
params: {
keyVaultName: kvSecrets.keyVaultName
keyVaultResourceGroup: kvSecrets.keyVaultResourceGroup
secrets: kvSecrets.secrets
}
}]
Related Posts
- Parameters In Azure Bicep - Ultimate Guide With Examples
- How To Pass Arrays and Numbers As @secure() Parameters - Azure Bicep
- Storage Account SAS Tokens, Access Keys, And Connection Strings In Azure Bicep
- Variables In Azure Bicep - From Basics To Advanced
- Learn Modules In Azure Bicep - Basics To Advanced, How It Works, Nested Modules, Outputs, Scopes
- Reference New Or Existing Resource In Azure Bicep
- Child Resources In Azure Bicep - 3 Ways To Declare, Loops, Conditions
- Create Resource Group With Azure Bicep and Deploy Resources In It
- 5 Ways To Deploy Bicep File With Parameters - Azure DevOps, PowerShell, CLI, Portal, Cloud Shell
- Deploy Azure Bicep In YAML and Classic Release Pipelines (CI/CD) - Azure DevOps