Azure / bicep-registry-modules

Bicep registry modules
MIT License
434 stars 286 forks source link

[AVM Module Issue]: resource group Role Assignment issue #2688

Open darrenFrowen opened 2 weeks ago

darrenFrowen commented 2 weeks ago

Check for previous/existing GitHub issues

Issue Type?

I'm not sure

Module Name

avm/ptn/authorization/role-assignment

(Optional) Module Version

0.1.1

Description

Hi,

I am trying to assign a service principle the Contributor Role to a resource group. My main.bicep is scoped to subscription as i am deploying multiple resource groups and resources. This is the main.bicep showing the targetScope and required parameters..

targetScope = 'subscription'

param principalId string
param location string
param hubPrefix string

@description('This is the existing Hub Virtual Network Resource Group')
resource hubVirtualNetworkServicesRg 'Microsoft.Resources/resourceGroups@2021-01-01' existing = {
  name: 'rg-${hubPrefix}-network'
}

If in my main.bicep my module is set like this using as described below.

Parameter: resourceGroupName

Name of the Resource Group to assign the RBAC role to. If Resource Group name is provided, and Subscription ID is provided, the module deploys at resource group level, therefore assigns the provided RBAC role to the resource group.

I set the Resource Group Name and Subscription ID to assign the role to the resource group. Then i see the same error.

Scope "subscription" is not valid for this module. Permitted scopes: "managementGroup".bicep(BCP134)
module roleAssignment 'br/public:avm/ptn/authorization/role-assignment:0.1.1' = {
  name: 'roleAssignmentDeployment'
  params: {
    principalId: principalId
    roleDefinitionIdOrName: 'Contributor'
    description: 'Role Assignment (resource group scope)'
    location: location
    principalType: 'ServicePrincipal'
    resourceGroupName: hubVirtualNetworkServicesRg.name
    subscriptionId: subscription().subscriptionId
  }
}

Can you please advise how this module shoould be formatted if my current target scope is allready at subscription scope? if i negate the resourceGroupName and subscriptionId and set the scope on the module i see this error instead.

Scope "resourceGroup" is not valid for this module. Permitted scopes: "managementGroup".bicep(BCP134)
module roleAssignment 'br/public:avm/ptn/authorization/role-assignment:0.1.1' = {
  name: 'roleAssignmentDeployment'
  scope: hubVirtualNetworkServicesRg
  params: {
    principalId: principalId
    roleDefinitionIdOrName: 'Contributor'
    description: 'Role Assignment (resource group scope)'
    location: location
    principalType: 'ServicePrincipal'
  }
}

(Optional) Correlation Id

No response

microsoft-github-policy-service[bot] commented 2 weeks ago

[!IMPORTANT] The "Needs: Triage :mag:" label must be removed once the triage process is complete!

[!TIP] For additional guidance on how to triage this issue/PR, see the BRM Issue Triage documentation.

avm-team-linter[bot] commented 2 weeks ago

@darrenFrowen, thanks for submitting this issue for the avm/ptn/authorization/role-assignment module!

[!IMPORTANT] A member of the @Azure/avm-ptn-authorization-roleassignment-module-owners-bicep or @Azure/avm-ptn-authorization-roleassignment-module-contributors-bicep team will review it soon!

arnoldna commented 2 weeks ago

@darrenFrowen - Thanks for the feedback. When using this module you must deploy at the management group scope. Here is a link to a similar issue where we discuss the reasons why. https://github.com/Azure/bicep-registry-modules/issues/2519

darrenFrowen commented 2 weeks ago

Hi @arnoldna,

This is what is confusing me as I had a working RBAC assignment to a resource Group using this method using a non AVM resource module. Here you can see the main.bicep has target scope 'subscription' and the the mainModule.bicep has the default scope resourceGroup (no scope defined). This works fine using the following deployment method current subscription scope.

