Using Shared Variable File Pattern to simplify Azure Roles In Bicep

Photo of author

Dan Rios

3 minute read

Azure Roles in Bicep – whenever I’m working with role assignments in Infrastructure as Code, I usually find myself checking AzRolesAdvertizer to get the correct role GUID for my assignments. This can become a tedious task, often repeated across many new templates especially when dealing with less common roles.

Thankfully, there’s a solution: leveraging the shared variable file pattern in Azure Bicep.

Azure roles json shared file pattern
Soon, this feature will be built-in to Bicep as a function: https://github.com/Azure/bicep/issues/16867 but currently, at the time of writing it’s still in development. I will update this post when it’s live!

Azure Roles Shared Pattern in Bicep

If you’re unsure about what the pattern is and how it can be beneficial in Bicep, check out an earlier blog I wrote on it.

I recently came across this repository from Microsoft: https://github.com/microsoft/azure-roles. It curates a list of all current Azure roles, adding new ones daily to a JSON file in the GitHub project. This means you can use the shared variable file pattern in Bicep to centrally reference any role you want and even better, you’ll get IntelliSense auto-completion to help you search as well.

Steps to try it out:

  1. Copy the azure-roles.json file to your repository.
  2. Create a variable to load the Azure roles JSON file.

Now, you can type your variable and search for Azure RBAC roles directly in VS Code for a much smoother experience.

Azure Roles in Bicep

Automation with Azure DevOps

Prerequisites

  • Service connection created with federated credentials.
  • Build service requires Contribute, Pull Request, and Branch permissions. However, for production scenarios, it’s recommended to shift to a dedicated service principal for improved pipeline security.

You can take this further by automating a nightly build in Azure DevOps for your Bicep projects. I’ve modified the original create-azureroles.ps1 script from the GitHub project to work with Azure DevOps federated credentials. This runs on a nightly pipeline cron job and automatically creates a pull request with any new roles to be added!

Azure Roles Pipeline run

Here’s the modified PowerShell script. You’ll just need to update the Git config details on lines 53/54. Otherwise, simply save it wherever you prefer in your repository structure.

TL;DR: This script does everything the existing one in the Microsoft Azure Roles repo does, but with a few enhancements: it now creates a branch, commits the updated file, and raises a pull request containing details of any new roles being added. This way, you can fully automate role file updates in your Bicep projects!

# Connect to Azure – Authentication handled automatically by AzurePowerShell task
$azResourceModule = Get-module -Name Az.Resources
if ($azResourceModule -eq $null) {
Install-Module -Name Az.Resources -Repository PSGallery -Force
}
$azAccountModule = Get-module -Name Az.Accounts
if ($azAccountModule -eq $null) {
Install-Module -Name Az.Accounts -Repository PSGallery -Force
}
# No manual Connect-AzAccount needed – AzurePowerShell task handles federated authentication
# Retrieve role definitions and create a custom object
$roleMappings = @{}
Get-AzRoleDefinition | ForEach-Object {
$roleMappings[($_.Name -replace ' ', '')] = $_.Id
}
# Convert the custom object to JSON
$json = $roleMappings | ConvertTo-Json
# Convert JSON to PowerShell object
$jsonObject = $json | ConvertFrom-Json
# Convert to hashtable, sort it and then to custom object
$sortedHashtable = @{}
$jsonObject.PSObject.Properties | ForEach-Object {
if ($_.Name -notmatch "IsFixedSize|IsReadOnly|IsSynchronized|Keys") {
$sortedHashtable[$_.Name] = $_.Value
}
}
$sortedObject = New-Object PSObject
$sortedHashtable.GetEnumerator() | Sort-Object Name | ForEach-Object {
$sortedObject | Add-Member -NotePropertyName $_.Name -NotePropertyValue $_.Value
}
# Convert back to JSON, specify the depth to ensure all nested objects are converted
$sortedJson = $sortedObject | ConvertTo-Json -Depth 5
# Original Azure roles Json
$originalAzureRoleJson = Get-Content .\azure_roles.json
# Output the sorted JSON
$sortedJson | Out-File "azure_roles.json"
# Current Azure roles Json
$currentAzureRoleJson = Get-Content .\azure_roles.json
# Configure git user for commits
& git config –local user.email "[email protected]"
& git config –local user.name "Dan Rios"
# Determine added role names by comparing original and current JSON
$origObj = ($originalAzureRoleJson -join "`n") | ConvertFrom-Json
$currObj = ($currentAzureRoleJson -join "`n") | ConvertFrom-Json
$origKeys = $origObj.PSObject.Properties | ForEach-Object { $_.Name }
$currKeys = $currObj.PSObject.Properties | ForEach-Object { $_.Name }
$added = $currKeys | Where-Object { $origKeys -notcontains $_ }
$newAzureRoleCount = $added.Count
if ($newAzureRoleCount -gt 0) {
$addedText = $added -join ", "
$commitMessage = "Added $newAzureRoleCount new roles: $addedText"
# Create a timestamped branch and switch to it
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$branch = "update-azure-roles-$timestamp"
& git checkout -b $branch
# Stage and commit the changes
& git add "azure_roles.json"
& git commit -m $commitMessage
# Push the branch using token authentication if available
if ($env:SYSTEM_ACCESSTOKEN) {
$authHeader = "AUTHORIZATION: bearer $env:SYSTEM_ACCESSTOKEN"
& git -c http.extraheader="$authHeader" push -u origin $branch
} else {
& git push -u origin $branch
}
# Create a pull request using Azure DevOps CLI
try {
$prTitle = "Update Azure roles: Added $newAzureRoleCount roles"
$prDescription = "This PR adds the following new Azure roles:`n`n$($added | ForEach-Object { "- $_" } | Out-String)"
# Use az repos pr create command
& az repos pr create –source-branch $branch –target-branch main –title $prTitle –description $prDescription
if ($LASTEXITCODE -eq 0) {
Write-Output "Successfully created pull request for branch: $branch"
} else {
Write-Output "Failed to create pull request. Please check Azure DevOps CLI configuration."
}
} catch {
Write-Output "Error creating pull request: $_"
}
}

Next up is the Azure DevOps pipeline YAML I’ve put together. Since I’m using federated credentials, I can take advantage of Azure DevOps predefined variables (such as tenantId, applicationId, and the federated token) to connect to Azure for the role checks.

The pipeline is scheduled to run nightly and will execute the PowerShell script mentioned above (be sure to amend the file paths to suit your setup). The only details you’ll need to update are the service connection name on line 19 and the Git config settings.

# Azure DevOps Pipeline to update Azure roles nightly
# Runs the create-azureroles.ps1 script to check for new Azure roles and create PRs
trigger: none # Disable CI triggers since this runs on schedule
schedules:
– cron: "0 0 * * *" # Run at midnight UTC every night
displayName: 'Nightly Azure Roles Update'
branches:
include:
– main
always: 'true' # Run even if there are no code changes
pool:
vmImage: 'ubuntu-latest'
variables:
# Service connection name – update this to match your Azure service connection
azureServiceConnection: ''
# Azure tenant ID from service connection – predefined var for fed credentials
AZURE_TENANT_ID: $(tenantId)
# Azure client ID from service connection – as above
AZURE_CLIENT_ID: $(servicePrincipalId)
# Azure DevOps authentication – predefined var, no need to edit
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
jobs:
– job: UpdateAzureRoles
displayName: 'Update Azure Roles'
steps:
– checkout: self
persistCredentials: 'true' # Allow git operations
– task: PowerShell@2
displayName: 'Configure Git Authentication'
inputs:
targetType: 'inline'
script: |
# Configure git user settings only
git config –global user.email "[email protected]"
git config –global user.name "Dan Rios"
– task: AzureCLI@2
displayName: 'Configure Azure DevOps CLI'
inputs:
azureSubscription: $(azureServiceConnection)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Configure Azure DevOps CLI for this organization and project
az devops configure –defaults organization=$(System.TeamFoundationCollectionUri) project=$(System.TeamProject)
– task: AzurePowerShell@5
displayName: 'Run Azure Roles Update Script'
inputs:
azureSubscription: $(azureServiceConnection)
ScriptType: 'FilePath'
ScriptPath: '$(Build.SourcesDirectory)/create-azureroles.ps1'
workingDirectory: '$(Build.SourcesDirectory)'
azurePowerShellVersion: 'LatestVersion'
env:
# Azure authentication variables
AZURE_TENANT_ID: $(AZURE_TENANT_ID)
AZURE_CLIENT_ID: $(AZURE_CLIENT_ID)
# Azure DevOps CLI authentication for PR creation
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
# Git authentication token
SYSTEM_ACCESSTOKEN: $(System.AccessToken)

In Action – Pull Request

When testing this method, I removed a role from the latest azure_roles.json file and then ran the pipeline. It correctly picked up two new roles the one I had deleted, plus a genuinely new one and provided a clear description showing exactly what was being added.

That’s it! Now you have two pretty awesome enhancements to your Bicep projects:

  • A curated list of all Azure Roles with their corresponding GUIDs to use in Bicep Shared Variable File Pattern
  • Automated approach to updating the file daily through Azure DevOps pipelines with nightly schedule

I think I’d like to try and contribute back to the repository so the script can be available there for Azure DevOps folks to use. If you are using GitHub then the current workflow can be found in the repository to be adopted already, but it doesn’t use federated credentials (maybe someone can modify that one next!).

Leave a comment