When you want to do a role assignment to a principal in an ARM template you will use code like the one below. In this example the role definition is actual the object id of the role. If you want to assign the contributor role you will use the value 'b24988ac-6180-42a0-ab88-20f7382dd24c'. You also have to specify the id of the principal so you will have to retrieve that value yourself upfront.
{ "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2020-04-01-preview", "name": "[guid(parameters('roleAssignmentName'))]", "properties": { "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", "principalId": "[parameters('principalId')]", "scope": "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('rgName'))]" } }
Would it not be easier to configure role assignments by the name of the principal and the role? For example via an array like the one below.
"roleAssignments": [ { "name": "myuserassignedidentity", "principalType": "ServicePrincipal", "builtInRoleType": "Contributor" }, { "name": "DEL-MyWebApplication-Contributors", "principalType": "Group", "builtInRoleType": "Contributors" }, { "name": "my-account@my-domain.com", "principalType": "User", "builtInRoleType": "Reader" } ] }
To make this kind of assignment possible I utilize the concept of deployment scripts again. I wrote a PowerShell script which will retrieve the id of a managed identity, Azure AD group or user principal. This script is executed by the ARM template and will run under the rights of an user assigned identity. The identity needs reader rights on the Azure subscription and should be part of the Directory Readers Azure AD group to be able to query groups and users.
[CmdletBinding()] param ( # The name of the principal. [Parameter(Mandatory = $true)] [string] $PrincipalName, # The type of principal. [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [ValidateSet("ServicePrincipal", "Group", "User")] [string] $PrincipalType ) # Add TLS 1.2 in session [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Install modules if (-not (Get-Module -Name Az.Resources -ListAvailable -ErrorAction SilentlyContinue | Where-Object { $_.Version -eq "3.2.1" })) { # Newer versions require another version than Az.Accounts version 2.2.5. Install-Module -Name Az.Resources -RequiredVersion 3.2.1 -Scope CurrentUser -Force } # Import modules Import-Module -Name Az.Resources -RequiredVersion 3.2.1 $contextName = (Get-AzContext).Name Write-Output "Context: $contextName" switch ($PrincipalType) { "ServicePrincipal" { Write-Output "Retrieve ID from Service Principal '$PrincipalName'." $principalId = Get-AzADServicePrincipal -DisplayName $PrincipalName | Select-Object -ExpandProperty Id } "Group" { Write-Output "Retrieve ID from Azure AD group '$PrincipalName'." $principalId = Get-AzADGroup -DisplayName $PrincipalName | Select-Object -ExpandProperty Id } "User" { Write-Output "Retrieve ID from Azure AD user '$PrincipalName'." $principalId = Get-AzADUser -UserPrincipalName $PrincipalName | Select-Object -ExpandProperty Id } default { $principalId = $null } } # Write the principal id to the ARM output section. $DeploymentScriptOutputs['principalId'] = $principalId
Finally the ARM template uses this PowerShell script to retrieve the id of the principal. It also has the knowlegde of the object ids of the roles which you want to support in the form of local variables named after the roles.
{ "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "cdStorageAccount": { "type": "string" }, "cdStorageAccountSASToken": { "type": "string" }, "nestedTemplateSuffix": { "type": "string" }, "resourceGroupName": { "type": "string" }, "roleAssignments": { "type": "array" }, "runAsIdentity": { "type": "string" } }, "variables": {}, "resources": [ { "name": "[concat('-roleAssignment', '-', padLeft(copyIndex(), 3, '0'), '-', parameters('nestedTemplateSuffix'))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2020-06-01", "copy": { "name": "roleAssignmentsArray", "count": "[length(parameters('roleAssignments'))]", "mode": "Parallel" }, "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "mode": "Incremental", "expressionEvaluationOptions": { "scope": "inner" }, "parameters": { "cdStorageAccount": { "value": "[parameters('cdStorageAccount')]" }, "cdStorageAccountSASToken": { "value": "[parameters('cdStorageAccountSASToken')]" }, "nestedTemplateSuffix": { "value": "[parameters('nestedTemplateSuffix')]" }, "resourceGroupName": { "value": "[parameters('resourceGroupName')]" }, "roleAssignment": { "value": "[parameters('roleAssignments')[copyIndex()]]" }, "runAsIdentity": { "value": "[parameters('runAsIdentity')]" }, "iterator": { "value": "[padLeft(copyIndex(), 3, '0')]" } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "cdStorageAccount": { "type": "string" }, "cdStorageAccountSASToken": { "type": "string" }, "nestedTemplateSuffix": { "type": "string" }, "resourceGroupName": { "type": "string" }, "roleAssignment": { "type": "object" }, "runAsIdentity": { "type": "string" }, "iterator": { "type": "string" }, "now": { "type": "string", "defaultValue": "[utcNow('u')]" } }, "variables": { "nameGuid": "[guid(concat(subscription().subscriptionId, '-', parameters('resourceGroupName'), '-', parameters('roleAssignment').name))]", "Contributor": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "Reader": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]" }, "resources": [ { "name": "[concat('-deploymentScript-getPrincipalId', '-', parameters('iterator'), '-', parameters('nestedTemplateSuffix'))]", "type": "Microsoft.Resources/deploymentScripts", "apiVersion": "2020-10-01", "identity": { "type": "UserAssigned", "userAssignedIdentities": { "[parameters('runAsIdentity')]": {} } }, "location": "westeurope", "kind": "AzurePowerShell", "properties": { "azPowerShellVersion": "5.6", "cleanupPreference": "Always", "primaryScriptUri": "[concat(parameters('cdStorageAccount'), '/', 'scripts/Get-PrincipalId.ps1', parameters('cdStorageAccountSASToken'))]", "arguments": "[concat('-PrincipalName ', parameters('roleAssignment').name, ' -PrincipalType ', parameters('roleAssignment').principalType)]", "forceUpdateTag": "[parameters('now')]", "retentionInterval": "PT1H", "timeout": "PT15M" } }, { "name": "[variables('nameGuid')]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2020-04-01-preview", "dependsOn": [ "[resourceId('Microsoft.Resources/deploymentScripts', concat('-deploymentScript-getPrincipalId', '-', parameters('iterator'), '-', parameters('nestedTemplateSuffix')))]" ], "properties": { "roleDefinitionId": "[variables(parameters('roleAssignment').builtInRoleType]", "principalId": "[reference(concat('-deploymentScript-getPrincipalId', '-', parameters('iterator'), '-', parameters('nestedTemplateSuffix'))).outputs.principalId]", "principalType": "[parameters('roleAssignment').principalType]" } } ], "outputs": {} } } } ], "outputs": {} }
The one concern you have to realize is the time penalty of this approach. Because deployment scripts have to create and destroy storage accounts and container instances it will not be quick. During my tests I measured execution times between 2 and 4 minutes. If you want to have quick deployments stick to the use of manual looking up principal ids in upfront and passing them in as parameters.
Comments
Post a Comment