Coder Perfect

Loop over key vault keys and encrypt storage account using the Bicep

Problem

I’m having a bit of a stumbling block with the implementation logic.

To build those resources, I’m collaborating with Bicep.

The first three steps have been completed. If I declare two storage accounts, it will generate two secret connection strings and two keys automatically. They match in every pairing (storage name and connection string).

Now the issue I am facing is the following, to start with, this is my code.

param tenantCode array = [
  'dsec'
  'sdre'
]

var storageName = [for item in tenantCode :{
  name: string('sthrideveur${item}')
}]


var connectionStringSecretName = [for connection in storageName :{
  name: '${connection.name}'

}]

output connectionStringSecretName array= [for connection in storageName :{
  name: '${connection.name}'

}]






resource storage_Accounts 'Microsoft.Storage/storageAccounts@2021-06-01' = [ for name in storageName :{
  name: '${name.name}'
  location: 'westeurope'
  sku: {
    name: 'Standard_RAGRS'
  }
  kind: 'StorageV2'
  properties: {
    allowCrossTenantReplication: true
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
    allowSharedKeyAccess: true
    networkAcls: {
      bypass: 'AzureServices'
      virtualNetworkRules: []
      ipRules: []
      defaultAction: 'Allow'
    }
    supportsHttpsTrafficOnly: true
    encryption: {
      services: {
        file: {
          keyType: 'Account'
          enabled: true
        }
        blob: {
          keyType: 'Account'
          enabled: true
        }
      }
      keySource: 'Microsoft.Storage'
      keyvaultproperties: { 
        keyname: '${tenantKey[0]}'
        keyvaulturi: keyVault.id
      }
    }
    accessTier: 'Cool'
  }
}]

resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
  name : 'XXX'
}

// Store the connection string in KV if specified
resource storageAccountConnectionString 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = [for name in storageName :{
  name: '${keyVault.name}/${name.name}'
  properties: {
    value: 'DefaultEndpointsProtocol=https;AccountName=${storage_Accounts[0].name};AccountKey=${listKeys('${storage_Accounts[0].id}', '${storage_Accounts[0].apiVersion}').keys[0].value};EndpointSuffix=${environment().suffixes.storage}'
  }
}]

resource tenantKey 'Microsoft.KeyVault/vaults/keys@2021-06-01-preview' = [for tenant in tenantCode : {
  name: '${keyVault.name}/Client-Key-${tenant}'
  properties: {
    keySize: 2048
    kty: 'RSA'
  }

}]

I’d like to setup two storage accounts. The keys, on the other hand, include the storage code. What I want to do, and what I’m having trouble doing, is figure out how to match the right key to the right storage account. In this instance, I must use the following codes:

dsec
sdre

The bicep script will create two storage accounts with the following names:

sthrideveurdsec
sthrideveursdre

AND 2 secrets with the same name
sthrideveurdsec
sthrideveursdre

AND 2 Keys named
Client-Key-dsec
Client-Key-sdre

What I’d like to do is encrypt the DSEC storage account with the DSEC key and the SDRE storage account with the SDRE key. But, because I’m new to biceps, I’m having trouble putting this together.

I’d appreciate it if someone could explain how to get this correct matching.

UPDATE: After testing Thomas solution, this is the error I am getting:

{"status":"Failed","error":{"code":"DeploymentFailed","message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.","details":[{"code":"BadRequest","message":"{\r\n  \"error\": {\r\n    \"code\": \"KeyVaultPolicyError\",\r\n    \"message\": \"Keyvault policy recoverable is not set\"\r\n  }\r\n}"},{"code":"BadRequest","message":"{\r\n  \"error\": {\r\n    \"code\": \"KeyVaultPolicyError\",\r\n    \"message\": \"Keyvault policy recoverable is not set\"\r\n  }\r\n}"}]}}

Asked by Nayden Van

Solution #1

I’m presuming the key vault has been built and that access policies are being used.

If you want to build a storage with customer-managed keys, the storage must first gain access to the key vault, therefore in my example, I’m using a user-assigned identity.

The steps are as follows:

// Default values I'm using to test 
param keyVaultName string = 'kvthomastest'
param managedIdentityName string = 'mi-storage-encryption-thomas-test'

param tenantCodes array = [
  'dsec'
  'sdre'
]

// I'm using prefix so I dont need to create additional arrays
var keyVaultKeyPrefix = 'Client-Key-'
var storagePrefix = 'stthomastest'

// Get a reference to key vault
resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
  name: keyVaultName
}

// Create a managed identity
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
  name: managedIdentityName
  location: resourceGroup().location
}

// Grant permissions to key vault
resource accessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2019-09-01' = {
  name: '${keyVault.name}/add'
  properties: {
    accessPolicies: [
      {
        tenantId: subscription().tenantId
        objectId: managedIdentity.properties.principalId
        permissions: {
          // minimum required permissions
          keys: [
            'get'
            'unwrapKey'
            'wrapKey'
          ]
        }
      }
    ]
  }
}

// Create key vault keys
resource keyVaultKeys 'Microsoft.KeyVault/vaults/keys@2021-06-01-preview' = [for tenantCode in tenantCodes: {
  name: '${keyVault.name}/${keyVaultKeyPrefix}${tenantCode}'
  properties: {
    keySize: 2048
    kty: 'RSA'
    // storage key should only needs these operations
    keyOps: [
      'unwrapKey'
      'wrapKey'
    ]
  }
}]

// Create storage accounts
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-04-01' = [for tenantCode in tenantCodes: {
  name: '${storagePrefix}${tenantCode}'
  location: resourceGroup().location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_RAGRS'
  }
  // Assign the identity
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${managedIdentity.id}': {}
    }
  }
  properties: {
    allowCrossTenantReplication: true
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
    allowSharedKeyAccess: true
    networkAcls: {
      bypass: 'AzureServices'
      virtualNetworkRules: []
      ipRules: []
      defaultAction: 'Allow'
    }
    supportsHttpsTrafficOnly: true
    encryption: {
      identity: {
        // specify which identity to use
        userAssignedIdentity: managedIdentity.id
      }
      keySource: 'Microsoft.Keyvault'
      keyvaultproperties: {
        keyname: '${keyVaultKeyPrefix}${tenantCode}'
        keyvaulturi: keyVault.properties.vaultUri
      }
      services: {
        file: {
          keyType: 'Account'
          enabled: true
        }
        blob: {
          keyType: 'Account'
          enabled: true
        }
      }
    }
    accessTier: 'Cool'
  }
}]

Answered by Thomas

Post is based on https://stackoverflow.com/questions/69665879/bicep-loop-over-key-vault-keys-and-encrypt-storage-account