Cannot assign Reservations Reader permission to a service principal via terraform azurerm_role_assignment resource over "/providers/Microsoft.Capacity" scope

Tomasz Kluczkowski 1 Reputation point
2023-12-18T18:37:33.85+00:00

Hi,

I am trying to assign Reservations Reader role to a service principal created in azure (this requires "/providers/Microsoft.Capacity" as scope).

All resources like azuread_Application/service principal etc are generated via terraform code. I show only the key bits.

This code fails to work:

resource "azurerm_role_assignment" "reservations_reader" {
  scope              = data.azurerm_subscription.primary.id
  role_definition_id = "/providers/Microsoft.Authorization/roleDefinitions/582fc458-8989-419f-a480-75249bc5db7e"
  principal_id       = azuread_service_principal.main.object_id}

with this message (which I expected, since Reservations Reader has to be assigned on Microsoft.Capacity scope:

Error: authorization.RoleAssignmentsClient#Create: Failure responding to request: StatusCode=400 -- Original Error: autorest/azure: Service returned an error. Status=400 Code="RoleAssignmentScopeNotAssignableToRoleDefinition" Message="The role Reservations Reader is not available for assignment at the requested scope."

But if I change code to use /providers/Microsoft.Capacity as scope:

resource "azurerm_role_assignment" "reservations_reader" {
  scope              = "/providers/Microsoft.Capacity"
  role_definition_id = "/providers/Microsoft.Authorization/roleDefinitions/582fc458-8989-419f-a480-75249bc5db7e"
  principal_id       = azuread_service_principal.main.object_id
}

I get the obvious error:

│   41:   scope              = "/providers/Microsoft.Capacity"
│ 
╵
╷
│ Error: Can not parse "scope" as a resource id: No subscription ID found in: "providers/Microsoft.Capacity"
│ 
│   with azurerm_role_assignment.reservations_reader,
│   on main.tf line 41, in resource "azurerm_role_assignment" "reservations_reader":
│   41:   scope              = "/providers/Microsoft.Capacity"

Which means scopes can only be in form of a resource based id sadly as below:

│ Expected a ResourceGroup ID that matched (containing 4 segments):
│ 
│ > /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group

I had to go for a workaround using null_resource, but that is unable to assign this permission using a non-interactive login with a service principal dedicated to terraform pipelines in ci/cd.

It requires me, with my own azure user to run the commands from a terminal and using an interactive login via browser for it to work (I have the necessary permissions to assign other's permissions).

resource "null_resource" "reservations_reader_role_assignment" {
  triggers = {
    tenant_id                     = data.azurerm_subscription.primary.tenant_id
    insight_application_object_id = azuread_service_principal.main.object_id
  }

  provisioner "local-exec" {
    command     = <<-EOF
      Connect-AzAccount -Tenant ${self.triggers.tenant_id}
      New-AzRoleAssignment -Scope '/providers/Microsoft.Capacity' -ObjectId ${self.triggers.insight_application_object_id} -RoleDefinitionName 'Reservations Reader'
    EOF
    interpreter = ["pwsh", "-Command"]
  }

If there is no way to assign Reservations Reader permission using plain azurerm_role_assignment, how can I make a non interactive login before New-AzRoleAssignment work for a service principal dedicated to running ci/cd pipelines?

I already tried with this code:

$SecurePassword = ConvertTo-SecureString -String 
Community Center | Not monitored
{count} votes

2 answers

Sort by: Most helpful
  1. Jared Holgate 5 Reputation points Microsoft Employee
    2023-12-22T10:49:13.2833333+00:00

    Hi @Tomasz Kluczkowski

    I took a look into this and see it is a limitation of the azurerm provider.

    Specifically the validation applied here: https://github.com/hashicorp/terraform-provider-azurerm/blob/69b5d740e43e0e6ed3bb1674dab76a8d5c42c02b/internal/services/authorization/role_assignment_resource.go#L63

    You could raise an issue or PR against the provider to get this resolved. It would be straight forward to fix by adding another validator or replacing the final validator in there with this one: https://github.com/hashicorp/terraform-provider-azurerm/blob/69b5d740e43e0e6ed3bb1674dab76a8d5c42c02b/helpers/azure/resourceid.go#L113

    0 comments No comments

  2. Vava 0 Reputation points
    2024-02-29T13:04:52.7433333+00:00

    Hi!

    We tried to go around this limitation of the azurerm provider by using the azapi provider. I think this would've worked but we faced the issue that the service principal applying the terraform would need to be a Global Admin within the tenant, which we do not want.

    Here is what we tried, if somebody wants to give it a try:

    data "azurerm_role_definition" "reservations_reader" {
      name = "Reservations Reader"
    }
    
    resource "random_uuid" "reservations_reader" {}
    
    # Assign "Reservations Reader" role to SP.
    # This role allows listing all reservations under the tenant.
    # https://learn.microsoft.com/en-us/azure/cost-management-billing/reservations/view-reservations#tenant-level-access
    # This role is not available using the azurerm_role_assignment resource, so we use the azapi_resource to create it.
    # Issue:
    # https://learn.microsoft.com/en-us/answers/questions/1464207/cannot-assign-reservations-reader-permission-to-a
    # Azapi Docs:
    # https://learn.microsoft.com/en-us/azure/templates/microsoft.authorization/roleassignments?pivots=deployment-language-terraform
    resource "azapi_resource" "reservations_reader" {
      type      = "Microsoft.Authorization/roleAssignments@2022-04-01"
      name      = random_uuid.reservations_reader.result
      parent_id = "/providers/Microsoft.Capacity"
      body = jsonencode({
        properties = {
          conditionVersion = "2.0"
          description      = "reservations-reader"
          principalId      = azuread_service_principal.reservations_reader.object_id
          principalType    = "ServicePrincipal"
          roleDefinitionId = data.azurerm_role_definition.reservations_reader.id
        }
      })
    }
    
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.