After using terraform for a very long time, I decided to start learning azure bicep. So far I am trying to have a grip on the logic. So far I have playing around on deployment of a storage account and keyvault. What I am doing here is the following.
- create a storage account
- use existing key vault to store storage account connection string as secret
- create a key based on the storage account name
And this works as I am expected.
So I wanted to take one step forward. and here is where I am a bit confused.
What I wanted to do, is to use the same bicep template, to create a new secret but in a different resource group into a different key vault.
Now according to my understand of azure documentation, the template comes with a default scope
which in my specific case target my default subscription and to run my bicep template from the terminal I use the command
az deployment group create -f ./template.bicep -g <resource-group-name>
and this is my template:
// Default values I'm using to test
param keyVaultName string = '<keyvault-name>'
param managedIdentityName string = 'test-managed-identity'
param tenantCodes array = [
'elhm'
'feor'
]
// I'm using prefix so I dont need to create additional arrays
var keyVaultKeyPrefix = 'Client-Key-'
var storagePrefix = 'sthrideveur'
// 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'
}
}]
// Store the connectionstrings in KV if specified
resource storageAccountConnectionStrings 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = [ for (name, i) in tenantCodes :{
name: '${keyVault.name}/${storagePrefix}${name}'
properties: {
value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount[i].name};AccountKey=${listKeys(storageAccount[i].id, storageAccount[i].apiVersion).keys[0].value};EndpointSuffix=${environment().suffixes.storage}'
}
}]
according to the documentation here https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/deploy-to-resource-group?tabs=azure-cli
When I need to target a specific resource group, I can use the scope in the resource, so I create this:
resource keyvaultApi 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
name: keyVaultApiName
scope: resourceGroup('secondresourcegroup')
}
So far no errors, but the problem happens when I had to create a managed identity resource.
resource keyvaultApi 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
name: keyVaultApiName
scope: resourceGroup('secondresourcegroup')
}
resource managedIdentityTwo 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
name: managedIdentityNameTwo
location: resourceGroup().location
}
resource accessPolicyApi 'Microsoft.Media/videoAnalyzers/accessPolicies@2021-11-01-preview' = {
name: '${keyvaultApi.name}/add'
properties: {
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: managedIdentityTwo.properties.principalId
permissions: {
// minimum required permissions
keys: [
'get'
'unwrapKey'
'wrapKey'
]
}
}
]
}
}
In the key vault I could declare the scope, but to the underlying resources, such as access policy etc, I cannot declare the scope. So how can bicep understand that those resources needs to target a specific resource group and specific key vault?
Because when I run the terminal command, I am targeting a specific resource group, so I don't really understand how I can use one template to target different resource groups and resources accordingly.
I hope I made my point clear, and please if I didn't, just feel free to ask me more informations.
Thank you so much for your time and help
UPDATE: When I try to run the code as it is, I get the following error:
{"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":"NotFound","message":"{\r\n \"error\": {\r\n \"code\": \"ParentResourceNotFound\",\r\n \"message\": \"Can not perform requested operation on nested resource. Parent resource 'secondkeyvault' not found.\"\r\n }\r\n}"}]}}
UPDATE: So I followed the Daniel lead and in a second template I deployed the code I needed for the second template as follow:
template2.bicep
param deploymentIdOne string = newGuid()
param deploymentIdTwo string = newGuid()
output deploymentIdOne string = '${deploymentIdOne}-${deploymentIdTwo}'
output deploymentIdTwo string = deploymentIdTwo
// Default values I'm using to test
param keyVaultApiName string = 'secondkeyvaultapi'
param managedIdentityNameTwo string = 'second-second-identity'
var keyVaultKeyPrefixTw = 'Client-Key-'
param tenantCodes array = [
'tgrf'
]
resource keyvaultApi 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
name: keyVaultApiName
}
resource managedIdentityTwo 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
name: managedIdentityNameTwo
location: resourceGroup().location
}
resource accessPolicyApi 'Microsoft.KeyVault/vaults/accessPolicies@2019-09-01' = {
name: '${keyvaultApi.name}/add'
properties: {
accessPolicies: [
{
tenantId: subscription().tenantId
objectId: managedIdentityTwo.properties.principalId
permissions: {
// minimum required permissions
keys: [
'get'
'unwrapKey'
'wrapKey'
]
}
}
]
}
}
// Store the connectionstrings in KV if specified
resource clientApiKeys 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = [ for name in tenantCodes :{
name: '${keyvaultApi.name}/${keyVaultKeyPrefixTw}${name}'
properties: {
value: '${deploymentIdOne}-${deploymentIdTwo}'
}
}]
and in my main template I added the module:
module clientKeyApi 'template2.bicep' = {
name: 'testfrgs'
scope: 'secondresourcegroup'
}
But there is something that is not clear 100% to me.
How does it work to override all the for
loop and parameters name I have declared in my template2.bicep , and yet the module require a scope subscription
, if I declare the scope, wouldn't this override the default value?
Sorry guys for the newbie questions, I am trying to break my mindset from terraform and understand better how bicep work.
Any explanation would be amazing and helpful