From f645d8c6da2c1c3d6c40384f3e9fe83a335fa02c Mon Sep 17 00:00:00 2001 From: Josiel Souza Date: Tue, 28 Apr 2026 15:08:34 +0100 Subject: [PATCH] DTOSS-12822: add managed identity for Azure Relay send access - Add azurerm_relay_namespace data source to resolve namespace ID for RBAC scope - Create relay-send managed identity and assign Azure Relay Sender role at namespace level - Assign managed identity to webapp and expose AZURE_RELAY_CLIENT_ID env var - Update core.bicep RBAC condition to permit assignment of Azure Relay Sender role --- infrastructure/modules/container-apps/data.tf | 1 + infrastructure/modules/container-apps/main.tf | 8 ++++++-- infrastructure/modules/container-apps/relay.tf | 17 +++++++++++++++++ .../modules/container-apps/variables.tf | 6 ++++++ infrastructure/modules/infra/output.tf | 4 ++++ infrastructure/terraform/main.tf | 1 + .../terraform/resource_group_init/core.bicep | 5 +++-- 7 files changed, 38 insertions(+), 4 deletions(-) diff --git a/infrastructure/modules/container-apps/data.tf b/infrastructure/modules/container-apps/data.tf index 41e8531cd..5e745ef57 100644 --- a/infrastructure/modules/container-apps/data.tf +++ b/infrastructure/modules/container-apps/data.tf @@ -26,3 +26,4 @@ data "azurerm_private_dns_zone" "storage-account-queue" { name = "privatelink.queue.core.windows.net" resource_group_name = "rg-hub-${var.hub}-uks-private-dns-zones" } + diff --git a/infrastructure/modules/container-apps/main.tf b/infrastructure/modules/container-apps/main.tf index 457d136ee..9313d9f17 100644 --- a/infrastructure/modules/container-apps/main.tf +++ b/infrastructure/modules/container-apps/main.tf @@ -34,13 +34,17 @@ module "webapp" { enable_entra_id_authentication = var.enable_entra_id_authentication app_key_vault_id = var.app_key_vault_id docker_image = var.docker_image - user_assigned_identity_ids = var.deploy_database_as_container ? [] : [module.db_connect_identity[0].id] + user_assigned_identity_ids = flatten([ + var.deploy_database_as_container ? [] : [module.db_connect_identity[0].id], + var.relay_namespace_name != null ? [module.relay_send_identity[0].id] : [] + ]) environment_variables = merge( local.common_env, { ALLOWED_HOSTS = "${var.app_short_name}-web-${var.environment}.${var.default_domain},localhost,127.0.0.1" }, - var.deploy_database_as_container ? local.container_db_env : local.azure_db_env + var.deploy_database_as_container ? local.container_db_env : local.azure_db_env, + var.relay_namespace_name != null ? { AZURE_RELAY_CLIENT_ID = module.relay_send_identity[0].client_id } : {} ) secret_variables = merge( { APPLICATIONINSIGHTS_CONNECTION_STRING = var.app_insights_connection_string }, diff --git a/infrastructure/modules/container-apps/relay.tf b/infrastructure/modules/container-apps/relay.tf index 2179991cf..267c92a2b 100644 --- a/infrastructure/modules/container-apps/relay.tf +++ b/infrastructure/modules/container-apps/relay.tf @@ -13,3 +13,20 @@ module "relay_hybrid_connection" { } } } + +module "relay_send_identity" { + count = var.relay_namespace_name != null ? 1 : 0 + source = "../dtos-devops-templates/infrastructure/modules/managed-identity" + resource_group_name = azurerm_resource_group.main.name + location = var.region + uai_name = "mi-${var.app_short_name}-${var.environment}-relay-send" +} + +module "relay_send_role_assignment" { + count = var.relay_namespace_name != null ? 1 : 0 + source = "../dtos-devops-templates/infrastructure/modules/rbac-assignment" + principal_id = module.relay_send_identity[0].principal_id + role_definition_name = "Azure Relay Sender" + scope = var.relay_namespace_id + depends_on = [module.relay_send_identity] +} diff --git a/infrastructure/modules/container-apps/variables.tf b/infrastructure/modules/container-apps/variables.tf index 0b22a34a5..cb0d2781f 100644 --- a/infrastructure/modules/container-apps/variables.tf +++ b/infrastructure/modules/container-apps/variables.tf @@ -206,6 +206,12 @@ variable "relay_namespace_name" { default = null } +variable "relay_namespace_id" { + description = "The ID of the Azure Relay namespace. Used for RBAC scope." + type = string + default = null +} + locals { resource_group_name = "rg-${var.app_short_name}-${var.environment}-container-app-uks" diff --git a/infrastructure/modules/infra/output.tf b/infrastructure/modules/infra/output.tf index 4232cb93b..7ac23cfea 100644 --- a/infrastructure/modules/infra/output.tf +++ b/infrastructure/modules/infra/output.tf @@ -42,6 +42,10 @@ output "relay_namespace_name" { value = var.enable_relay ? module.relay_namespace[0].name : null } +output "relay_namespace_id" { + value = var.enable_relay ? module.relay_namespace[0].id : null +} + output "servicebus_namespace_name" { value = var.enable_service_bus ? module.servicebus_namespace[0].namespace_name : null } diff --git a/infrastructure/terraform/main.tf b/infrastructure/terraform/main.tf index 71778eb68..96c714bf9 100644 --- a/infrastructure/terraform/main.tf +++ b/infrastructure/terraform/main.tf @@ -82,4 +82,5 @@ module "container-apps" { container_memory = var.container_memory min_replicas = var.min_replicas relay_namespace_name = var.deploy_infra ? module.infra[0].relay_namespace_name : null + relay_namespace_id = var.deploy_infra ? module.infra[0].relay_namespace_id : null } diff --git a/infrastructure/terraform/resource_group_init/core.bicep b/infrastructure/terraform/resource_group_init/core.bicep index 5a03b7b1d..61927979e 100644 --- a/infrastructure/terraform/resource_group_init/core.bicep +++ b/infrastructure/terraform/resource_group_init/core.bicep @@ -17,6 +17,7 @@ var roleID = { rbacAdmin: 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' storageBlobDataContributor: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' storageQueueDataContributor: '974c5e8b-45b9-4653-ba55-5f855dd0fb88' + azureRelaySender: '26baccc8-eea7-41f1-98f4-1762cc7f685d' } // Define role assignments for managed identity @@ -36,7 +37,7 @@ var miRoleAssignments = [ roleId: roleID.rbacAdmin description: 'Role Based Access Control Administrator access to subscription. Can assign Key Vault Secrets User, Storage Blob Data Contributor, and Storage Queue Data Contributor roles.' // Optional properties - only rbacAdmin has a condition - condition: '((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/write\'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsOfficer}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}})) AND ((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/delete\'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsOfficer}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}}))' + condition: '((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/write\'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsUser}, ${roleID.kvSecretsOfficer}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}, ${roleID.azureRelaySender}})) AND ((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/delete\'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsUser}, ${roleID.kvSecretsOfficer}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}, ${roleID.azureRelaySender}}))' conditionVersion: '2.0' } ] @@ -58,7 +59,7 @@ var groupRoleAssignments = [ roleId: roleID.rbacAdmin description: 'Role Based Access Control Administrator access to subscription. Can assign Key Vault Secrets Officer, Storage Blob Data Contributor, and Storage Queue Data Contributor roles.' // Optional properties - only rbacAdmin has a condition - condition: '((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/write\'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsOfficer}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}})) AND ((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/delete\'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsOfficer}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}}))' + condition: '((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/write\'})) OR (@Request[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsUser}, ${roleID.kvSecretsOfficer}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}, ${roleID.azureRelaySender}})) AND ((!(ActionMatches{\'Microsoft.Authorization/roleAssignments/delete\'})) OR (@Resource[Microsoft.Authorization/roleAssignments:RoleDefinitionId] ForAnyOfAnyValues:GuidEquals {${roleID.kvSecretsUser}, ${roleID.kvSecretsOfficer}, ${roleID.storageBlobDataContributor}, ${roleID.storageQueueDataContributor}, ${roleID.azureRelaySender}}))' conditionVersion: '2.0' } ]