Header image

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_addresses variable 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"
}