Skip to content

Repository files navigation

Terraform Azure AD Role Assignment

Manage Microsoft Entra (Azure AD) directory roles: activate built-in roles, define custom directory roles, and assign either to users, groups or service principals, with optional scoping. The companion to terraform-azuread-service-principal for the directory-RBAC side of identity.

CI Release Terraform Registry License


Usage

module "role_assignment" {
  source  = "libre-devops/role-assignment/azuread"
  version = "~> 4.0"

  activated_directory_roles = {
    reader = { display_name = "Directory Readers" }
  }

  role_assignments = {
    ci-reader = {
      directory_role_key  = "reader"
      principal_object_id = "00000000-0000-0000-0000-000000000000" # a user, group or SP object id
    }
  }
}

Role references

A role assignment points at its role in exactly one of three ways:

  • directory_role_key - a key from activated_directory_roles (assigns a built-in role by its template id)
  • custom_role_key - a key from custom_directory_roles (assigns a custom role by its object id)
  • role_id - a raw template id (built-in) or object id (custom), for roles managed elsewhere

Graph application permission grants (the managed-identity shape)

graph_app_role_grants grants Microsoft Graph application permissions (admin consent) to EXISTING principals, which is the shape managed identities need: a Logic App, Function App, automation, or VM identity doing app-only Graph has no app registration of its own, so the service-principal module's in-call grants cannot reach it and directory roles are the wrong tool. Permissions are written as names and resolved against the tenant's Graph service principal:

module "graph_grants" {
  source  = "libre-devops/role-assignment/azuread"
  version = "~> 4.2"

  graph_app_role_grants = {
    "logic-app-mi" = {
      principal_object_id = module.logic_app_workflow.identities["logic-ldo-uks-prd-001"].principal_id
      role_names          = ["ServiceMessage.Read.All", "Tasks.ReadWrite.All"]
    }
  }
}

A check block flags escalation-capable permissions (Directory.ReadWrite.All, RoleManagement.ReadWrite.Directory, Application.ReadWrite.All, AppRoleAssignment.ReadWrite.All; the set is caller-extendable) so a privileged grant is always a visible decision, never a quiet one.

Required permissions

Managing directory roles (activation, custom roles and assignments) needs the running principal to hold Microsoft Graph RoleManagement.ReadWrite.Directory, that is the Privileged Role Administrator directory role. graph_app_role_grants additionally needs AppRoleAssignment.ReadWrite.All (each grant IS tenant-wide admin consent). A check block flags any attempt to touch Global Administrator so it is never assigned by accident.

Examples

  • examples/minimal - the smallest valid call: one custom directory role.
  • examples/complete - activate a built-in role, create a custom role, and assign both to a principal; plus a user-assigned managed identity granted a benign Graph application permission by name through graph_app_role_grants.

Requirements

Name Version
terraform >= 1.9.0, < 2.0.0
azuread >= 3.0.0, < 4.0.0

Providers

Name Version
azuread >= 3.0.0, < 4.0.0

Modules

No modules.

Resources

Name Type
azuread_app_role_assignment.graph resource
azuread_custom_directory_role.this resource
azuread_directory_role.this resource
azuread_directory_role_assignment.this resource
azuread_application_published_app_ids.well_known data source
azuread_client_config.current data source
azuread_service_principal.msgraph data source

Inputs

Name Description Type Default Required
activated_directory_roles Built-in Entra (Azure AD) directory roles to activate in the tenant, keyed by a stable logical
name. Built-in roles exist from templates but are dormant until activated; activating one
exports its object id so assignments can be made against it. Reference an entry from
role_assignments with directory_role_key. Provide exactly one of display_name or template_id
per entry.
map(object({
display_name = optional(string)
template_id = optional(string)
}))
{} no
custom_directory_roles Custom Entra directory roles to create, keyed by a stable logical name. Each carries one or
more permissions blocks listing allowed_resource_actions (see the Microsoft permissions
reference). Reference an entry from role_assignments with custom_role_key.
map(object({
display_name = string
description = optional(string)
enabled = optional(bool, true)
version = optional(string, "1.0")
template_id = optional(string)
permissions = list(object({
allowed_resource_actions = list(string)
}))
}))
{} no
graph_app_role_grants Microsoft Graph APPLICATION permission grants (admin consent) to EXISTING principals, keyed by a
logical label you choose. This is the managed-identity shape the rest of the family cannot reach: a
Logic App, Function App, automation, or VM identity that needs app-only Graph has no app
registration of its own, so the service-principal module's in-call grants cannot target it, and
directory roles are the wrong tool. role_names take Graph permission names
(ServiceMessage.Read.All) and resolve against the tenant's Microsoft Graph service principal, so
configuration reads like the permission reference rather than GUID soup; role_ids take explicit
app role GUIDs for anything unusual. Each grant IS tenant-wide admin consent, and creating one needs
AppRoleAssignment.ReadWrite.All (or Global Administrator) on the applier.
map(object({
principal_object_id = string
role_names = optional(list(string), [])
role_ids = optional(list(string), [])
}))
{} no
privileged_graph_app_role_ids Graph application permissions treated as privileged by the warning check (name => app role GUID on
the Microsoft Graph service principal): each one is escalation-capable, able to grant or rewrite its
way to broader tenant control. Extend this to treat more permissions as privileged.
map(string)
{
"AppRoleAssignment.ReadWrite.All": "06b708a9-e830-4db3-a914-8e69da51d44f",
"Application.ReadWrite.All": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9",
"Directory.ReadWrite.All": "19dbc75e-c2e2-444c-a770-ec69d8559fc7",
"RoleManagement.ReadWrite.Directory": "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8"
}
no
role_assignments Directory role assignments to create, keyed by a stable logical name. Each assigns one role to
one principal (a user, group or service principal object id), optionally scoped to a single
directory object (directory_scope_id) or an application-specific scope (app_scope_id).

Reference the role in exactly one of three ways:
- directory_role_key: a key from activated_directory_roles (assigns the built-in by template id)
- custom_role_key: a key from custom_directory_roles (assigns the custom role by object id)
- role_id: a raw template id (built-in) or object id (custom), for roles managed elsewhere
map(object({
principal_object_id = string
directory_role_key = optional(string)
custom_role_key = optional(string)
role_id = optional(string)
app_scope_id = optional(string)
directory_scope_id = optional(string)
}))
{} no

Outputs

Name Description
activated_directory_role_object_ids Map of activated built-in directory role key to its object id.
activated_directory_role_template_ids Map of activated built-in directory role key to its template id (the value used when assigning a built-in role).
custom_directory_role_object_ids Map of custom directory role key to its object id (the value used when assigning a custom role).
custom_directory_roles Map of custom directory role key to its useful attributes.
graph_app_role_grant_ids Map of grant instance key (label|permission) to the app role assignment id.
role_assignment_ids Map of role assignment key to the directory role assignment id.

About

🛡️ Terraform module for Microsoft Entra (Azure AD) directory roles: activate built-in roles, define custom roles, and assign them to users, groups or service principals

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages