From e8430c5b398c4f78a2381aa88760854f9a23cd24 Mon Sep 17 00:00:00 2001 From: Borislav Raynov Date: Mon, 24 Nov 2025 12:39:33 +0200 Subject: [PATCH 1/8] Add resource to module's resources --- src/role_assignments.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/src/role_assignments.tf b/src/role_assignments.tf index 7c041a61..f1e5e5d1 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] From de6bcccbb94b743b7815c0c60cfb05394933e7a9 Mon Sep 17 00:00:00 2001 From: Borislav Raynov Date: Mon, 24 Nov 2025 15:39:58 +0200 Subject: [PATCH 2/8] Add sp handling for sub role assignment --- .../role_assignments/subscription/main.tf | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/modules/role_assignments/subscription/main.tf b/src/modules/role_assignments/subscription/main.tf index e7714b3a..fcf746c6 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,6 +10,7 @@ variable "global_settings" { data "azurerm_client_config" "current" {} locals { + # Build a flat list: "_" => { role = "...", user = "..." } flat_assignments = merge([ for role, users in var.subscription_assignments : { for user in users : "${role}_${user}" => { @@ -20,19 +21,36 @@ locals { ]...) } -# Lookup Azure AD user by UPN +# Lookup Azure AD user by UPN only (skip GUIDs) data "azuread_user" "users" { for_each = { - for assignment in local.flat_assignments : assignment.user => assignment.user + for assignment in local.flat_assignments : + assignment.user => assignment.user + if !can(regex("^[0-9a-fA-F-]{36}$", assignment.user)) } user_principal_name = each.value } +# Lookup service principal by GUID +data "azuread_service_principal" "sps" { + for_each = { + for assignment in local.flat_assignments : + assignment.user => assignment.user + if can(regex("^[0-9a-fA-F-]{36}$", assignment.user)) + } + + 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.user].object_id, + data.azuread_service_principal.sps[each.value.user].object_id + ) + role_definition_name = each.value.role scope = "/subscriptions/${data.azurerm_client_config.current.subscription_id}" } From 56d131b81ac258718a5f06673240a5a6bb9ae9cc Mon Sep 17 00:00:00 2001 From: Borislav Raynov Date: Tue, 25 Nov 2025 07:39:40 +0200 Subject: [PATCH 3/8] Fix rule for instantiating module --- src/role_assignments.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/role_assignments.tf b/src/role_assignments.tf index f1e5e5d1..d1ee9479 100644 --- a/src/role_assignments.tf +++ b/src/role_assignments.tf @@ -46,7 +46,7 @@ module "subscription_assignments" { try(var.subscription_assignments.built_in_roles, []) ) > 0 ? 1 : 0 - subscription_assignments = var.subscription_assignments.built_in_roles + subscription_assignments = var.subscription_assignments global_settings = local.global_settings } From 59f399a6dcf54d78a7df0b571fabe03cad092640 Mon Sep 17 00:00:00 2001 From: Borislav Raynov Date: Tue, 25 Nov 2025 07:40:47 +0200 Subject: [PATCH 4/8] Fix reference --- src/role_assignments.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/role_assignments.tf b/src/role_assignments.tf index d1ee9479..6a947ec7 100644 --- a/src/role_assignments.tf +++ b/src/role_assignments.tf @@ -43,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 From 38abc160c3b8d969fb6dcc8622b37a2fb48b7d58 Mon Sep 17 00:00:00 2001 From: Borislav Raynov Date: Tue, 25 Nov 2025 07:52:16 +0200 Subject: [PATCH 5/8] Fix outputs --- .../role_assignments/subscription/_outputs.tf | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/modules/role_assignments/subscription/_outputs.tf b/src/modules/role_assignments/subscription/_outputs.tf index c4e89ed9..9508468e 100644 --- a/src/modules/role_assignments/subscription/_outputs.tf +++ b/src/modules/role_assignments/subscription/_outputs.tf @@ -1,12 +1,26 @@ +# output "subscription_assignments" { +# description = "Mapping of users to built-in role assignments at the subscription scope" +# 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 +# } +# } +# } output "subscription_assignments" { - description = "Mapping of users to built-in role assignments at the subscription scope" 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 + principal_id = assignment.principal_id + + user_principal_name = ( + contains(keys(data.azuread_user.users), local.flat_assignments[key].user) + ? data.azuread_user.users[local.flat_assignments[key].user].user_principal_name + : null + ) } } } From dde5e508e12f8c351462036650b35269ca7cdc15 Mon Sep 17 00:00:00 2001 From: Borislav Raynov Date: Tue, 25 Nov 2025 07:56:38 +0200 Subject: [PATCH 6/8] Remove comments --- .../role_assignments/subscription/_outputs.tf | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/modules/role_assignments/subscription/_outputs.tf b/src/modules/role_assignments/subscription/_outputs.tf index 9508468e..63dac924 100644 --- a/src/modules/role_assignments/subscription/_outputs.tf +++ b/src/modules/role_assignments/subscription/_outputs.tf @@ -1,15 +1,3 @@ -# output "subscription_assignments" { -# description = "Mapping of users to built-in role assignments at the subscription scope" -# 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 -# } -# } -# } output "subscription_assignments" { value = { for key, assignment in azurerm_role_assignment.assignments : From 2ec4a0bf513a55c9e98744f1f5b368798605fc71 Mon Sep 17 00:00:00 2001 From: Borislav Raynov Date: Tue, 25 Nov 2025 16:09:25 +0200 Subject: [PATCH 7/8] Change module to work with different settings --- .../role_assignments/subscription/_outputs.tf | 22 +++++++++++-- .../role_assignments/subscription/main.tf | 31 ++++++++++--------- src/role_assignments.tf | 2 +- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/modules/role_assignments/subscription/_outputs.tf b/src/modules/role_assignments/subscription/_outputs.tf index 63dac924..753a1469 100644 --- a/src/modules/role_assignments/subscription/_outputs.tf +++ b/src/modules/role_assignments/subscription/_outputs.tf @@ -1,12 +1,30 @@ +# output "subscription_assignments" { +# value = { +# for key, assignment in azurerm_role_assignment.assignments : +# key => { +# principal_id = assignment.principal_id + +# user_principal_name = ( +# contains(keys(data.azuread_user.users), local.flat_assignments[key].user) +# ? data.azuread_user.users[local.flat_assignments[key].user].user_principal_name +# : null +# ) +# } +# } +# } output "subscription_assignments" { + description = "Resolved subscription-level role assignments" + value = { for key, assignment in azurerm_role_assignment.assignments : key => { + 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].user) - ? data.azuread_user.users[local.flat_assignments[key].user].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 fcf746c6..fb732893 100644 --- a/src/modules/role_assignments/subscription/main.tf +++ b/src/modules/role_assignments/subscription/main.tf @@ -10,34 +10,35 @@ variable "global_settings" { data "azurerm_client_config" "current" {} locals { - # Build a flat list: "_" => { role = "...", user = "..." } + # 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 only (skip GUIDs) +# Lookup Azure AD users (UPNs) data "azuread_user" "users" { for_each = { - for assignment in local.flat_assignments : - assignment.user => assignment.user - if !can(regex("^[0-9a-fA-F-]{36}$", 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 principal by GUID +# Lookup service principals (GUIDs) data "azuread_service_principal" "sps" { for_each = { - for assignment in local.flat_assignments : - assignment.user => assignment.user - if can(regex("^[0-9a-fA-F-]{36}$", assignment.user)) + for _, assignment in local.flat_assignments : + assignment.principal => assignment.principal + if can(regex("^[0-9a-fA-F-]{36}$", assignment.principal)) } object_id = each.value @@ -47,8 +48,8 @@ resource "azurerm_role_assignment" "assignments" { for_each = local.flat_assignments principal_id = try( - data.azuread_user.users[each.value.user].object_id, - data.azuread_service_principal.sps[each.value.user].object_id + 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 diff --git a/src/role_assignments.tf b/src/role_assignments.tf index 6a947ec7..6a8c16eb 100644 --- a/src/role_assignments.tf +++ b/src/role_assignments.tf @@ -46,7 +46,7 @@ module "subscription_assignments" { try(var.subscription_assignments, []) ) > 0 ? 1 : 0 - subscription_assignments = var.subscription_assignments + subscription_assignments = var.subscription_assignments.built_in_roles global_settings = local.global_settings } From d0e47863b046a091794e35edc18febc7219109d6 Mon Sep 17 00:00:00 2001 From: Borislav Raynov Date: Tue, 25 Nov 2025 16:16:00 +0200 Subject: [PATCH 8/8] Remove comments --- .../role_assignments/subscription/_outputs.tf | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/modules/role_assignments/subscription/_outputs.tf b/src/modules/role_assignments/subscription/_outputs.tf index 753a1469..f6741b72 100644 --- a/src/modules/role_assignments/subscription/_outputs.tf +++ b/src/modules/role_assignments/subscription/_outputs.tf @@ -1,17 +1,3 @@ -# output "subscription_assignments" { -# value = { -# for key, assignment in azurerm_role_assignment.assignments : -# key => { -# principal_id = assignment.principal_id - -# user_principal_name = ( -# contains(keys(data.azuread_user.users), local.flat_assignments[key].user) -# ? data.azuread_user.users[local.flat_assignments[key].user].user_principal_name -# : null -# ) -# } -# } -# } output "subscription_assignments" { description = "Resolved subscription-level role assignments"