Setting up Customer-Managed Keys in Bicep for Storage

So for security levels, especially in government when you starting talking Impact Levels of 4+, one of the key requirements is customer-managed keys. The reason for this being is that it ensures that no one can gain access to the encrypted data without using a key that yourself control, not even a cloud service provider. So the question becomes how to enable that in bicep.

Setting up the key vault

So the first step of that, is how do we configure a keyvault with managed identity to support this. You can do that with the following:

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 storage_managed_identity_name string = 'storage-managed-identity'

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

// Key Configuration:
param storage_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: []
    }
  }
}

Next step is to setup the managed identity, which can be done in the following manner:

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

Next you need to configure the role assignment for “Key Vault Crypto Service Encryption User”:

// Managed Identity Role Assignments
resource storage_roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  scope: key_vault
  name: guid(key_vault.id, storage_managed_identity.id, storage_account_role_definition_id)
  properties: {
    roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/${storage_account_role_definition_id}' // Key Vault Crypto Service Encryption User
    principalId: storage_managed_identity.properties.principalId
  }
}

And then we need to create the key in the keyvault to be used:

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

Setting up the storage account

Next up, we have to configure the storage account, which can be done in the following:

param location string = resourceGroup().location
param storage_account_name string
param privateDnsZoneName string = 'privatelink.${environment().suffixes.storage}'
param subnet_id string
param vnet_id string 
param default_tag_name string
param default_tag_value string

// Key Vault Configuration:
param key_vault_uri string // Key Vault URI for the customer-managed key
param key_name string // Key name in the Key Vault

// Identity Configuration:
param storage_managed_identity_id string

resource storage_account 'Microsoft.Storage/storageAccounts@2022-05-01' = {
  name: storage_account_name
  location: location
  tags: {
    '${default_tag_name}': default_tag_value
  }
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${storage_managed_identity_id}': {}
    }
  }
  properties: {
    publicNetworkAccess: 'Disabled'
    supportsHttpsTrafficOnly: true
    allowBlobPublicAccess: false
    allowSharedKeyAccess: false
    encryption: {
      identity: {
        userAssignedIdentity: storage_managed_identity_id
      }
      keySource: 'Microsoft.Keyvault'
      keyvaultproperties: {
        keyvaulturi: key_vault_uri
        keyname: key_name
      }
      services: {
        file: {
          keyType: 'Account'
          enabled: true
        }
        table: {
          keyType: 'Account'
          enabled: true
        }
        queue: {
          keyType: 'Account'
          enabled: true
        }
        blob: {
          keyType: 'Account'
          enabled: true
        }
      }
    }
    networkAcls: {
      defaultAction: 'Deny'
      bypass: 'AzureServices'
      ipRules: []
      virtualNetworkRules: []
      resourceAccessRules: []
    }
  }
}

For this to work, you need to associate the identity with the storage account AND configure it in the encryption section to make this success. f

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

Hope this helps!

Leave a Reply

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