# Deploy the Bicep template
New-AzDeployment -name $deploymentName `
-TemplateParameterFile $paramFile `
-TemplateFile $templateFile `
-Location $location 

Therefore i proceeded to raise an issue #2612 against the 'resource-role-assignment' module as effectively this is what i am doing albeit without the AVM module required parameter 'resourceId'. The scope in my main.bicep module takes care of the resourceId effectively. The #2612 issue was closed on me saying the following.

I see now your issue. You are trying to assign a contributor role to a resource group.
That won't work with this module.
As the description says: "This module deploys a Role Assignment for a specific resource."
Resource Group != Resource

To me all the options 'resource-role-assignment' and 'role-assignment' use the same resource template ''Microsoft.Authorization/roleAssignments@2022-04-01' so i believe its just matter of changing one of the templates to make the 'resourceId' optional = null parameter if not present?

resourceId: !empty(resourceId) ? resourceId: null

What are your thoughts on this please?

main.bicep

targetScope = 'subscription'

param hubPrefix string
param principalId string

@description('This is the existing Hub Virtual Network Resource Group')
resource hubVirtualNetworkServicesRg 'Microsoft.Resources/resourceGroups@2021-01-01' existing = {
  name: 'rg-${hubPrefix}-network'
}

@description('This is the required vault Role assignment for Virtual Network Resource Group')
module backupVaultVnetRgRoleAssignment 'mainModule.bicep' = {
  name: 'backupVaultVnetRgRoleAssignment'
  scope: hubVirtualNetworkServicesRg
  params: {
    principalId: principalId
    roleDefinitionId: 'Contributor'
    scope: hubVirtualNetworkServicesRg.id
  }
}

mainModule.bicep


@description('DEploy Role Assignment at the specified scope')
param principalId string
param roleDefinitionId string
param scope string

@description('Deploy Role Assignment at the specified scope')
resource resourceRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(scope, principalId, roleDefinitionId)
  properties: {
    principalId: principalId
    roleDefinitionId: roleDefinitionId
  }
}
AlexanderSehr commented 2 weeks ago

Hey @darrenFrowen, let me chime in here and add my 5 cents since I know the module since the CARML days. There seems to be a fundamental misunderstanding/confusion regarding the a ARM/Bicep deployment's scope and what impact that has on your deployments.

First things first,

Now a bit of history as to why this module is designed in the way it is: As said before, it has been around since the CARML days, and in CARML we're targeting an inner-sourcing scenario. What it's CI did was to take the modules (like the ones in AVM) and publish them and all their nested child-modules as their own modules. For example, you ended up with a KeyVault module, a key module, a secret module etc. For this module here that meant that 4 modules got published

Back to AVM we are currently not able to publish child/nested modules which is why only this very first top-level main.bicep is published and to wrap the story up, why you also only find a key-vault module, but not 'key-vault.keys' or 'key-vault.secrets' module in the registry. It's something we want to somehow make possible, but it's not a quick fix.

In any case - given the described scope issue you cannot achieve what you want to achieve with the given role-assignment modules, unless you're willing to run the role-assignment module from an initial management-group scope. It's not the first time this confusion came up, and in full transparency I adviced against migrating this module for that very reason. It's child modules are useful - the parent, not really. It would instead have been better to split the module in multiple independent ptn modules that would only target their respective scope. So to unblock yourself, I highly recommend to do what you did in your last comment and run your own custom role assignment.

It's far from satisfying, but I hope what I wrote above at least makes sense to explain what's happening - or rather not happening.

darrenFrowen commented 2 weeks ago

Hi @AlexanderSehr ,

Thank you for the detailed and historical CARML information. Even though i had noticed the three modules scopes i hadn't noticed the scope in the top level main.bicep. So now understand why i was getting the error. I completely agree with you where you say."It would instead have been better to split the module in multiple independent ptn modules that would only target their respective scope".

Given what you have said and the fact that the role-assignment module cannot be changed. I am wondering if i either need to re-open #2612 as this is at the correct scope. However i can see that the resource-role-assignment is actually calling a roleAssignment ARM template module not sure why? Or request a new ptn module specifically for resource-group assignment. I would suspect this issue is going to repeat itself many times over.

For context the reason i needed the role assignment at the resource group level was the pre req required to configure private endpoint for a Recovery Services vault sub resource 'AzureBackup'. This requires the vault to have Contributor role on the Virtual Network Resource Group and the Private DNS Zone resource group. So anyone else doing this trying to use AVM will hit the same issue unless they have started at scope management group which i reckon is unlikely.

Please let me know your thoughts.

AlexanderSehr commented 2 weeks ago

and the fact that the role-assignment module cannot be changed. I am wondering if i either need to re-open #2612 as this is at the correct scope. However i can see that the resource-role-assignment is actually calling a roleAssignment ARM template module not sure why? Or request a new ptn module specifically for resource-group assignment. I would suspect this issue is going to repeat itself many times over.

Hey @darrenFrowen, makes sense to me. I connected with @arnoldna in the background and we'll discuss what the best next steps would be (e.g., if it's worth creating 3 sepeate ptn modules, or if there is another alternative). Mind you, just 'splitting' the module in 3 is also not as straight forwad as it may seem. Technically it's easy - but - if the ptn ends up just deploying a role assignment to a scope, the question may be asked what the added value of that module is compared to a Bicep-native deployment (aside from saving you to create a file) 😄

Regarding re-opening #2612, I'd say - no. As I tried to explain in my original response, that module won't do you any good either as it was published in the resource group scope. This means its role assignments can only target resources inside a resource group, not the resource group itself (which would be at the subscription scope).

I can anyhow explain though why it uses an ARM template as it does indeed appear odd on first sight, but bare with me. In Bicep, if you want to assign a role to a resource you always need a direct reference to that resource. For example:

resource searchService 'Microsoft.Search/searchServices@2024-03-01-preview' = {
  name: name
  // (...)
  properties: {
    // (...)
  }
}

resource searchService_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: 'TheCakeIsALie'
  properties: {
    // (...)
  }
  scope: searchService
}

or

resource searchService 'Microsoft.Search/searchServices@2024-03-01-preview' existing = {
  name: name
}

resource searchService_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: 'TheCakeIsALie'
  properties: {
    // (...)
  }
  scope: searchService
} 

What you can not do (and that's the kicker) is something like

resource searchService_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: 'TheCakeIsALie'
  properties: {
    // (...)
  }
  scope: '/subscriptions/(...)/resourceGroups/(...)/providers/Microsoft.Search/searchServices/mySearchService'
}

That is, to set the scope dynamically via a resource Id. Reason being: Bicep wants to help you set the correct values, but by doing so also isn't as dynamic as native ARM which relies primarily on primitive types like strings. This limitation leads to the impossiblity of a role assignment module that can be applied to every resource using native Bicep.

So what @peterbud applies in his module is a little trick. By using an ARM template for the actual role assignment, he can pass the 'scope' of a resource as a string (instead of a 'resource' reference like in the first 2 examples), and ARM will just accept that and do whatever magic in the background to make the role assignment happen: https://github.com/Azure/bicep-registry-modules/blob/021b0a706fb4af400ae024e01bc2f7aa4e5fae5e/avm/ptn/authorization/resource-role-assignment/modules/generic-role-assignment.json#L40


Small sidenote: We apply a similar trick in the StorageAccount file shares module to work around a resource provider bug: https://github.com/Azure/bicep-registry-modules/blob/021b0a706fb4af400ae024e01bc2f7aa4e5fae5e/avm/res/storage/storage-account/file-service/share/modules/nested_roleAssignment.bicep#L61

darrenFrowen commented 2 weeks ago

@AlexanderSehr Hi,

Yes i just tried to use the other module resource-role-assignment but failed to get it working locally and thank you again for the detailed explanation another one i never knew around Bicep limitations regarding the limitation of the scope compared to that of ARM.

So just to share with you what i have done. I copied the content from your role-assignment sub module scoped at the resource group unedited 'as-is' into a local folder and file 'modules/templates/role/main.bicep' . So here is a snipet from my main.bicep taking the vaultSystemAssignedMIPrincipalId as an output from my backup vault module ran it and worked absolutely fine. So if this could be used as a separate pattern module specifically for resource-group-role?

targetScope = 'subscription'

@description('This is Azure Recovery Services Vault and Vault Policy resources')
module sharedBackupVault './modules/01-sharedBackupVault.bicep' = {
  name: 'SharedBackupVaultModule'
  scope: sharedBackupVaultResourceGroup
  params: {
    // (...)
  }
}

@description('This is the existing Hub Virtual Network Resource Group')
resource hubVirtualNetworkServicesRg 'Microsoft.Resources/resourceGroups@2021-01-01' existing = {
  name: 'rg-${hubPrefix}-network'
}

@description('This is the built-in Contributor role. See https://docs.microsoft.com/azure/role-based-access-control/built-in-roles#contributor')
resource contributorRoleDefinition 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' existing = {
  scope: subscription()
  name: 'b24988ac-6180-42a0-ab88-20f7382dd24c'
}

@description('This AVM role assignment "Works" for Contributor Role on the RG Scope')
module avmRoleAssignment 'modules/templates/role/main.bicep' = {
  name: 'roleAssignmentDeployment'
  scope: hubVirtualNetworkServicesRg
  params: {
    principalId: sharedBackupVault.outputs.vaultSystemAssignedMIPrincipalId
    roleDefinitionIdOrName: contributorRoleDefinition.id
    principalType: 'ServicePrincipal'
  }
}