Skip to main content
Jorge Bernhardt Jorge Bernhardt
  1. Posts/

Bicep - Azure Key Vault Deployments in Multiple Environments

·1035 words·5 mins· 100 views · 5 likes ·
Azure Key Vault Azure CLI Microsoft Microsoft Azure

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>

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>

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>

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>

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>

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>

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>

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>

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.