Imagine the following scenario: We have to deploy an Azure Key Vault that comes already populated with a secret, let’s say, some third party’s API access key. Our bicep file (azuredeploy.bicep
) could look like this:
param location string = resourceGroup().location
param tenantId string = tenant().tenantId
@secure()
param keyvaultSecretCatfunValue string
var name = 'kv-${uniqueString(resourceGroup().id)}'
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: name
location: location
properties: {
tenantId: tenantId
sku: {
name: 'standard'
family: 'A'
}
accessPolicies: []
}
}
resource keyvaultSecretCatfun 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
parent: keyVault
name: 'Catfun'
properties: {
value: keyvaultSecretCatfunValue
}
}
But how to we deploy this to Azure without entering the secret’s value every time we call az deployment
?
Loading individual secrets from external files
One trick that worked well for me so far, was to use an external text file, that would be excluded from version control.
For this to work would create a file Catfun.secret
that contains the API secret and make sure, that it is excluded from version control via .gitignore
.
This secret would then be loaded by the bicep template as the default value for the keyvaultSecretCatfunValue
parameter:
@secure()
#disable-next-line secure-parameter-default
param keyvaultSecretCatfunValue string = loadTextContent('Catfun.secret')
(We have to add #disable-next-line secure-parameter-default
) to ignore the warning, that secure parameters should not use default values. Our scenario is a reasonable exception to this rule.)
Loading several secrets from the same file
If you need to deploy several secrets in a secure way it is not necessary to create a secret file for each. Instead you can save your secrets in a json file, that can be parsed by bicep.
The content of my MoreSecrets.json
file looks like this:
{
"Catfun": "MySuperSecretAPIKey!",
"Fishfun": "TheSameButBetter##!"
}
The file can then be parsed using the loadJsonContent()
function in bicep:
param location string = resourceGroup().location
param tenantId string = tenant().tenantId
@secure()
#disable-next-line secure-parameter-default
param secrets object = loadJsonContent('MoreFun.secret')
var name = 'kv-${uniqueString(resourceGroup().id)}'
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
name: name
location: location
properties: {
tenantId: tenantId
sku: {
name: 'standard'
family: 'A'
}
accessPolicies: []
}
}
resource keyvaultSecretCatfun 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
parent: keyVault
name: 'Catfun'
properties: {
value: secrets.Catfun
}
}
resource keyvaultSecretFishfun 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
parent: keyVault
name: 'Fishfun'
properties: {
value: secrets.Fishfun
}
}
Use a loop to deploy the secrets
If you happen to have a lot of secrets that you want to deploy this way, or if you want to make sure that new secrets can be added without touching the deployment code, you can use a loop to iterate over the properties of the `keyvaultSecrets’ object.
resource secret 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = [for secretItem in items(keyvaultSecrets): {
parent: keyVault
name: secretItem.key
properties: {
value: secretItem.value
}
}]
Protect your local secret files
So far we have managed to keep our secrets out of version control, but they are still laying around in plain text files, which is not what we want for any type of secret.
One way I like to do this is by encrypting the secrets using the OpenPGP extension in vscode (ugosan.vscode-openpgp
).
This allows me to protect the files that contain the secrets using a private key and passphrase.