
Configuring Customer Managed Keys in Azure
Table of Contents
What are Azure Customer Managed Keys?
Customer Managed Keys (CMK) in Azure are keys stored in Azure Key Vault or Azure Managed HSM (Hardware Security Module) that you use to encrypt your data at rest stored in Azure services (e.g. Storage Accounts, CosmosDB Databases, AI Services, etc) that Microsoft do not have access to.
By default, most Azure services use Microsoft-managed encryption keys. This means that Microsoft retains control of the encryption keys. In some industries, such as healthcare, law enforcement, finance, and defence, organisations require full control of their encryption keys to meet regulatory or data-sovereignty requirements.
Using Customer Managed Keys gives you full control over what identities can decrypt your data and the life-cycle of those keys. Unless you explicitly grant access to the Customer Managed Key to an identity, that identity cannot decrypt your data, not even Microsoft.
If the Customer Managed Key is disabled or deleted then it is no longer possible to decrypt that data. Therefore taking steps such as enabling soft delete and purge protection on your Key Vault to prevent accidental deletion is essential to guard against data loss.
Typical Implementation
Assuming you already have a Key Vault deployed using the RBAC access model, a typical implementation pattern is as follows:
- Create or identify a managed identity (User‑Assigned, System‑Assigned, Platform provided depending on the Azure service). This identity will access the Customer Managed Key stored in the Key Vault.
- Assign the managed identity the required Key Vault RBAC role, typically “Key Vault Crypto User”, “Key Vault Crypto Officer”, or “Key Vault Crypto Service Encryption User”, depending on the service.
- Configure the Azure service you wish to protect with the Customer Managed Key to use the User Assigned Managed Identity you just created.
- Ensure that the Azure service has network access to your Key Vault - either through allowing Trusted Azure Services access, or allowing the Subnet ID of the service if using a private endpoint.
- Configure the Azure service to use a Customer Managed Key stored in your Key Vault
Terraform Example
Below is a example using Terraform to configure a Storage Account to use a Customer Managed Key. You can find a full working repo with provider configuration and variables etc. here
A couple of things of interest here:
- We are assigning the principal running the terraform the “Key Vault Administrator” role so we can manage the Key Vault and add keys
- We are adding our IP address in CIDR notation via the
admin_ip_addressesvariable so we can restrict network access to the Key Vault - We have a pause that depends on the “Key Vault Crypto Service Encryption User” role assignment to the managed identity used by the Storage Account, and we then have an explicit depends on configured on the Storage Account on this pause to ensure that we give some time for the role assignment to take effect.
- We have Purge Protection and Soft Delete configured on the Key Vault to protect against accidental deletion.
# --------------------------
# Set Up
# --------------------------
resource "random_string" "suffix" {
length = 6
upper = false
special = false
}
data "azurerm_client_config" "current" {}
locals {
storage_account_name = "stcmkdemo${random_string.suffix.result}"
key_vault_name = "kv${random_string.suffix.result}"
storage_account_mi_name = "id-stcmkdemo${random_string.suffix.result}"
}
# --------------------------
# Resource Group
# --------------------------
resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = var.location
}
# --------------------------
# User Assigned Identity for CMK
# --------------------------
resource "azurerm_user_assigned_identity" "cmk_identity" {
name = local.storage_account_mi_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
# --------------------------
# Key Vault
# --------------------------
resource "azurerm_key_vault" "kv" {
name = local.key_vault_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
purge_protection_enabled = true
soft_delete_retention_days = 90
rbac_authorization_enabled = true
public_network_access_enabled = true
network_acls {
bypass = "AzureServices"
default_action = "Deny"
ip_rules = var.admin_ip_addresses
}
}
resource "azurerm_role_assignment" "kv_admin" {
scope = azurerm_key_vault.kv.id
role_definition_name = "Key Vault Administrator"
principal_id = data.azurerm_client_config.current.object_id
}
resource "time_sleep" "wait_for_kv_admin" {
depends_on = [azurerm_role_assignment.kv_admin]
create_duration = "60s"
}
# --------------------------
# Key Vault Key (CMK)
# --------------------------
resource "azurerm_key_vault_key" "cmk" {
depends_on = [time_sleep.wait_for_kv_admin]
name = "sa-cmk-key"
key_vault_id = azurerm_key_vault.kv.id
key_type = "RSA"
key_size = 4096
key_opts = [
"decrypt",
"encrypt",
"sign",
"unwrapKey",
"verify",
"wrapKey",
]
}
# --------------------------
# Storage Account with CMK
# --------------------------
resource "azurerm_storage_account" "sa" {
depends_on = [time_sleep.wait_for_role_assignment]
name = local.storage_account_name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = "Standard"
account_replication_type = "LRS"
min_tls_version = "TLS1_2"
allow_nested_items_to_be_public = false
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.cmk_identity.id]
}
customer_managed_key {
key_vault_key_id = azurerm_key_vault_key.cmk.id
user_assigned_identity_id = azurerm_user_assigned_identity.cmk_identity.id
}
}
# --------------------------
# RBAC: allow User Assigned Identity to use the CMK
# --------------------------
resource "azurerm_role_assignment" "sa_kv_crypto" {
scope = azurerm_key_vault.kv.id
role_definition_name = "Key Vault Crypto Service Encryption User"
principal_id = azurerm_user_assigned_identity.cmk_identity.principal_id
}
resource "time_sleep" "wait_for_role_assignment" {
depends_on = [azurerm_role_assignment.sa_kv_crypto]
create_duration = "60s"
}