Bicep - Azure Key Vault Deployments in Multiple Environments
Hi folks, I sincerely hope you are all doing great. You are probably aware of the importance of Azure Key Vault in securely and centrally managing secrets, encryption keys, and certificates. However, it is crucial to adopt the right architecture to ensure the best practices.
One of the key recommendations is to create distinct key vaults for different applications and environments.
This article shows how using Bicep modules can simplify the configuration of Azure Key Vaults, improving operational efficiency and security.
Prerequisites #
Before you start, you’ll need the following to deploy and manage resources with Bicep:
- You need Azure CLI version 2.20.0 or later to deploy Bicep files on your local machine.
- A text editor or IDE of your choice (Visual Studio Code with Bicep extension is my recommendation)
Create the Bicep files #
The initial phase in rolling out our Bicep module involves crafting a specialized Bicep file—named main.bicep—that serves as the blueprint for configuring multiple Azure Key Vaults tailored for distinct applications and environments. This file will contain the code necessary to automate the deployment of separate Key Vaults for different applications and environments, adhering to security best practices and enhancing operational efficiency.
targetScope= 'subscription'
@description('Array de Key Vaults para crear.')
param keyVaultArray array = []
@description('The tags to be associated with the resources.')
param tags object = {
bicep: 'true'
}
// This module iterates over the keyVaultArray to create multiple Key Vaults
module keyVaultDeployments './modules/kv.bicep' = [for (keyVault, index) in keyVaultArray: {
name: 'deployment${index}'
params: {
keyVaultName: '${keyVault.name}-${keyVault.environment}'
location: keyVault.location
tags: tags
properties: {
enabledForDeployment: keyVault.properties.enabledForDeployment
enabledForDiskEncryption: keyVault.properties.enabledForDiskEncryption
enabledForTemplateDeployment: keyVault.properties.enabledForTemplateDeployment
enablePurgeProtection: keyVault.properties.enablePurgeProtection == false ? null : keyVault.properties.enablePurgeProtection
enableSoftDelete: keyVault.properties.enableSoftDelete
networkAcls: keyVault.properties.networkAcls
publicNetworkAccess: keyVault.properties.publicNetworkAccess
sku: keyVault.properties.sku
softDeleteRetentionInDays: keyVault.properties.softDeleteRetentionInDays
tenantId: keyVault.properties.tenantId
}
}
scope: resourceGroup(keyVault.resourceGroupName)
}]
// Outputs to return the names and URIs of the deployed Key Vaults
output keyVaultInfo array = [for keyVault in keyVaultArray: {
name: '${keyVault.name}-${keyVault.environment}'
uri: 'https://${keyVault.name}-${keyVault.environment}${environment().suffixes.keyvaultDns}'
}]
Important: This module covers almost all parameters used in Azure Key Vault deployment, except key rotation policies. I explained the implementation of key rotation policies in another article, which you can find here.
After creating main.bicep, create a modules folder in your working directory. This folder will contain all modular Bicep files, making organizing and reusing them easier.
To proceed, create a new file named kv.bicep in the modules folder. This file acts as a sub-module that is called by main.bicep to manage the creation of each Azure Key Vault. It has its own set of parameters and sets default values for certain attributes like defaultSkuFamily and defaultEnableRbacAuthorization.
The kv.bicep file defines parameters to configure each Key Vault, setting up and filling properties of the Azure resource type Microsoft.KeyVault/vaults using both parameters and hardcoded default values, similar to main.bicep.
@description('Name of the Key Vault.')
param keyVaultName string
@description('Azure location where the Key Vault will be deployed.')
param location string
@description('Tags to be associated with the Key Vault.')
param tags object
@description('Properties for configuring the Key Vault.')
param properties object
// Defining default values for specific properties
var defaultSkuFamily = 'A'
var defaultEnableRbacAuthorization = true
resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' = {
name: keyVaultName
location: location
tags: tags
properties: {
enabledForDeployment: properties.enabledForDeployment
enabledForDiskEncryption: properties.enabledForDiskEncryption
enabledForTemplateDeployment: properties.enabledForTemplateDeployment
enablePurgeProtection: properties.enablePurgeProtection
enableRbacAuthorization: defaultEnableRbacAuthorization
enableSoftDelete: properties.enableSoftDelete
networkAcls: properties.networkAcls
publicNetworkAccess: properties.publicNetworkAccess
sku: {
family: defaultSkuFamily
name: properties.sku.name
}
softDeleteRetentionInDays: properties.softDeleteRetentionInDays
tenantId: properties.tenantId
}
}
Deployment scope #
In our Bicep-based Key Vault deployment module, you have the flexibility to deploy multiple Key Vault instances across various resource groups while executing the deployment at the subscription level. This uses Bicep’s ability to deploy to various scopes, including subscriptions, management groups, and tenants.
In this context, even though the deployment is initiated at the subscription level, we strategically scope each individual Key Vault to its specified resource group.
This approach provides both the broad oversight of subscription-level management and the granularity of resource group-specific deployments.
Deploy the Bicep template using the Azure CLI #
Once the scope is established, we can proceed to deploy the template via the Azure CLI. To do so, run the following commands.
Parameters #
Personalization is key to making your template reusable. With the parameters, you can easily tailor the template to your specific needs. You can use either inline parameters or a parameter file to pass parameter values. In my case, I will use a file to pass the parameters; here is an example.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"keyVaultArray": {
"value": [
{
"name": "KV-DEMO",
"location": "westeurope",
"resourceGroupName": "RG-DEMO-WE",
"environment": "dev",
"properties": {
"enabledForDeployment": true,
"enabledForDiskEncryption": true,
"enabledForTemplateDeployment": true,
"enablePurgeProtection": false,
"enableSoftDelete": true,
"networkAcls": {
"bypass": "AzureServices",
"defaultAction": "Allow",
"ipRules": [],
"virtualNetworkRules": []
},
"publicNetworkAccess": "Enabled",
"sku": {
"name": "standard"
},
"softDeleteRetentionInDays": 7,
"tenantId": "00000000-0000-0000-0000-0000000000"
}
},
{
"name": "KV-DEMO",
"location": "northeurope",
"resourceGroupName": "RG-DEMO-NE",
"environment": "prod",
"properties": {
"enabledForDeployment": true,
"enabledForDiskEncryption": true,
"enabledForTemplateDeployment": true,
"enablePurgeProtection": false,
"enableSoftDelete": true,
"networkAcls": {
"bypass": "AzureServices",
"defaultAction": "Deny",
"ipRules": [
{"value":"192.168.123.124"}
],
"virtualNetworkRules": []
},
"publicNetworkAccess": "Enabled",
"sku": {
"name": "premium"
},
"softDeleteRetentionInDays": 7,
"tenantId": "00000000-0000-0000-0000-0000000000"
}
}
]
},
"tags": {
"value": {
"bicep": "true",
"app": "demo"
}
}
}
}
Preview changes #
Before deploying a Bicep file, you can preview the changes that will occur to your resources. Using what-if operations does not change existing resources; it simply shows you an output that includes color-coded results that allow you to see different changes.
az deployment sub what-if \
--template-file <filename>.bicep \
--parameters @<filename>.parameters.json \
--location <location>
Deploy the Azure resource #
Finally, to deploy the template, run the following command.
az deployment sub create \
--template-file <filename>.bicep \
--parameters @<filename>.parameters.json \
--location <location>
Validate the deployment #
To verify that the resources were created correctly, you can use the Azure Portal or the Azure CLI to check the created resources and their configurations. For Azure CLI, use the following command.
az keyvault list \
--query "[].{name: name, resourceGroup: resourceGroup}" \
--output table
References and useful links #
Thank you for taking the time to read my post. I sincerely hope that you find it helpful.