I have 2 resource groups as follow:
rg-shared
rg-storage-accounts
in resource group one I am trying to create a storage account and get its connection string and pass it to resourcegroup2 on which I have a key vault.
my actual code is as follow.
Shared.bicep
targetScope = 'resourceGroup'
param deploymentIdOne string = newGuid()
param deploymentIdTwo string = newGuid()
output deploymentIdOne string = '${deploymentIdOne}-${deploymentIdTwo}'
output deploymentIdTwo string = deploymentIdTwo
param keyvaultmain string = 'Name-keyvault'
param keyvaultshared string = 'Name-keyvault'
param sharedManagedIdentity string = 'Name-Managed-identity'
param storageAccountString string
var storagePrefix = 'sttesteur'
var clientDataKeyPrefix = 'Key-Data-'
var learnersguidsecrets = 'Guidtest'
param tenantCodes array = [
  'tste'
]
resource keyVaultClients 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
  name: keyvaultmain
}
resource keyVaultShared 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
  name: keyvaultshared
}
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
  name: sharedManagedIdentity
  location: resourceGroup().location
}
resource kvClientsKey 'Microsoft.KeyVault/vaults/keys@2021-06-01-preview' = [for code in tenantCodes: {
  name: '${keyVaultClients.name}/${clientDataKeyPrefix}${toUpper(code)}'
  properties: {
    keySize: 2048
    kty: 'RSA'
    // Assign the least permission
    keyOps: [
      'unwrapKey'
      'wrapKey'
    ]
  }
}]
resource accessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2021-06-01-preview' = {
  name: '${keyVaultClients.name}/add'
  properties: {
    accessPolicies: [
      {
        tenantId: subscription().tenantId
        objectId: managedIdentity.properties.principalId
        permissions: {
          // minimum required permission
          keys: [
            'get'
            'unwrapKey'
            'wrapKey'
          ]
        } 
      }
    ]
  }
}
resource clientLearnersGuid 'Microsoft.KeyVault/vaults/secrets@2021-06-01-preview' = [for tenant in tenantCodes: {
  name: '${keyVaultClients.name}/${tenant}${learnersguidsecrets}'
  properties: {
    contentType: 'GUID Key'
    value: '${deploymentIdOne}-${deploymentIdTwo}'
  }
}]
resource storageAccountConnectionString 'Microsoft.KeyVault/vaults/secrets@2021-06-01-preview' = [for tenant in tenantCodes: {
  name: '${keyVaultShared.name}${storagePrefix}${tenant}'
  properties:{
    contentType: '${tenant} Storage Account Connection String'
    value: storageAccountString
  }
}]
And this is my storage-account.bicep
param tenantCodes array = [
  'tste'
]
param tenantManagedIdentity string = 'Manage-identity-Name'
param secondresource string = 'rg-sec-eur-shared'
var keyVaultKeyPrefix = 'Key-Data-'
var storagePrefix = 'sthritesteur'
// Create a managed identity
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = {
  name: tenantManagedIdentity
  location: resourceGroup().location
}
// 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}${toUpper(tenantCode)}'
        // keyvaulturi: keyVault.properties.vaultUri
        keyvaulturi:'https://keyvaultclient.vault.azure.net'
      }
      services: {
        file: {
          keyType: 'Account'
          enabled: true
        }
        blob: {
          keyType: 'Account'
          enabled: true
        }
      }
    }
    accessTier: 'Hot'
  }
}]
resource storage_Accounts_name_default 'Microsoft.Storage/storageAccounts/blobServices@2021-04-01' = [ for (storageName, i) in tenantCodes :{
  parent: storageAccount[i]
  name: 'default'
  properties: {
    changeFeed: {
      enabled: false
    }
    restorePolicy: {
      enabled: false
    }
    containerDeleteRetentionPolicy: {
      enabled: true
      days: 7
    }
    cors: {
      corsRules: []
    }
    deleteRetentionPolicy: {
      enabled: true
      days: 30
    }
    isVersioningEnabled: true
  }
}]
module connectionString 'shared.bicep' = [for (storageName, i) in tenantCodes :{
  scope: resourceGroup(secondresource)
  name: storageName
  params: {
    storageAccountString: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount[i].name};AccountKey=${listKeys(storageAccount[i].id, storageAccount[i].apiVersion).keys[0].value};EndpointSuffix=${environment().suffixes.storage}'
  }
}]
This is the details of this workflow.
in the resource group rg-sharedi have 2 key vaults, keyvault-sharedand keyvaultstorage. And their purpose is as follow:
keyvault-shared => Store StorageAccount Connection String as Secret
keyvault-storage => Generate a Key Name based on the `tenantCode` in the key section, and in secret, generate a GUID and store it
while in the other resource-group rg-storage I want to create a storage account, encrypt the storage account with the key I have generated in the keyault earlier, and pass the connection string of this storageAccount to the shared key vault.
Following your advice, I used the module from shared.bicep and called it in my storage account.bicep.
Based on my command:
az deployment group what-if -f ./storage-account.bicep -g rg-storage-accounts     
the output It shows that will create only the resource in the storage-account.bicep:
- user identity
- storageAccount
- container
How to reproduce:
- Create 2 resource groups (shared and storage accounts)
- in Shared create 2 key vaults
- Update the bicep files with the correct key vault names.
- In both bicep scripts in tenantCodeput a random name to create a storage account or multiple storage accounts.
- and run the above bicep command.
I tried to explain as clear as I could this issue, as its driving me crazy and have no idea what I am doing wrong and this stage.
Please please, if you need anymore information about this issue, just ask and will be glad to clarify any doubt
UPDATE: To generate the key before hand, I moved the key creation into the storage.bicep as follow:
resource keyVaultClients 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = {
  name: keyvaultmain
  scope: resourceGroup(secondresource)
}
resource kvClientsKey 'Microsoft.KeyVault/vaults/keys@2021-06-01-preview' = [for code in tenantCodes: {
  name: '${keyVaultClients.name}-${clientDataKeyPrefix}${toUpper(code)}'
  properties: {
    keySize: 2048
    kty: 'RSA'
    // Assign the least permission
    keyOps: [
      'unwrapKey'
      'wrapKey'
    ]
  }
}]
but I get this error:
{"error":{"code":"InvalidTemplate","message":"Deployment template validation failed: 'The template resource 'keyvault-Key-Data-ORNX' for type 'Microsoft.KeyVault/vaults/keys' at line '54' and column '46' has incorrect segment lengths. A nested resource type must have identical number of segments as its resource name. A root resource type must have segment length one greater than its resource name. Please see https://aka.ms/arm-template/#resources for usage details.'.","additionalInfo":[{"type":"TemplateViolation","info":{"lineNumber":54,"linePosition":46,"path":"properties.template.resources[1].type"}}]}}
Which I don't understand exactly to what refers.
UPDATE: This is an interesting output. So according to the last update (and thank you so so much for your help) I realised that at the code is creating all the correct resource, but at the very end it throws this 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":"Conflict","message":"{\r\n  \"status\": \"Failed\",\r\n  \"error\": {\r\n    \"code\": \"ResourceDeploymentFailure\",\r\n    \"message\": \"The resource operation completed with terminal provisioning state 'Failed'.\",\r\n    \"details\": [\r\n      {\r\n        \"code\": \"DeploymentFailed\",\r\n        \"message\": \"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.\",\r\n        \"details\": [\r\n          {\r\n            \"code\": \"Conflict\",\r\n            \"message\": \"{\\r\\n  \\\"error\\\": {\\r\\n    \\\"code\\\": \\\"StorageAccountOperationInProgress\\\",\\r\\n    \\\"message\\\": \\\"An operation is currently performing on this storage account that requires exclusive access.\\\"\\r\\n  }\\r\\n}\"\r\n          },\r\n          {\r\n            \"code\": \"Conflict\",\r\n            \"message\": \"{\\r\\n  \\\"error\\\": {\\r\\n    \\\"code\\\": \\\"StorageAccountOperationInProgress\\\",\\r\\n    \\\"message\\\": \\\"An operation is currently performing on this storage account that requires exclusive access.\\\"\\r\\n  }\\r\\n}\"\r\n          }\r\n        ]\r\n      }\r\n    ]\r\n  }\r\n}"}]}}
 
    



