Back to blog
AzureBest PracticesCloud SecurityCompute & ContainersOperations & Compliance

Azure Container Registry Not Encrypted With a Customer-Managed Key

Learn why Azure Container Registries need customer-managed key (CMK) encryption, the risks of platform keys, and step-by-step CLI, Terraform, and policy fixes.

TL;DR

This check flags Azure Container Registries that rely on Microsoft-managed keys instead of a customer-managed key (CMK). Without a CMK you lose control over key rotation, revocation, and audit. Enable encryption with a key stored in Azure Key Vault on a Premium-tier registry to take ownership of your image data's encryption lifecycle.

Azure Container Registry (ACR) stores the container images, Helm charts, and OCI artifacts that feed your build and deployment pipelines. Those images often contain proprietary code, embedded configuration, and sometimes secrets that should never have been baked in but occasionally are. By default, Azure encrypts everything at rest using platform-managed keys. That is fine for a baseline, but it leaves the key entirely in Microsoft's hands. For regulated workloads or anything with a strict data-governance posture, that is usually not enough.

The acr_nocmk check (module acr_checks) identifies registries that have not been configured with a customer-managed key. This post explains what the check looks for, why it matters, and exactly how to remediate and prevent it.


What this check detects

The check inspects each Azure Container Registry in your subscription and reports any registry whose encryption property is either absent or set to Disabled. When CMK encryption is off, ACR falls back to encryption with a Microsoft-managed key.

In concrete terms, a passing registry has an encryption block that references a Key Vault key and is bound to a managed identity that can read that key. A failing registry has nothing of the sort.

Note: Customer-managed key encryption for ACR requires the Premium service tier. Basic and Standard registries cannot use CMK at all, so this check often goes hand in hand with a tier upgrade.

A few facts about how CMK works in ACR are worth knowing before you start:

  • The key lives in Azure Key Vault (or Managed HSM), not in the registry.
  • ACR uses a managed identity to wrap and unwrap its internal data encryption keys with your key.
  • Soft delete and purge protection must be enabled on the Key Vault, otherwise ACR will refuse to use the key.
  • CMK must be configured at registry creation time, or enabled later if you used a user-assigned identity. Switching encryption on existing registries has constraints, covered below.

Why it matters

Platform-managed encryption protects against one specific threat: someone walking off with a physical disk from an Azure data center. It does nothing for the scenarios that actually keep security teams up at night.

You cannot revoke access in an incident

With a Microsoft-managed key, you have no way to cut off access to your encrypted data. If you suspect a registry has been compromised, or a third party with subscription access has gone rogue, your only levers are RBAC and network controls. With a CMK, you can disable or delete the key in Key Vault and the registry data becomes cryptographically inaccessible almost immediately. That is a hard stop you control.

Key rotation is on your schedule

Many compliance frameworks (PCI DSS, HIPAA-aligned controls, FedRAMP, and various internal data classification policies) require demonstrable control over encryption key rotation. A platform-managed key rotates on Microsoft's cadence and you have no visibility into it. A CMK rotates when you say so, and the event lands in your Key Vault audit logs.

Auditability and separation of duties

Key Vault produces detailed logs of every key operation: who unwrapped what, and when. That gives you an independent audit trail tied to your identity, separate from the team that manages the registry. Splitting key custody from registry custody is a meaningful separation-of-duties control.

Warning: CMK is powerful precisely because losing the key means losing the data. If you delete or let the key expire without purge protection and a recovery plan, your registry images become permanently unreadable. Treat the Key Vault holding ACR keys as a tier-zero asset.

Real-world business impact

Container images frequently leak more than people expect. A build that copies in a .env file, an image layer with cached credentials, or source code that reveals internal architecture all become part of the registry's encrypted-at-rest data. If an auditor asks "who can decrypt your container images and how do you prove it," you want a Key Vault answer, not a shrug.


How to fix it

