I need to:
create a data factory
create a storage account
create a function app
add a role assignment for the data factory to the storage account
add a role assignment for the function app to the storage account
The data factory is created in a separate module from the "main" bicep. This is to prevent the "main" template being so large it is difficult to work with - one of the main benefits of bicep over arm templates. Same goes for creation of the function app.
For the role assignment I have:
resource roleAssignment 'Microsoft.Authorization/roleAssignments#2020-08-01-preview' = {
name: guid(storageAccount.id, contributorRoleId, adfDeploy.outputs.dfId)
VSCode then presents the following "problem":
This expression is being used in an assignment to the "name" property
of the "Microsoft.Authorization/roleAssignments" type, which requires
a value that can be calculated at the start of the deployment.
Properties of adfDeploy which can be calculated at the start include
"name".
I can't compose the storageAccount Id from a string (subscription/rg/resource etc.) because the subscription id is also determined at runtime since the same main bicep is called for deployment to multiple subscriptions.
Is there any way to achieve what's needed without pulling back the creation of the data factory and function apps to the "main" bicep?
You could create a generic module for storage role assignment:
// storage-account-role-assignment.bicep
param storageAccountName string
param principalId string
param roleId string
// Get a reference to the storage account
resource storageAccount 'Microsoft.Storage/storageAccounts#2019-06-01' existing = {
name: storageAccountName
}
// Grant permissions to the storage account
resource storageAccountAppRoleAssignment 'Microsoft.Authorization/roleAssignments#2020-04-01-preview' = {
name: guid(storageAccount.id, roleId, principalId)
scope: storageAccount
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
principalId: principalId
}
}
Then invoke this module from where you are creating data factory or function app:
// function-app.bicep
...
resource functionApp 'Microsoft.Web/sites#2021-03-01' = {
name: functionAppName
kind: 'functionapp'
identity: {
type: 'SystemAssigned'
}
...
}
// Create role assignment
module roleAssignment 'storage-account-role-assignment.bicep' = {
name: 'function-storage-account-role-assignment'
scope: resourceGroup()
params:{
storageAccountName: storageAccountName
roleId: '<role-id>'
principalId: functionApp.identity.principalId
}
}
// data-factory.bicep
...
resource dataFactory 'Microsoft.DataFactory/factories#2018-06-01' = {
name: name
identity: {
type: 'SystemAssigned'
}
...
}
// Create role assignment
module roleAssignment 'storage-account-role-assignment.bicep' = {
name: 'data-facory-storage-account-role-assignment'
scope: resourceGroup()
params:{
storageAccountName: storageAccountName
roleId: '<role-id>'
principalId: dataFactory.identity.principalId
}
}
Related
i would like to do this steps App Configuration -> Access control (IAM) -> Add role assigment -> App Configuration Data Reader -> Assign access to Managed identity -> Select Members (choose my app service) -> Save but instead of using Azure Portal for that, I wanted to use ARM/Bicep template,
I tried something like this:
targetScope = 'resourceGroup'
param principalId string = 'x-x-x-x-x-x-x-x-x'
param roleDefinitionId string = 'x-x-x-x-x-x'
var roleAssignmentName = guid('/', principalId, roleDefinitionId)
resource roleAssignment 'Microsoft.Authorization/roleAssignments#2020-03-01-preview' = {
name: roleAssignmentName
properties: {
roleDefinitionId: tenantResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
principalId: principalId
}
}
But there are 2 problems with this solutions. Firstly, I am using this targetScope = resourceGroup which creates this Role inside RG, and then my App Confiugration just inherit it from RG. Probably, the proper solution would be to provide App Configuration name somewhere, so it would be used instead of scoping it to Resource Group.
Also, hard-coding principalId and roleDefinitionId like this feels pretty bad, but f.e I can't access principalID of my Web App by doing something like this:
resource webApp 'Microsoft.Web/sites#2022-03-01' existing = {
name: 'myUniqueWebAppName'
}
param principalId string = webApp.identity.principalId
as it says that This symbol cannot be referenced here. Only other parameters can be referenced in parameter default values.
Also, I don't know how to access roleDefinitionId, I know where to find it in Azure Portal, but no idea how to access it without hard-coding.
Few things :
You can specify the scope fo the roleAssignment using the scope property.
Role Id won't change so hardcoding roleId is not really an issue, you could alway pass it as a parameter as well.
If you create a module to do the role assignment, you would be able to inject the webapp principalId
you can create a module like that:
// app-configuration-role-assignment.bicep
param appConfigurationName string
param principalId string
param roleId string
// Get a reference to app config
resource appConfiguration 'Microsoft.AppConfiguration/configurationStores#2022-05-01' existing = {
name: appConfigurationName
}
// Grant permission
resource appConfigurationRoleAssignment 'Microsoft.Authorization/roleAssignments#2022-04-01' = {
name: guid(appConfiguration.id, roleId, principalId)
scope: appConfiguration
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
principalId: principalId
}
}
Then from your main you could invoke it and pass the webapp principalId:
// main.bicep
param appConfigurationName string
param webAppName string
// get a reference to webapp
resource webApp 'Microsoft.Web/sites#2022-03-01' existing = {
name: webAppName
}
module roleAssignment 'app-configuration-role-assignment.bicep' = {
name: 'app-configuration-role-assignment-to-webapp'
scope: resourceGroup()
params: {
appConfigurationName: appConfigurationName
principalId: webApp.identity.principalId
roleId: '516239f1-63e1-4d78-a4de-a74fb236a071' // App Configuration Data Reader
}
}
I'm trying to create a resource group and add a key vault to it.
However, I'm not able to set the new resource group as a target resource group for the key vault.
How can I have the key vault assigned to the newly created resource group without creating a second Bicep module for it?
var loc = 'westus'
// outputs the newly created resource group
module rgCreate 'test.rg.bicep' = {
scope: subscription()
name: 'rgCreate'
params: {
rgLocation: loc
}
}
resource keyVault 'Microsoft.KeyVault/vaults#2021-10-01' = {
name: 'Test'
location: loc
properties: {
enabledForTemplateDeployment: true
sku: {
family: 'A'
name: 'standard'
}
tenantId: tenant().tenantId
}
}
This is the workflow I'm aiming at:
First, if the resource group does not exist, you can't have targetScope = 'resourceGroup' in the main.bicep file. The command az deployment group create will fail:
{"code": "ResourceGroupNotFound", "message": "Resource group '' could not be found."}
You could always trigger the deployment form another resource that already exists (Not sure if it s a good idea tho).
An approach could be to have you main.bicep invoking two modules: one for resource group creation, one for resource creation:
// =========== rg.bicep ===========
// Setting target scope
targetScope = 'subscription'
param name string
param location string
// Creating resource group
resource rg 'Microsoft.Resources/resourceGroups#2021-01-01' = {
name: name
location: location
}
// =========== resources.bicep ===========
param location string = resourceGroup().location
param keyVaultName string
...
//Deploying key vault
resource keyVault 'Microsoft.KeyVault/vaults#2021-10-01' = {
name: keyVaultName
location: location
properties: {
enabledForTemplateDeployment: true
sku: {
family: 'A'
name: 'standard'
}
tenantId: tenant().tenantId
}
}
// Deploying other resources
...
// =========== main.bicep ===========
// Setting target scope
targetScope = 'subscription'
// Parameters
param rgName string = 'test-rg'
param rgLocation string = 'westus'
param keyVaultName string
...
// Creating resource group
module rgModule 'rg.bicep' = {
scope: subscription()
name: '${rgName}-create'
params:{
name: rgName
location: rgLocation
}
}
// Deploying resources in the newly created resource
module resources 'resources.bicep' = {
name: '${rgName}-resources-deployment'
scope: resourceGroup(rgName)
dependsOn: [ rgModule ]
params: {
location: rgLocation
keyVaultName: keyVaultName
...
}
}
To be honest, you could just run az group create command before deploying your template it will make things simpler.
I have an Azure Functions project, with a Function that uses a Service Bus binding (that is used to Listen on a subscription and to Send to a topic).
The Azure functions deployment is running under a managed identity. And as we want to deploy everything automatically, using Azure Bicep, I want to automatically give the correct role assignment on the Service Bus namespace (or entities) for that managed identity, in an Azure Bicep file.
But I don't seem to find out how to do that. Would someone be able to indicate the correct bicep snippet to create the role assignments Azure Service Bus Data Receiver and Azure Service Bus Data Sender on a Service Bus entity for a specific managed identity?
(and even better : how can I find that out for myself, knowing that I am rather new to bicep)
Best regards
Documentation to create RBAC using Bicep can be found here.
Azure built-in roles can be found here
So for ServiceBus and managed identity, you could create a module that looks like that
// servicebus-role-assignment.bicep
param serviceBusName string
param principalId string
#allowed([
'4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0' // Azure Service Bus Data Receiver
'69a216fc-b8fb-44d8-bc22-1f3c2cd27a39' // Azure Service Bus Data Sender
])
param roleId string
// Get a reference to servicebus namespace
resource servicebus 'Microsoft.ServiceBus/namespaces#2022-01-01-preview' existing = {
name: serviceBusName
}
// Grant permissions to the principalID to specific role to servicebus
resource roleAssignment 'Microsoft.Authorization/roleAssignments#2020-04-01-preview' = {
name: guid(servicebus.id, roleId, principalId)
scope: servicebus
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId)
principalId: principalId
principalType: 'ServicePrincipal'
}
}
If you are using a user-assigned identity, you could invoke this module once the identity has been created:
param location string = resourceGroup().location
param identityName string
param serviceBusName string
// Create the identity
resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities#2022-01-31-preview' = {
name: identityName
location:location
}
// Do the role assignment
module serviceBusRoleAssignment 'servicebus-role-assignment.bicep' = {
name: 'servicebus-role-assignment'
params: {
serviceBusName: serviceBusName
roleId: '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0' // Azure Service Bus Data Receiver
principalId: identity.properties.principalId
}
}
If you are using a system-assigned identity, you would need to first create the function app:
param location string = resourceGroup().location
param functionAppName string
param serviceBusName string
...
// Create the function app
resource functionApp 'Microsoft.Web/sites#2022-03-01' = {
name: functionAppName
identity: {
type: 'SystemAssigned'
}
...
}
// Do the role assignment
module serviceBusRoleAssignment 'servicebus-role-assignment.bicep' = {
name: 'servicebus-role-assignment'
params: {
serviceBusName: serviceBusName
roleId: '4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0' // Azure Service Bus Data Receiver
principalId: functionApp.identity.principalId
}
}
How would I create a cosmos db account and pass it as a parameter to a bicep module? I would like enhance this sample bicep script by moving the role definition and role assignment to a separate module.
Here is my attempt at creating a module (that compiles and creates a CosmosDBAccount with no errors):
//#description ('cosmosDbAccount')
//param cosmosDbAccount object
#description ('cosmosDbAccountId')
param cosmosDbAccountId string
#description ('cosmosDbAccountName')
param cosmosDbAccountName string
#description('iteration')
param it int
#description('Principal ID of the managed identity')
param principalId string
var roleDefId = guid('sql-role-definition-', principalId, cosmosDbAccountId)
var roleDefName = 'Custom Read/Write role-${it}'
var roleAssignId = guid(roleDefId, principalId, cosmosDbAccountId)
resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions#2021-06-15' = {
name: '${cosmosDbAccountName}/${roleDefId}'
properties: {
roleName: roleDefName
type: 'CustomRole'
assignableScopes: [
cosmosDbAccountId
]
permissions: [
{
dataActions: [
'Microsoft.DocumentDB/databaseAccounts/readMetadata'
'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*'
]
}
]
}
}
resource roleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments#2021-06-15' = {
name: '${cosmosDbAccountName}/${roleAssignId}'
properties: {
roleDefinitionId: roleDefinition.id
principalId: principalId
scope: cosmosDbAccountId
}
}
Here is my attempt at calling the module:
#batchSize(1)
module cosmosRole 'cosmosRole.bicep' = [for (princId, jj) in principals: {
name: 'cosmos-role-definition-and-assignment-${jj}'
params: {
// cosmosDbAccount: cosmosDbAccount
cosmosDbAccountId: cosmosDbAccount.id
cosmosDbAccountName: cosmosDbAccount.name
principalId: princId
it: jj
}
}]
As you can see, the original code uses cosmosDbAccount.id and I have replaced this with a string called cosmosDbAccountId. When I try un-comment the above code and pass the cosmosDbObject and use the original syntax ("cosmosDbAccount.id" and "cosmosDbAccount.name") I get this error
ERROR: ..."Deployment template validation failed: 'The template variable 'roleDefId' is not valid: The language expression property 'id' doesn't exist, available properties are 'apiVersion, location, tags, identity, kind, properties, condition, deploymentResourceLineInfo, existing, isConditionTrue, subscriptionId, resourceGroupName, scope, resourceId, referenceApiVersion, isTemplateResource, isAction, provisioningOperation'..
Questions:
I would prefer the original syntax (fewer parameters) inside my new module. How do I do this?
How do I confirm the script created the roleAssignment and roleDefinition? I cannot find these in the azure portal. When I use the bicep output statement they appear but I would like to see them using the portal web page.
Few things here.
Passing a resource type parameter is an experimental feature, you will have to enable it (see Proposal - simplifying resource referencing for more details)
Before deploying your bicep file, you will need to set this environment variable:
# powershell example
$env:BICEP_RESOURCE_TYPED_PARAMS_AND_OUTPUTS_EXPERIMENTAL="true"
It will still show errors in visual studio code but the deployment was successful.
Here is the modified module with a parameter of type resource:
param cosmosDbAccount resource 'Microsoft.DocumentDB/databaseAccounts#2021-11-15-preview'
param it int
param principalId string
var roleDefId = guid('sql-role-definition-', principalId, cosmosDbAccount.id)
var roleDefName = 'Custom Read/Write role-${it}'
var roleAssignId = guid(roleDefId, principalId, cosmosDbAccount.id)
resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions#2021-06-15' = {
name: '${cosmosDbAccount.name}/${roleDefId}'
properties: {
roleName: roleDefName
type: 'CustomRole'
assignableScopes: [
cosmosDbAccount.id
]
permissions: [
{
dataActions: [
'Microsoft.DocumentDB/databaseAccounts/readMetadata'
'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*'
]
}
]
}
}
resource roleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments#2021-06-15' = {
name: '${cosmosDbAccount.name}/${roleAssignId}'
properties: {
roleDefinitionId: roleDefinition.id
principalId: principalId
scope: cosmosDbAccount.id
}
}
In the main bicep file, we can then pass the cosmosDbAccount as a parameter:
#batchSize(1)
module cosmosRole 'cosmosRole.bicep' = [for (princId, jj) in principals: if (!empty(principals)) {
name: 'cosmos-role-definition-and-assignment-${jj}'
params: {
cosmosDbAccount: cosmosDbAccount
principalId: princId
it: jj
}
}]
This solution is still experimental and while running the az deployment group create, you will see this big warning:
WARNING: Resource-typed parameters and outputs in ARM are experimental, and should be enabled for testing purposes only. Do not enable this setting for any production usage, or you may be unexpectedly broken at any time!
If you don't want to pass two parameters, you could declare an existing resource in your module and just pass the cosmosDbAccountName parameter:
param cosmosDbAccountName string
param it int
param principalId string
var roleDefId = guid('sql-role-definition-', principalId, cosmosDbAccount.id)
var roleDefName = 'Custom Read/Write role-${it}'
var roleAssignId = guid(roleDefId, principalId, cosmosDbAccount.id)
resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts#2021-11-15-preview' existing = {
name: cosmosDbAccountName
}
resource roleDefinition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions#2021-06-15' = {
name: '${cosmosDbAccount.name}/${roleDefId}'
properties: {
roleName: roleDefName
type: 'CustomRole'
assignableScopes: [
cosmosDbAccount.id
]
permissions: [
{
dataActions: [
'Microsoft.DocumentDB/databaseAccounts/readMetadata'
'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*'
]
}
]
}
}
resource roleAssignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments#2021-06-15' = {
name: '${cosmosDbAccount.name}/${roleAssignId}'
properties: {
roleDefinitionId: roleDefinition.id
principalId: principalId
scope: cosmosDbAccount.id
}
}
You main file will look like that:
#batchSize(1)
module cosmosRole 'cosmos-module.bicep' = [for (princId, jj) in principals: if (!empty(principals)) {
name: 'cosmos-role-definition-and-assignment-${jj}'
params: {
cosmosDbAccountName: cosmosDbAccount.name
principalId: princId
it: jj
}
}]
Regarding your second question, if you navigate to your cosmos db account and then click on the export template button, you should be able to see the created roles and related assignments (I know it s not ideal...):
I've created a Bicep template. In it I create a user-assigned identity and reference it in other resources like this
var identityName = 'mid-dd-test'
var roleName = 'TestRole'
var roleDescription = 'Some test'
var roleScopes = [
resourceGroup().id
]
var resolvedActions = [
'Microsoft.Resources/subscriptions/resourcegroups/*'
'Microsoft.Compute/sshPublicKeys/*'
]
var permittedDataActions = []
resource userId 'Microsoft.ManagedIdentity/userAssignedIdentities#2018-11-30' = {
name: identityName
location: resourceGroup().location
}
resource roleDef 'Microsoft.Authorization/roleDefinitions#2018-01-01-preview' = {
name: guid(subscription().id, 'bicep', 'dsadsd')
properties: {
roleName: roleName
description: roleDescription
type: 'customRole'
assignableScopes: roleScopes
permissions: [
{
actions: resolvedActions
dataActions: permittedDataActions
}
]
}
}
resource roles 'Microsoft.Authorization/roleAssignments#2018-09-01-preview' = {
name: guid(subscription().id, 'bicep-roleassignments', 'dsddsd')
properties: {
principalId: userId.properties.principalId
roleDefinitionId: roleDef.id
}
}
Whenever I deploy this I need 2 runs. The first run ends in the error message:
Principal XXX does not exist in the directory YYY
where XXX would be a principal id the user-assigned identity has and YYY is my tenant id. If I now look into the portal the identity is created and XXX is the correct id.
So when I now simply re-run the deployment it works.
I consider it a bug in dependsOn which should relate to ARM templates and not Bicep. I could not find any place where I can report ARM template issues to Microsoft.
I'm asking to assure that I do not miss something else here.
Edit: Added complete working sample which shows the bug. To use it, copy the script content into a test.bicep locally. Then create a resource group (lets call it "rg-test"), ensure that your local POSH context is set correctly and execute the following line in the folder where you stored the bicep in:
New-AzResourceGroupDeployment -Name deploy -Mode Incremental -TemplateFile .\test.bicep -ResourceGroupName rg-test
In the role assignment, you need to specify the principalType to ServicePrincipal and also use an api version greater or equal than: 2018-09-01-preview.
When you create a service principal, it is created in an Azure AD. It takes some time for the service principal to be replicated globally. By setting the principalType to ServicePrincipal, it tells the ARM API t0 wait for the replication.
resource roles 'Microsoft.Authorization/roleAssignments#2018-09-01-preview' = {
name: guid(subscription().id, 'bicep-roleassignments', 'dsddsd')
properties: {
principalId: userId.properties.principalId
roleDefinitionId: roleDef.id
principalType: 'ServicePrincipal'
}
}
You need to reference a newly created identity inside identity property of the target resource. dependsOn is redundant because bicep creates resources in the correct order based on actual usage:
resource userId 'Microsoft.ManagedIdentity/userAssignedIdentities#2018-11-30' = {
name: 'myidentity'
location: resourceGroup().location
}
resource appService 'Microsoft.Web/sites#2021-02-01' = {
name: 'appserviceName'
location: resourceGroup().location
properties: {
//...
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'/subscriptions/{your_subscription_id}/resourceGroups/${resourceGroup().name}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/${userId.name}': {}
}
}
}
The documentation doesn't recommend to use dependsOn without as strong reason:
In most cases, you can use a symbolic name to imply the
dependency between resources. If you find yourself setting explicit
dependencies, you should consider if there's a way to remove it.
So bicep does not require the dependsOn segment if referencing the property correctly.
Need to reference the properties.principalId of the userId in the resource block.
So would look like:
userId.properties.principalId
Here's a quickstart that calls out in a working example how this would work.