AzAPI role definitions

AzAPI role definitions

A name for thy role definitions

AzAPI role definitions

One of the things I miss when switching from the AzureRM to the AzAPI resource provider is the ability to specify role assignments using the role definition name.

In AzureRM, you can do something like this:

1
2
3
4
5
resource "azurerm_role_assignment" "example" {
  scope                = data.azurerm_subscription.primary.id
  role_definition_name = "Reader"
  principal_id         = data.azurerm_client_config.example.object_id
}

See in the above how the built-in role definition “Reader” can be specified by name.

What if you wanted to do this in AzAPI?

Perhaps you have a module that is nearly all written in AzAPI, and you’d like to be able to specify role assignments by name whilst avoiding a dependency on the AzureRM provider.

The same snippet in AzAPI would looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
resource "azapi_resource" "role_assignment" {
 
  type = "Microsoft.Authorization/roleAssignments@2020-04-01-preview"
  name = uuid()

  parent_id = azapi_resource.vnet.id

  body = {
    properties = {
      principalId      = each.value.principal_id
      roleDefinitionId = <somehow need to find this!>
    }
  }
  # etc
}

There’s no role definition name, only a “roleDefinitionId”. Oh.

The roleDefinitionId follows a well-known and familiar resource ID form, that looks like this:

1
/subscriptions/<id>/providers/Microsoft.Authorization/roleDefinitions/<role GUID>

At the end of this ID we need to specify a role GUID, which are listed here: Azure built-in roles - Azure RBAC | Microsoft Learn

So, we’re no closer yet to going from name to ID!

Bicep users are all too aware of this problem:

Provide Azure Built-In role constants · Issue #1895 · Azure/bicep

There are some example workarounds in the above link. The workaround used by Azure Verified Modules is to create the mappings for each applicable role assignment in each module.

However, with AzAPI, we have additional superpowers!

Fetching a role definition ID using AzAPI

I started by seeing how the Az CLI does this task, i.e. how does this work:

az role definition list –name “Contributor”

This returns a JSON response that includes the ID we need (and a bunch of other stuff)

alt text

You could use a null resource provider to call the above CLI, but that is slow and relies on external tooling, so I kept digging.

Under the hood, the Az CLI works by making the following REST API call:

GET https://management.azure.com/subscriptions//providers/Microsoft.Authorization/roleDefinitions?api-version=2022-05-01-preview

The takeaway is the CLI is calling the resource manager API (see the definition on Learn), which means we can mimic this using the azapi_resource_list resource.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
data "azapi_resource_list" "role_definition" {
  type      = "Microsoft.Authorization/roleDefinitions@2022-05-01-preview"
  parent_id = "/subscriptions/${var.subscription_id}"
  query_parameters = {
    "$filter" = ["roleName eq 'Reader'"]
  }
  response_export_values = ["value"]
}

output "role_definition" {
  value = data.azapi_resource_list.role_definition.output
}

The example below takes this one step further by showing how you can retrieve multiple roles:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
variable "subscription_id" {
  description = "The subscription ID to query for role definitions."
  type        = string
}

variable "role_names" {
  description = "Role Definition names to query."
  type        = set(string)
  default     = ["Contributor", "Key Vault Secrets Officer"]
}

data "azapi_resource_list" "role_definition" {
  for_each = var.role_names

  type      = "Microsoft.Authorization/roleDefinitions@2022-05-01-preview"
  parent_id = "/subscriptions/${var.subscription_id}"
  query_parameters = {
    "$filter" = ["roleName eq '${each.key}'"]
  }
  response_export_values = ["value"]
}

output "role_definition" {
  value = { for k, v in data.azapi_resource_list.role_definition : k => v.output.value[*].id}
}

If we terraform plan, we get the following:

alt text

We now have the same capability as AzureRM, to use the role definition name, and have it converted to an ID that can be used with AzAPI:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
resource "azapi_resource" "role_assignment" {

  type = "Microsoft.Authorization/roleAssignments@2020-04-01-preview"
  name = uuid()

  parent_id = azapi_resource.vnet.id

  body = {
    properties = {
      principalId      = each.value.principal_id
      roleDefinitionId = data.azapi_resource_list.role_definition.output.value[*].id
    }
  }
}

The link below points to a gist on Github that illustrates the above (with nicer code formatting!)

This terraform illustrates how to fetch the role definition ID via its common name (e.g. “Reader”), when using AzAPI.

The docs for “azapi_resource_list” is available here:

azapi_resource_list | Data Sources | Azure/azapi | Terraform | Terraform Registry

Happy role assignments with AzAPI!

This post is licensed under CC BY 4.0 by the author.