diff --git a/src/modules/role_assignments/subscription/_outputs.tf b/src/modules/role_assignments/subscription/_outputs.tf index c4e89ed9..f6741b72 100644 --- a/src/modules/role_assignments/subscription/_outputs.tf +++ b/src/modules/role_assignments/subscription/_outputs.tf @@ -1,12 +1,18 @@ output "subscription_assignments" { - description = "Mapping of users to built-in role assignments at the subscription scope" + description = "Resolved subscription-level role assignments" + value = { for key, assignment in azurerm_role_assignment.assignments : key => { - user_principal_name = assignment.principal_id != "" ? data.azuread_user.users[local.flat_assignments[key].user].user_principal_name : null - role = local.flat_assignments[key].role - role_assignment_id = assignment.id - scope = assignment.scope + role = local.flat_assignments[key].role + principal_id = assignment.principal_id + + # UPN only when principal is a user (non-GUID) + user_principal_name = ( + contains(keys(data.azuread_user.users), local.flat_assignments[key].principal) + ? data.azuread_user.users[local.flat_assignments[key].principal].user_principal_name + : null + ) } } } diff --git a/src/modules/role_assignments/subscription/main.tf b/src/modules/role_assignments/subscription/main.tf index e7714b3a..fb732893 100644 --- a/src/modules/role_assignments/subscription/main.tf +++ b/src/modules/role_assignments/subscription/main.tf @@ -1,5 +1,5 @@ variable "subscription_assignments" { - description = "Map of built-in role name to list of user UPNs" + description = "Map of built-in role name to list of user UPNs or service principal object IDs" type = map(list(string)) } @@ -10,29 +10,48 @@ variable "global_settings" { data "azurerm_client_config" "current" {} locals { + # Build a flat list: "_" => { role = "...", principal = "..." } flat_assignments = merge([ - for role, users in var.subscription_assignments : { - for user in users : "${role}_${user}" => { - role = role - user = user + for role, principals in var.subscription_assignments : { + for principal in principals : + "${role}_${principal}" => { + role = role + principal = principal } } ]...) } -# Lookup Azure AD user by UPN +# Lookup Azure AD users (UPNs) data "azuread_user" "users" { for_each = { - for assignment in local.flat_assignments : assignment.user => assignment.user + for _, assignment in local.flat_assignments : + assignment.principal => assignment.principal + if !can(regex("^[0-9a-fA-F-]{36}$", assignment.principal)) } user_principal_name = each.value } +# Lookup service principals (GUIDs) +data "azuread_service_principal" "sps" { + for_each = { + for _, assignment in local.flat_assignments : + assignment.principal => assignment.principal + if can(regex("^[0-9a-fA-F-]{36}$", assignment.principal)) + } + + object_id = each.value +} + resource "azurerm_role_assignment" "assignments" { for_each = local.flat_assignments - principal_id = data.azuread_user.users[each.value.user].object_id + principal_id = try( + data.azuread_user.users[each.value.principal].object_id, + data.azuread_service_principal.sps[each.value.principal].object_id + ) + role_definition_name = each.value.role scope = "/subscriptions/${data.azurerm_client_config.current.subscription_id}" } diff --git a/src/role_assignments.tf b/src/role_assignments.tf index 7c041a61..6a8c16eb 100644 --- a/src/role_assignments.tf +++ b/src/role_assignments.tf @@ -17,6 +17,7 @@ module "role_assignments" { role_definitions = module.role_definitions disk_encryption_sets = module.disk_encryption_sets container_registries = module.container_registries + application_gateways = module.application_gateways linux_virtual_machines = { for key, vm in module.virtual_machines : key => vm.linux_virtual_machines[0] @@ -42,7 +43,7 @@ module "role_assignments" { module "subscription_assignments" { source = "./modules/role_assignments/subscription" count = length( - try(var.subscription_assignments.built_in_roles, []) + try(var.subscription_assignments, []) ) > 0 ? 1 : 0 subscription_assignments = var.subscription_assignments.built_in_roles