Remediation has three building blocks: a Key Vault with the right protections, a managed identity that can use the key, and the registry configured to use both. The cleanest path is to provision everything together. Below is the full sequence using the Azure CLI.

Step 1: Create a Key Vault with soft delete and purge protection

RG="prod-acr-rg"
LOCATION="eastus"
KV_NAME="kv-acr-prod-01"
KEY_NAME="acr-cmk-key"

az keyvault create \
  --name "$KV_NAME" \
  --resource-group "$RG" \
  --location "$LOCATION" \
  --enable-soft-delete true \
  --enable-purge-protection true

az keyvault key create \
  --vault-name "$KV_NAME" \
  --name "$KEY_NAME" \
  --kty RSA \
  --size 3072

Step 2: Create a user-assigned managed identity

A user-assigned identity is the recommended choice because it lets you grant key access before the registry exists and makes rotation of identities easier later.

IDENTITY_NAME="id-acr-cmk"

az identity create \
  --name "$IDENTITY_NAME" \
  --resource-group "$RG"

IDENTITY_ID=$(az identity show -g "$RG" -n "$IDENTITY_NAME" --query id -o tsv)
IDENTITY_PRINCIPAL=$(az identity show -g "$RG" -n "$IDENTITY_NAME" --query principalId -o tsv)
IDENTITY_CLIENT=$(az identity show -g "$RG" -n "$IDENTITY_NAME" --query clientId -o tsv)

Step 3: Grant the identity access to the key

If your Key Vault uses RBAC (the modern default), assign the Key Vault Crypto Service Encryption User role. If it uses access policies, grant get, wrapKey, and unwrapKey permissions instead.

# RBAC permission model
KV_ID=$(az keyvault show -n "$KV_NAME" -g "$RG" --query id -o tsv)

az role assignment create \
  --assignee "$IDENTITY_PRINCIPAL" \
  --role "Key Vault Crypto Service Encryption User" \
  --scope "$KV_ID"

Step 4: Create (or recreate) the registry with CMK

Grab the key ID, then create a Premium registry with encryption enabled.

KEY_ID=$(az keyvault key show \
  --vault-name "$KV_NAME" \
  --name "$KEY_NAME" \
  --query 'key.kid' -o tsv)

az acr create \
  --name "acrprod01" \
  --resource-group "$RG" \
  --sku Premium \
  --identity "$IDENTITY_ID" \
  --key-encryption-key "$KEY_ID" \
  --location "$LOCATION"

Danger: You cannot retrofit CMK onto a registry that was created with a system-assigned identity or no identity at all in every scenario. If your existing registry cannot be converted, you may need to create a new registry and re-push or import images, then delete the old one. Deleting a registry is irreversible and breaks any pipeline pulling from it, so coordinate the cutover and update all references first.

Enabling CMK on an existing registry

If the registry already has the user-assigned identity attached and that identity has key access, you can rotate it onto CMK:

az acr encryption rotate-key \
  --name "acrprod01" \
  --resource-group "$RG" \
  --key-encryption-key "$KEY_ID" \
  --identity "$IDENTITY_CLIENT"

Verify the result

az acr encryption show \
  --name "acrprod01" \
  --resource-group "$RG" -o table

A correctly configured registry returns status: enabled along with the key vault key URI.

Tip: Enable auto-rotation by passing a versionless key ID (drop the version suffix from the kid). ACR will then automatically pick up new key versions when you rotate the key in Key Vault, so you never have to run rotate-key manually again.


Doing it in infrastructure as code

Click-ops and one-off CLI runs drift. Define the registry, identity, and key binding in code so every environment is consistent and reviewable.

Terraform

resource "azurerm_user_assigned_identity" "acr" {
  name                = "id-acr-cmk"
  resource_group_name = azurerm_resource_group.acr.name
  location            = azurerm_resource_group.acr.location
}

