Reputation: 1007
I am trying to use bicep to create an Azure storage account that uses a customer managed key. I have got something at the moment, but it is very messy, you have to run 3 x deployments and then a manual change to the storage account to link it with the key created within key vault. The last step involves running a PowerShell script that will rotate the key.
What am i trying to achieve ?
A simple modularised script that uses modules to achieve the above. The network configuration already exists and so does the key vault.
From what I have read up. It needs to be in the order below.
I have got a template below which will almost achieve the above but without the private endpoint.
param keyName string = ''
param keyVersion string = ''
param vaultName string = ''
param location string = resourceGroup().location
param accountName string = 'tetsdfgfgdffd'
resource storageAcc 'Microsoft.Storage/storageAccounts@2019-06-01' = {
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
kind: 'Storage'
name: accountName
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
supportsHttpsTrafficOnly: true
}
dependsOn: []
}
resource keyVault 'Microsoft.KeyVault/vaults@2016-10-01' = {
name: vaultName
location: 'eastasia'
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
accessPolicies: []
enabledForDeployment: true
enabledForDiskEncryption: true
enabledForTemplateDeployment: true
enableSoftDelete: true
}
}
module updateStorageAccount './enableEncryption.bicep' = {
name: 'updateStorageAccount'
params: {
msiObjectId: storageAcc.identity.principalId
vaultUri: keyVault.properties.vaultUri
vaultName: vaultName
accountName: accountName
location: location
keyName: keyName
keyversion: keyVersion
}
}
What have I got working so far ?
This creates the storage account.
var privateStorageBlobDnsZoneName = 'privatelink.blob.${environment().suffixes.storage}'
var privateEndpointStorageBlobName = 'pep-${blobStorageAccountName}-blob-001'
var subnet = resourceId(vnetRG, 'Microsoft.Network/virtualNetworks/subnets', vnetName, subnetName)
var commonTags = {
Application: applicationTag
Criticality: criticalityTag
Department: departmentTag
Environment: environmentTag
Owner: ownerTag
}
var appliedTags = union(commonTags, tags)
// Private Endpoints
resource privateEndpointStorageBlobName_blobPrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-05-01' = {
parent: privateEndpointStorageBlob
name: 'blobPrivateDnsZoneGroup'
properties: {
privateDnsZoneConfigs: [
{
name: 'config'
properties: {
privateDnsZoneId: extensionResourceId('/subscriptions/${dnsZoneSubscriptionId}/resourceGroups/${dnsZoneRG}', 'Microsoft.Network/privateDnsZones', privateStorageBlobDnsZoneName)
}
}
]
}
}
resource privateEndpointStorageBlob 'Microsoft.Network/privateEndpoints@2021-05-01' = {
name: privateEndpointStorageBlobName
location: location
properties: {
subnet: {
id: subnet
}
customNetworkInterfaceName: '${privateEndpointStorageBlobName}-nic'
privateLinkServiceConnections: [
{
name: 'MyStorageBlobPrivateLinkConnection'
properties: {
privateLinkServiceId: blobStorageAccount.id
groupIds: [
'blob'
]
}
}
]
}
tags: appliedTags
}
resource blobStorageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: blobStorageAccountName
location: location
tags: appliedTags
kind: 'StorageV2'
sku: {
name: skuName
}
identity: {
type: 'SystemAssigned'
}
properties: {
accessTier: accessTier
publicNetworkAccess: 'Disabled'
allowBlobPublicAccess: false
isHnsEnabled: isHnsEnabled
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Deny'
}
}
}
// Storage Account SystemAssigned Managed Identity
@description('The Principal ID of the System Assigned Managed Identity for the new Storage Account.')
output storageAccount_ManagedIdentity string = blobStorageAccount.identity.principalId
Attach the private end point to the created storage account.
var privateStorageBlobDnsZoneName = 'privatelink.blob.${environment().suffixes.storage}'
var privateEndpointStorageBlobName = 'pep-${blobStorageAccountName}-blob-001'
var subnet = resourceId(vnetRG, 'Microsoft.Network/virtualNetworks/subnets', vnetName, subnetName)
var commonTags = {
Application: applicationTag
Criticality: criticalityTag
Department: departmentTag
Environment: environmentTag
Owner: ownerTag
}
var appliedTags = union(commonTags, tags)
// Private Endpoints
resource privateEndpointStorageBlobName_blobPrivateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-05-01' = {
parent: privateEndpointStorageBlob
name: 'blobPrivateDnsZoneGroup'
properties: {
privateDnsZoneConfigs: [
{
name: 'config'
properties: {
privateDnsZoneId: extensionResourceId('/subscriptions/${dnsZoneSubscriptionId}/resourceGroups/${dnsZoneRG}', 'Microsoft.Network/privateDnsZones', privateStorageBlobDnsZoneName)
}
}
]
}
}
resource privateEndpointStorageBlob 'Microsoft.Network/privateEndpoints@2021-05-01' = {
name: privateEndpointStorageBlobName
location: location
properties: {
subnet: {
id: subnet
}
customNetworkInterfaceName: '${privateEndpointStorageBlobName}-nic'
privateLinkServiceConnections: [
{
name: 'MyStorageBlobPrivateLinkConnection'
properties: {
privateLinkServiceId: blobStorageAccount.id
groupIds: [
'blob'
]
}
}
]
}
tags: appliedTags
}
resource blobStorageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: blobStorageAccountName
location: location
tags: appliedTags
kind: 'StorageV2'
sku: {
name: skuName
}
identity: {
type: 'SystemAssigned'
}
properties: {
accessTier: accessTier
publicNetworkAccess: 'Disabled'
allowBlobPublicAccess: false
isHnsEnabled: isHnsEnabled
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Deny'
}
}
}
// Storage Account SystemAssigned Managed Identity
@description('The Principal ID of the System Assigned Managed Identity for the new Storage Account.')
output storageAccount_ManagedIdentity string = blobStorageAccount.identity.principalId
This creates the key within an existing vault.
param keyVaultname string
param keyName string
param keyOps array = [
'sign'
'verify'
'encrypt'
'decrypt'
'wrapKey'
'unwrapKey'
]
param keyType string = 'RSA'
param keySize int = 3072
param versionExpiryDays int = 180
param rotateBeforeExpiry int = 30
// Resource Lookups
resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = {
name: keyVaultname
}
resource kvKey 'Microsoft.KeyVault/vaults/keys@2023-02-01' = {
parent: keyVault
name: keyName
properties: {
keyOps: keyOps
attributes: {
enabled: true
}
keySize: keySize
kty: keyType
rotationPolicy:{
attributes: {
expiryTime: 'P${versionExpiryDays}D'
}
lifetimeActions: [
{
action: {
type: 'rotate'
}
trigger: {
timeBeforeExpiry: 'P${rotateBeforeExpiry}D'
}
}
]
}
}
}
// Outputs
output keyVersionUri string = kvKey.properties.keyUriWithVersion
output keyUri string = kvKey.properties.keyUri
output keyName string = kvKey.name
Then I have to manually link the key to the storage account configuration. The entire process is long winded and sub optimal. The last step is a PS script that I have to run to rotate the key.
Now rotate the key using Ps, ideally from the output of the bicep code such that its not a manual step
# Connect-AzAccount
$keyVaultsName = @('my-kv')
foreach ($KeyVaultName in $KeyVaultsName)
{
$keys = Get-AzKeyVaultKey -VaultName $KeyVaultName
foreach ($key in $keys) {
# Rotate the key
$newKeyVersion = Invoke-AzKeyVaultKeyRotation -VaultName $KeyVaultName -KeyName $key.Name
Write-Host "Key rotation completed for $($key.Name). New version: $($newKeyVersion.Version)"
}
Write-Host "Key rotation completed for all keys in $KeyVaultName."
}
Upvotes: 0
Views: 2053
Reputation: 29736
Few things:
Here is a complete bicep sample.
key-vault-role-assignment.bicep
param keyVaultName string
param principalId string
param principalType string = 'ServicePrincipal'
param roleId string
resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' existing = {
name: keyVaultName
}
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(subscription().subscriptionId, resourceGroup().name, keyVaultName, roleId, principalId)
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
principalId: principalId
principalType: principalType
}
}
main .bicep:
param location string = resourceGroup().location
param keyVaultName string = 'thomastestkv725'
param storageAccountName string = 'thomasteststr725'
param storageAccountCmkKeyName string = '${storageAccountName}-encryption-key'
param identityName string = '${storageAccountName}-identity'
// create a user assigned identity for the storage account
resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: identityName
location: location
}
// create a key vault
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: keyVaultName
location: location
properties: {
enableRbacAuthorization: true
enabledForTemplateDeployment: true
enablePurgeProtection: true // required when enabling CMK
enableSoftDelete: true // required when enabling CMK
softDeleteRetentionInDays: 90
publicNetworkAccess: 'Enabled' // require when enabling CMK
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices' // require when enabling CMK if firewall enable
}
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
}
}
// create the stroage account key
resource storageAccountCmkKey 'Microsoft.KeyVault/vaults/keys@2023-07-01' = {
parent: keyVault
name: storageAccountCmkKeyName
properties: {
kty: 'RSA' // CMK only supports RSA keys
keySize: 4096
attributes: {
enabled: true
exportable: false
}
rotationPolicy: {
lifetimeActions: [
{
trigger: {
timeAfterCreate: 'P5M' // 5 months
}
action: {
type: 'rotate'
}
}
{
trigger: {
timeBeforeExpiry: 'P1M' // 1 months
}
action: {
type: 'notify'
}
}
]
attributes: {
expiryTime: 'P6M' // 6 months
}
}
}
}
// grant kv crypo permission to the identity
module keyVaultRoleAssignment 'key-vault-role-assignment.bicep' = {
name: '${identityName}-${keyVaultName}-rbac'
params: {
keyVaultName: keyVault.name
principalId: identity.properties.principalId
roleId: '12338af0-0e69-4776-bea7-57ae8d297424' // Key Vault Crypto User
}
}
// Create the storage account once the role assignment is done
resource storageAcc 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
kind: 'StorageV2'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${identity.id}': {}
}
}
sku: {
name: 'Standard_LRS'
}
properties: {
supportsHttpsTrafficOnly: true
encryption: {
identity: {
userAssignedIdentity: identity.id // use the user assigned identity
}
keySource: 'Microsoft.Keyvault'
keyvaultproperties: {
keyvaulturi: keyVault.properties.vaultUri
keyname: storageAccountCmkKey.name
keyversion: null // do not specify version for automatic rotation
}
services: {
blob: {
enabled: true
keyType: 'Account'
}
file: {
enabled: true
keyType: 'Account'
}
}
}
}
dependsOn: [ keyVaultRoleAssignment ]
}
Upvotes: 0