I have a bicep file as below to create role assignment to resourceGroup scope using azure devops pipeline.
main.bicep
targetScope = 'resourceGroup'
#description('Principal type of the assignee.')
#allowed([
'Device'
'ForeignGroup'
'Group'
'ServicePrincipal'
'User'
])
param principalType string
#description('the id for the role defintion, to define what permission should be assigned')
param RoleDefinitionId string
#description('the id of the principal that would get the permission')
param principalId string
#description('the role deffinition is collected')
resource roleDefinition 'Microsoft.Authorization/roleDefinitions#2018-01-01-preview' existing = {
scope: subscription()
name: RoleDefinitionId
}
resource RoleAssignment 'Microsoft.Authorization/roleAssignments#2020-10-01-preview' = [for id in split(principalId, ','): {
name: guid(resourceGroup().id, RoleDefinitionId, id)
properties: {
roleDefinitionId: roleDefinition.id
principalId: principalId
principalType: principalType
}
}]
parameters file
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"principalType": {
"value": "#{principalType}#"
},
"RoleDefinitionId": {
"value": "#{RoleDefinitionId}#"
},
"principalId": {
"value": "#{principalId}#"
}
}
}
pipeline file.
- rolesname: readerall
environment: development
scope: resourcegroup
principalType: Group
principalid: aaaaaaaaaaaaaa,bbbbbbbbbbbbbbbbb,ccccccccccccccccccccc,ddddddddddddddddddddd
roleDefinitionId: acddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
resourceGroup: aks-rg
- rolesname: reader_apimrg_all
environment: development
scope: resourcegroup
principalType: Group
principalid: aaaaaaaaaaaaaa,bbbbbbbbbbbbbbbbb,ccccccccccccccccccccc,ddddddddddddddddddddd
roleDefinitionId: acddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
resourceGroup: apim-rg
- rolesname: reader_lawrg_all
environment: development
scope: resourcegroup
principalType: Group
principalid: aaaaaaaaaaaaaa,bbbbbbbbbbbbbbbbb,ccccccccccccccccccccc,ddddddddddddddddddddd
roleDefinitionId: acddxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
resourceGroup: la-rg
So Now I tried to ammend my templates to assign the Roles in resource level (looking to assign this to Appinsight). my new bicep file was below.. But its failing completely..
#description('Principal type of the assignee.')
#allowed([
'Device'
'ForeignGroup'
'Group'
'ServicePrincipal'
'User'
])
param principalType string
#description('the id for the role defintion, to define what permission should be assigned')
param RoleDefinitionId string
#description('the id of the principal that would get the permission')
param principalId string
#description('the id of the Resource that would get the RBAC assigned')
param ResourceId string
#description('the role deffinition is collected')
resource roleDefinition 'Microsoft.Authorization/roleDefinitions#2018-01-01-preview' existing = {
scope: subscription()
name: RoleDefinitionId
}
resource RoleAssignment 'Microsoft.Authorization/roleAssignments#2020-10-01-preview' = [for id in split(principalId, ','): {
name: guid(ResourceId, RoleDefinitionId, id)
scope: Insights
properties: {
roleDefinitionId: roleDefinition.id
principalId: id
principalType: principalType
}
}]
So looking for solutions on below requirements.
can I re-use same initail Bicep file used ( the working for resourcegrop level) to have the Role assignments in Resource level as well.
Can I use the same scenario with any kind of resource, for example if I pass the type of resource and name of the resource as input, the RBAC should be assigned to that resource
Related
I am trying to create two reusable bicep modules to allow reading specific secrets in chosen key vaults. To do this, I first declare the role definition:
targetScope = 'subscription'
param subscriptionId string
param resourceGroupName string
param keyVaultName string
param allowedSecrets array
param managementGroupRoot string
var keyVaultScope = '/subscriptions/${subscriptionId}/resourcegroups/${resourceGroupName}/providers/Microsoft.KeyVault/vaults/${keyVaultName}'
var assignableScopes = [for secretName in allowedSecrets: '${keyVaultScope}/secrets/${secretName}']
var roleName = 'Limitied ${keyVaultName} secret reader ${managementGroupRoot}'
// Permissions based on Key Vault Secrets User
// https://www.azadvertizer.net/azrolesadvertizer/4633458b-17de-408a-b874-0445c86b69e6.html
resource key_vault_secrets_user_role_definition 'Microsoft.Authorization/roleDefinitions#2018-01-01-preview' existing = {
name: '4633458b-17de-408a-b874-0445c86b69e6'
}
resource role_definition 'Microsoft.Authorization/roleDefinitions#2018-01-01-preview' = {
name: guid(roleName)
properties: {
roleName: roleName
description: 'Allows reading specific secrets in the ${keyVaultName} key vault in ${managementGroupRoot}'
assignableScopes: assignableScopes
permissions: key_vault_secrets_user_role_definition.properties.permissions
}
}
output roleDefinitionId string = role_definition.id
The role definition creation works well, and it results in this role definition:
{
"assignableScopes": [
"/subscriptions/subscriptionId/resourcegroups/resourceGroupName/providers/Microsoft.KeyVault/vaults/keyVaultName/secrets/secretName",
"/subscriptions/subscriptionId/resourcegroups/resourceGroupName/providers/Microsoft.KeyVault/vaults/keyVaultName/secrets/anotherSecret"
],
"description": "Allows reading specific secrets in the xxx} key vault in xxx",
"id": "/subscriptions/xxx/providers/Microsoft.Authorization/roleDefinitions/xxx",
"name": "c64aa8eb-479d-5c2d-8f25-b1acb151c0af",
"permissions": [
{
"actions": [],
"dataActions": [
"Microsoft.KeyVault/vaults/secrets/getSecret/action",
"Microsoft.KeyVault/vaults/secrets/readMetadata/action"
],
"notActions": [],
"notDataActions": []
}
],
"roleName": "Limitied key vault secret reader xxx",
"roleType": "CustomRole",
"type": "Microsoft.Authorization/roleDefinitions"
}
Next, I want to assign this role to a service principal. Here's where I'm not entirely clear on the details, but since I want this principal to be able to read n number of individual secrets, I made the assmuption that I would need to iterate on the assignable scopes.
To do that, I have a main file:
targetScope = 'managementGroup'
resource roleDefinition 'Microsoft.Authorization/roleDefinitions#2018-01-01-preview' existing = {
name: roleDefinitionId
}
module example 'module.bicep' = {
name: 'example-${managementGroup().name}'
scope: resourceGroup(keyVaultSubscriptionId, keyVaultResourceGroupName)
params: {
roleDefinitionId: roleDefinitionId
assignableScopes: roleDefinition.properties.assignableScopes
managementGroupName: managementGroup().name
keyVaultName: keyVaultName
}
}
The module then looks like this:
targetScope = 'resourceGroup'
param roleDefinitionId string
param assignableScopes array = []
param managementGroupName string
param keyVaultName string
param principalId string
// Full scope looks like this:
// '/subscriptions/<sub>/resourcegroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault>/<secret>'
// Hence 8 is the secret name
// Also verifies that the secrets exist
var secretNames = [for scope in assignableScopes: split(scope, '/')[8]]
resource secretResources 'Microsoft.KeyVault/vaults/secrets#2021-11-01-preview' existing = [for secret in secretNames: {
name: '${keyVaultName}/${secret}'
}]
// Iterating the secretResources array is not supported, so we iterate the scope which they are based
resource regressionTestKeyVaultReaderAssignment 'Microsoft.Authorization/roleAssignments#2020-04-01-preview' = [for (scope, index) in assignableScopes: {
name: guid(managementGroupName, principalId, scope)
scope: secretResources[index] // Access by index and apply this role assignment to all assignable scopes
properties: {
principalId: principalId
roleDefinitionId: roleDefinitionId
}
}]
However, this fails with the following error:
ERROR: ***"code": "InvalidTemplate", "message": "Deployment template validation failed: 'The template resource 'exmaple-xxx' at line '97' and column '5' is not valid: Unable to evaluate template language function 'extensionResourceId': function requires exactly two multi-segmented arguments. The first must be the parent resource id while the second must be resource type including resource provider namespace. Current function arguments '/providers/Microsoft.Management/managementGroups/ESD,Microsoft.Authorization/roleDefinitions,/subscriptions/***/providers/Microsoft.Authorization/roleDefinitions/xxx'. Please see https://aka.ms/arm-template-expressions/#extensionresourceid for usage details.. Please see https://aka.ms/arm-template-expressions for usage details.'.", "additionalInfo": [***"type": "TemplateViolation", "info": ***"lineNumber": 97, "linePosition": 5, "path": "properties.template.resources[6]"***]***
I am using az to deploy in a GitHub pipeline so I tried to access the request and response, to no avail:
$deployment = az deployment mg create | ConvertFrom-Json // additional params
Write-Host "Request: $(ConvertTo-Json -InputObject $deployment.request)" // Request: null
Write-Host "Response: $(ConvertTo-Json -InputObject $deployment.response)" // Response: null
The error is very cryptic to me and I don't really know what is going on as I'm not even using that utility method that is being referenced. I'm guessing the conversion to ARM does something in the background. vscode says everything is fine and dandy.
What am I doing wrong? My only guess is the scope part of the assignment, but I have no ideas on how to correct it.
Any help would be greatly appreciated.
Update
Some additional information that I found while trying to solve this. The validation of the template fails and the deployment doesn't even start. I built both the main and the module bicep files to see if that would give some additional context. The module looks fine but main has an error on the module resource:
So this is in the main file with targetScope = 'managementGroup', and the module with targetScope = 'resourceGroup' shows no validation errors when built.
Update 2
When compiled to ARM, I see the following value is passed from main to the module:
"assignableScopes": {
"value": "[reference(extensionResourceId(managementGroup().id, 'Microsoft.Authorization/roleDefinitions', parameters('secretReaderRoleDefinitionId')), '2018-01-01-preview').assignableScopes]"
},
AFAICT this is 3 arguments, and the error I get in the GitHub pipeline says:
Unable to evaluate template language function 'extensionResourceId': function requires exactly two multi-segmented arguments.
That doesn't seem to be true when reading the docs about that function.
Update 3
The error is produced in a GitHub pipeline where I'm running on ubuntu-latest. I'm going to replicate the same command locally and see If I can get it to work here in case of a runner issue.
Update 4
Exact same error reproduced outside of the GitHub pipeline.
A couple thoughts...
Creating a custom roleDef with limited assignable scopes doesn't have a ton of value from a security perspective, because the built-in roleDef has the same permissions has a broader scope - and the principal that assigns one would be able to assign the other.
If your goal is to simply iterate over the secrets and assign the role to those secrets all you need is the resourceId of those secrets. It looks like you're trying to pull that list from the roleDefinition (instead of passing to the template) which is possible but seems somewhat complex. That would mean that any time you want to "adjust" this deployment you have to define a new role or modify the existing, both have some downstream consequences. There are a finite number of custom roles that can be defined in a tenant and as you change them you could break existing assignments unintentionally (either remove access or inadvertently give access to new ones).
That said, I don't see that specific error in your code but perhaps a few others - try this:
main.bicep
targetScope = 'managementGroup'
param roleDefinitionId string
param keyVaultSubscriptionId string
param keyVaultResourceGroupName string
param keyVaultName string
param principalId string
resource roleDefinition 'Microsoft.Authorization/roleDefinitions#2018-01-01-preview' existing = {
scope: subscription(keyVaultSubscriptionId)
name: roleDefinitionId
}
module example 'module.bicep' = {
name: 'example-${managementGroup().name}'
scope: resourceGroup(keyVaultSubscriptionId, keyVaultResourceGroupName)
params: {
roleDefinitionId: roleDefinitionId
assignableScopes: roleDefinition.properties.assignableScopes
keyVaultName: keyVaultName
principalId: principalId
}
}
module.bicep
targetScope = 'resourceGroup'
param roleDefinitionId string
param assignableScopes array
param keyVaultName string
param principalId string
var secretNames = [for scope in assignableScopes: last(split(scope, '/'))]
resource secretResources 'Microsoft.KeyVault/vaults/secrets#2021-11-01-preview' existing = [for secret in secretNames: {
name: '${keyVaultName}/${secret}'
}]
resource roleDef 'Microsoft.Authorization/roleDefinitions#2022-04-01' existing = {
name: roleDefinitionId
}
resource regressionTestKeyVaultReaderAssignment 'Microsoft.Authorization/roleAssignments#2020-04-01-preview' = [for (scope, index) in assignableScopes: {
name: guid(roleDef.id, principalId, scope)
scope: secretResources[index]
properties: {
principalId: principalId
roleDefinitionId: roleDef.id
}
}]
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
}
}
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.
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 am deploying an aks cluster with agic (application gateway ingress controller) with bicep
In the aks.bicep I declare this
resource aksCluster 'Microsoft.ContainerService/managedClusters#2021-03-01' = {
name: 'aks-core-${env}'
location: resourceGroup().location
identity: {
type: 'SystemAssigned'
}
properties: {
...
addonProfiles: {
...
ingressApplicationGateway: {
enabled: true
config: {
applicationGatewayId: applicationGatewayId
effectiveApplicationGatewayId: applicationGatewayId
}
}
}
}
}
However for some reason the identity created by aks seems to need a role in the resource group created for the node pool, because an error "needs contributor role" appears,so I added this:
resource contributorRoleDefinition 'Microsoft.Authorization/roleDefinitions#2020-08-01-preview' existing = {
scope: subscription()
name: 'xxx'
}
resource aksfix 'Microsoft.Authorization/roleAssignments#2020-04-01-preview' = {
name: guid(resourceGroup().id,'aksfix','Contributor')
scope: resourceGroup()
properties: {
description: 'fixes aks cross resource group principal permissions for agic'
principalId: aksCluster.properties.addonProfiles.ingressApplicationGateway.identity.objectId
principalType: 'ServicePrincipal'
roleDefinitionId: contributorRoleDefinition.id
}
}
Now to be clear this WORKS, but not always. Even if the principal has the appropriate permissions, the same error keeps popping up for 30 min to 1-2 hours and then by magic it works!
I had a similar error when trying to use internal load balancers which requires network contributor to the kubelet principal, and the same behavior appears, it takes an insane amount of time to reflect the role changes, now what seems interesting is a message "if your permissions changed try refreshing your credentials" on the agic pod, is there a way to force a cluster to refresh its credentials (without creating a new service principal)?
You should assign Contributor roles to both the aks magnaged cluster identity to the resource group where the application gateway resources are present and the ingress application gateway identity on the resource group.
So the solution will be better to use user-assigned identity and providing the contributor role and using it in AKS identity like below:
resource aksClusterUserDefinedManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities#2018-11-30' = {
name: 'aksClusterUserDefinedManagedIdentityName'
location: resourceGroup().location
}
resource akscontributorroleassignement 'Microsoft.Authorization/roleAssignments#2020-10-01-preview' = {
name: guid(concat(resourceGroup().id, aksClusterUserDefinedManagedIdentity.name,aksclustername))
scope: resourceGroup()
properties: {
description: 'Contributor role to the AKS identity to access the AGIC reosurce'
principalId: aksClusterUserDefinedManagedIdentity.properties.principalId
principalType: 'ServicePrincipal'
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
}
}
resource aksCluster 'Microsoft.ContainerService/managedClusters#2021-03-01' = {
name: 'aks-core-${env}'
dependsOn:akscontributorroleassignement
location: resourceGroup().location
identity: {
type: 'UserAssigned',
userAssignedIdentities: {aksClusterUserDefinedManagedIdentity.id}
}
properties: {
...
addonProfiles: {
...
ingressApplicationGateway: {
enabled: true
config: {
applicationGatewayId: applicationGatewayId
effectiveApplicationGatewayId: applicationGatewayId
}
}
}
}
}
resource contributorRoleDefinition 'Microsoft.Authorization/roleDefinitions#2020-08-01-preview' existing = {
scope: subscription()
name: 'xxx'
}
resource aksfix 'Microsoft.Authorization/roleAssignments#2020-04-01-preview' = {
name: guid(resourceGroup().id,'aksfix','Contributor')
scope: resourceGroup()
properties: {
description: 'fixes aks cross resource group principal permissions for agic'
principalId: aksCluster.properties.addonProfiles.ingressApplicationGateway.identity.objectId
principalType: 'ServicePrincipal'
roleDefinitionId: contributorRoleDefinition.id
}
}