resource "azurerm_role_assignment" "acr_key" {
  scope                = azurerm_key_vault.acr.id
  role_definition_name = "Key Vault Crypto Service Encryption User"
  principal_id         = azurerm_user_assigned_identity.acr.principal_id
}

resource "azurerm_container_registry" "acr" {
  name                = "acrprod01"
  resource_group_name = azurerm_resource_group.acr.name
  location            = azurerm_resource_group.acr.location
  sku                 = "Premium"

  identity {
    type         = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.acr.id]
  }

  encryption {
    key_vault_key_id   = azurerm_key_vault_key.acr.id
    identity_client_id = azurerm_user_assigned_identity.acr.client_id
  }

  depends_on = [azurerm_role_assignment.acr_key]
}

Warning: The depends_on the role assignment matters. If Terraform tries to create the registry before the identity can read the key, ACR creation fails. Make the ordering explicit rather than relying on implicit dependencies.

Bicep

resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
  name: 'acrprod01'
  location: location
  sku: {
    name: 'Premium'
  }
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${acrIdentity.id}': {}
    }
  }
  properties: {
    encryption: {
      status: 'enabled'
      keyVaultProperties: {
        keyIdentifier: keyVaultKeyUri
        identity: acrIdentity.properties.clientId
      }
    }
  }
}

How to prevent it from happening again

Fixing one registry is easy. Stopping the next non-compliant registry from being created is what actually moves your risk down. Use a layered approach.

1. Azure Policy as a guardrail

Assign a policy that audits or denies registries without CMK encryption. There is a built-in policy named Container registries should be encrypted with a customer-managed key (CMK). Start in Audit mode to find existing offenders, then move to Deny once teams are ready.

az policy assignment create \
  --name "acr-require-cmk" \
  --display-name "Require CMK on Container Registries" \
  --policy "5b9159ae-1701-4a6f-9a7a-aa9c8ddd0580" \
  --scope "/subscriptions/$SUBSCRIPTION_ID" \
  --params '{ "effect": { "value": "Deny" } }'

Note: The policy GUID above is the well-known ID for the ACR CMK built-in definition. Confirm it in your tenant with az policy definition list --query "[?contains(displayName, 'customer-managed key')]" since IDs occasionally change between built-in versions.

2. Block it in CI/CD before deployment

If you provision infrastructure through pipelines, scan the IaC before it ever reaches Azure. Tools like Checkov, tfsec, and Terrascan all ship rules for ACR CMK. A pre-merge check fails the build when an encryption block is missing.

# Example: fail the pipeline on a missing ACR CMK check
checkov -d ./infra --check CKV_AZURE_164

3. Continuous monitoring with Lensix

Policies and pipeline scans catch what passes through them. Drift, manual changes, and resources created outside your pipelines still happen. Lensix runs acr_nocmk continuously across every subscription so a registry that slips through gets flagged with the rest of your posture, not discovered during an audit.


Best practices

  • Use a user-assigned identity for the registry. It decouples key access from the registry lifecycle and makes CMK far easier to enable and rotate.
  • Always enable purge protection on the Key Vault holding ACR keys. Without it, an accidental key deletion is unrecoverable and so is your registry data.
  • Use versionless key references so key rotation in Key Vault flows automatically into the registry.
  • Keep the Key Vault in the same region as the registry to avoid cross-region latency and availability coupling.
  • Restrict who can manage the key separately from who manages the registry. That separation is the whole point of CMK.
  • Pair CMK with network isolation. Encryption at rest protects stored data, but a private endpoint and disabled public access protect data in transit and reduce the attack surface.
  • Document your recovery runbook. If the key is ever disabled or deleted, the team should know exactly how to recover from soft delete and re-enable access.

Customer-managed key encryption on ACR is a control you set once and benefit from continuously. It moves the encryption key, the most sensitive part of protecting your container images, into a vault you own, audit, and can revoke. Combined with policy guardrails and continuous checks, it turns "we trust the platform" into "we control and can prove it."