Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Easy way to set Azure RBAC roles in Bicep

When deploying resources in Azure using Bicep, occasionally you will have to assign rights to a user or principal to perform certain actions. For example, authorizing an app service to access a storage account.

Initially you would create something like this:

// Assume we have an app service with a System Assigned managed service identity
var principalId = appService.identity.principalId;

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' existing = {
    name: 'some-existing-storage-account'
}

resource roleAuthorization 'Microsoft.Authorization/roleAssignments@2020-10-01-preview' = {
    name: guid(storageAccount.id, resourceGroup().id, principalId)
    scope: storageAccount
    properties: {
        principalId: principalId
        roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe' // Storage Blob Data Contributor
    }
}

I came up with the following Bicep module which shows a nice way to hide the nasty details such as the role guids in a module.

param storageAccountName string
param principalId string

@allowed[
    'Device'
    'ForeignGroup'
    'Group'
    'ServicePrincipal'
    'User'
    ''
]
param principalType string = ''

@allowed([
    'Storage Blob Data Contributor'
    'Storage Blob Data Reader'
])
param roleDefinition string

var roles = {
    // See https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for these mappings and more.
    'Storage Blob Data Contributor': '/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe'
    'Storage Blob Data Reader': '/providers/Microsoft.Authorization/roleDefinitions/2a2b9908-6ea1-4ae2-8e65-a410df84e7d1'
}

var roleDefinitionId = roles[roleDefinition]

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' existing = {
    name: storageAccountName
}

resource roleAuthorization 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
    // Generate a unique but deterministic resource name
    name: guid('storage-rbac', storageAccount.id, resourceGroup().id, principalId, roleDefinitionId)
    scope: storageAccount
    properties: {
        principalId: principalId
        roleDefinitionId: roleDefinitionId
        principalType: empty(principalType) ? null : principalType
    }
}

This makes the Bicep files a lot more readable, especially when you have to assign roles more often.

Creating a module to do this also has the advantage that you can change the scope, for example when the storage account is part of a different resource group.

Cleaned up initial example:

// Assume we have an app service with a System Assigned managed service identity
var principalId = appService.identity.principalId;

// I used 'storageAuth.bicep' as the file name, but doesn't matter.
module roleAuthorization 'storageAuth.bicep' = {
    name: 'roleAuthorization'
    properties: {
        principalId: principalId
        storageAccountName: 'some-existing-storage-account'
        roleDefinition: 'Storage Blob Data Contributor'
    }
}

I hope someone has some use for this as well.

Update 18-07-2023: Updated to include principalType in template.

Jos van der Til

Hi! I'm Jos, a Software Developer from the Netherlands.

Comments

3 responses

Jens

Hi Jos, well done! Do you have any idea how to update an existing roleDefinition if you only want to use the known roleName?

resource roleDef 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
    name: 'roleName' // NOT GUID 
}

for later use of e.g.:

 var realid = roleDef.id

Wasn’t successful to update existing roleDefinitions with bicep due to the issue that I have to know the GUID for it as the mandatory name property. :(

Jos van der Til

Hi Jens,

I am not quite sure if I understand your question. You want to find the name (guid) of an existing role assignment by using the role name such as ‘Storage Blob Data Reader’? If so, I unfortunately do not know of a way to do that in Bicep.

I know you can download a CSV or JSON file of all the role assignments on a resource in the Azure Portal, which might be of some help. Alternatively, you can use the AzCLI to get what you want using: az role assignment list --scope <resource id> --role "<role name>"

Hope this helps!

Jens

Hi Jos, thanks for tryin to help. Yes, you got me correct. But meanwhile I understand and found my issue.

targetScope='resourceGroup'

@description('creating new custom role with limited rights')

param resourceGroupName string
// param location string
param actions array = [
  'Microsoft.ServiceBus/namespaces/queues/write'
  'Microsoft.ServiceBus/namespaces/queues/read'
  'Microsoft.ServiceBus/namespaces/queues/Delete'
  'Microsoft.Storage/storageAccounts/blobServices/read'
  'Microsoft.Storage/storageAccounts/blobServices/containers/delete'
  'Microsoft.Storage/storageAccounts/blobServices/containers/read'
  'Microsoft.Storage/storageAccounts/blobServices/containers/lease/action'
  'Microsoft.Storage/storageAccounts/blobServices/write'
  'Microsoft.Storage/storageAccounts/blobServices/containers/write'
  'Microsoft.Resources/deployments/*'
  'Microsoft.Resources/deploymentScripts/*'
]
param notActions array = []
param dataActions array = [
  'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read'
  'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/delete'
  'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write'
  'Microsoft.ServiceBus/namespaces/messages/send/action'
  'Microsoft.ServiceBus/namespaces/messages/receive/action'
]
param notDataActions array = []
param roleDescription string = 'Custom role'


var roleDefName = guid(resourceGroup().id, string(actions), string(notActions), string(dataActions), string(notDataActions))

resource roleDef 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' = {
  name: roleDefName
  properties: {
    roleName: '${resourceGroupName}-min-RW'
    description: roleDescription
    type: 'customRole'
    permissions: [
      {
        actions: actions
        notActions: notActions
        dataActions: dataActions
        notDataActions: notDataActions
      }
    ]
    assignableScopes: [
      resourceGroup().id
    ]
  }
}

My mistake was to generate the name like this:

var roleDefName = guid(resourceGroup().id, string(actions), string(notActions), string(dataActions), string(notDataActions))

When I add now an entry to actions, dataActions etc. of course the guid generates a new one instead of using the existing one which was my expectation. So the fix for me was just to change to:

 var roleDefName = guid(resourceGroup().id)

Now I am able to change the role when adding or dropping rights. ^^ Thx a lot again

Emran Hossain

Hi Jos ,

Is it possible to assign RBAC role like “Storage Blob Data Contributor” to Azure AD Group

Jos van der Til

Hi Emran,

Yes, this is also possible! Thank you for asking. I have updated the template to include the principalType parameter that you can use.

It works in the same way, you will need to get the objectId of the group you want to assign the role to from Azure AD. Then you can use the template like this:

module roleAuthorization 'storageAuth.bicep' = {
    name: 'roleAuthorization'
    properties: {
        principalId: principalId
        principalType: 'Group'
        storageAccountName: 'some-existing-storage-account'
        roleDefinition: 'Storage Blob Data Contributor'
    }
}

Hope this helps!

Russ
Thanks for this Jos, this is great.