Setting up Container Registry with Customer-Managed Keys

So yesterday, I posted a blog post about setting up azure storage accounts for Customer-Managed keys. You can see that post here. Now, I will say the implementation of this with regard to container registry is tricky because it doesn’t look the same for container registry.

Setting up Key Vault

The following are the required steps for setting up a keyvault:

param location string
param key_vault_name string
param private_dns_zone_name string = 'privatelink.vaultcore.azure.net'
param key_vault_sku string = 'standard'
param key_vault_sku_family string = 'A'
param subnet_id string
param vnet_id string 

// Managed Identity Configuration:
param registry_managed_identity_name string = 'registry-managed-identity'

// Role Configuration
param registry_account_role_definition_id string = 'e147488a-f6f5-4113-8e2d-b22465e65bf6' // Key Vault Crypto Service Encryption User

// Key Configuration:
param registry_account_key_name string

// Tag Configuration:
param default_tag_name string
param default_tag_value string

var randomSuffix = uniqueString(resourceGroup().id, key_vault_name)
var maxLength = 24
var truncatedKeyVaultName = substring('${key_vault_name}-${randomSuffix}', 0, maxLength)
//var randomSuffix = uniqueString(subscription().subscriptionId, location)

resource key_vault 'Microsoft.KeyVault/vaults@2022-07-01' = {
  name: truncatedKeyVaultName
  location: location
  tags: {
    '${default_tag_name}': default_tag_value
  }
  properties: {
    sku: {
      name: key_vault_sku
      family: key_vault_sku_family
    }
    tenantId: subscription().tenantId
    accessPolicies: []
    enabledForDeployment: true 
    enabledForDiskEncryption: true
    enabledForTemplateDeployment: true 
    enableSoftDelete: true
    softDeleteRetentionInDays: 90
    enableRbacAuthorization: true 
    enablePurgeProtection: true 
    publicNetworkAccess: 'Disabled'
    networkAcls: {
      defaultAction: 'Deny'
      ipRules: []
      virtualNetworkRules: []
    }
  }
}

// DNS Configuration:
resource private_dns_zone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: private_dns_zone_name
  location: 'global'
  tags: {
    '${default_tag_name}': default_tag_value
  }
}

resource private_endpoint 'Microsoft.Network/privateEndpoints@2022-05-01' = {
  name: '${key_vault.name}-pe'
  location: location
  tags: {
    '${default_tag_name}': default_tag_value
  }
  properties: {
    privateLinkServiceConnections: [
      {
        name: '${key_vault.name}-plsc'
        properties: {
          privateLinkServiceId: key_vault.id
          groupIds: [
            'vault'
          ]
        }
      }
    ]
    subnet: {
      id: subnet_id
    }
  }
}

resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2018-09-01' = {
  name: '${private_dns_zone.name}-link'
  tags: {
    '${default_tag_name}': default_tag_value
  }
  parent: private_dns_zone
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: vnet_id
    }
  }
}

resource private_dns_zone_group 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-09-01' = {
  name: 'default'
  parent: private_endpoint
  properties: {
    privateDnsZoneConfigs: [
      {
        name: 'keyVaultConfig1'
        properties: {
          privateDnsZoneId: private_dns_zone.id
        }
      }
    ]
  }
}

And then you need to create the managed identity:

resource registry_managed_identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
  name: registry_managed_identity_name
  location: resourceGroup().location
  tags: {
    '${default_tag_name}': default_tag_value
  }
}

And then create the role assignment:

resource registry_roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  scope: key_vault
  name: guid(key_vault.id, registry_managed_identity.id, registry_account_role_definition_id)
  properties: {
    roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/${registry_account_role_definition_id}' // Key Vault Crypto Service Encryption User
    principalId: registry_managed_identity.properties.principalId
  }
}

And then you need to create the key:

resource registry_key 'Microsoft.KeyVault/vaults/keys@2024-11-01' = {
  parent: key_vault
  name: registry_account_key_name
  properties: {
    attributes: {
      enabled: true
      exportable: false
    }
    kty: 'RSA'
    keySize: 2048
  }
}

Configure the registry:

The following then in the module that will setup for customer-managed keys for the container registry:

param acr_name string
param location string
param sku string = 'Premium'
param private_dns_zone_name string = 'privatelink.azurecr.io'
param subnetId string
param vnet_id string 

// Identity Configuration:
param registry_managed_identity_id string 
param registry_managed_identity_principal_id string 
param registry_managed_identity_client_id string
param registry_role_definition_id string = 'e147488a-f6f5-4113-8e2d-b22465e65bf6' // AcrKeyVaultReader role for Azure Government

// Key Vault Configuration:
param registry_key_uri string // Key Vault URI for the customer-managed key

param default_tag_name string
param default_tag_value string

resource container_registry 'Microsoft.ContainerRegistry/registries@2021-06-01-preview' = {
  name: acr_name
  location: location
  tags: {
    '${default_tag_name}': default_tag_value
  }
  sku: {
    name: sku
  }
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${registry_managed_identity_id}': {}
    }
  }
  properties: {
    adminUserEnabled: false
    publicNetworkAccess: 'Disabled'
    encryption: {
      status: 'Enabled'
      keyVaultProperties: {
        identity: registry_managed_identity_client_id
        keyIdentifier: registry_key_uri
      }
    }
    policies: {
      quarantinePolicy: {
        status: 'disabled'
      }
      trustPolicy: {
        type: 'Notary'
        status: 'disabled'
      }
      retentionPolicy: {
        days: 7
        status: 'disabled'
      }
    }
  }
}

resource private_endpoint 'Microsoft.Network/privateEndpoints@2020-11-01' = {
  name: '${acr_name}-pe'
  location: location
  tags: {
    '${default_tag_name}': default_tag_value
  }
  properties: {
    privateLinkServiceConnections: [
      {
        name: '${acr_name}-plsc'
        properties: {
          privateLinkServiceId: container_registry.id
          groupIds: [
            'registry'
          ]
        }
      }
    ]
    subnet: {
      id: subnetId
    }
  }
}

resource private_dns_zone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: private_dns_zone_name
  location: 'global'
  tags: {
    '${default_tag_name}': default_tag_value
  }
}

resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2018-09-01' = {
  name: '${private_dns_zone.name}-link'
  parent: private_dns_zone
  location: 'global'
  tags: {
    '${default_tag_name}': default_tag_value
  }
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: vnet_id
    }
  }
}

resource registry_dns_zone_group 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2020-11-01' = {
  name: 'default'
  parent: private_endpoint
  properties: {
    privateDnsZoneConfigs: [
      {
        name: 'config1'
        properties: {
          privateDnsZoneId: private_dns_zone.id
        }
      }
    ]
  }
}

And then make sure to add the following to create the appropriate role assignment:

resource acr_role_assignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
  name: guid(container_registry.id, registry_managed_identity_id, 'AcrKeyVaultReader')
  scope: container_registry
  properties: {
    roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', registry_role_definition_id) // AcrKeyVaultReader role
    principalId: registry_managed_identity_principal_id
  }
}

If you want a full version of the file, look key-vault.bicep, registry.bicep, and main.bicep.

Leave a Reply

Your email address will not be published. Required fields are marked *