diff --git a/terraform/azure-devops/create-service-connection/access-control.tf b/terraform/azure-devops/create-service-connection/access-control.tf new file mode 100644 index 0000000..ec76970 --- /dev/null +++ b/terraform/azure-devops/create-service-connection/access-control.tf @@ -0,0 +1,35 @@ +module azure_role_assignments { + providers = { + azurerm = azurerm.target + } + source = "./modules/azure-access" + create_role_assignment = !var.azdo_creates_identity + identity_object_id = local.principal_id + resource_id = each.value.scope + role = each.value.role + + for_each = { for ra in local.azure_role_assignments : format("%s-%s", ra.scope, ra.role) => ra } +} + +data azuread_group entra_security_group { + display_name = each.value + for_each = toset(var.entra_security_group_names) + + lifecycle { + postcondition { + condition = self.security_enabled + error_message = "${self.display_name} is not a security enabled" + } + postcondition { + condition = !self.onpremises_sync_enabled || self.writeback_enabled + error_message = "${self.display_name} is a synced group that is not writeback enabled" + } + } +} + +resource azuread_group_member entra_security_group { + group_object_id = each.value.object_id + member_object_id = local.principal_id + + for_each = data.azuread_group.entra_security_group +} \ No newline at end of file diff --git a/terraform/azure-devops/create-service-connection/doc-gen/header.md b/terraform/azure-devops/create-service-connection/doc-gen/header.md index 5b75aba..d78283a 100644 --- a/terraform/azure-devops/create-service-connection/doc-gen/header.md +++ b/terraform/azure-devops/create-service-connection/doc-gen/header.md @@ -94,6 +94,26 @@ Pre-requisites: - A resource group to hold the Managed Identity has been pre-created - The user is an owner of the Azure scopes to create role assignments on +#### Managed Identity with FIC assigned to Entra ID security group + +This creates a Managed Identity with Federated Identity Credential and custom Azure RBAC (role-based access control) role assignments: + +```hcl +azdo_creates_identity = false +azdo_organization_url = "https://dev.azure.com/my-organization" +azdo_project_name = "my-project" +azure_role_assignments = [] +create_federation = true +create_managed_identity = true +entra_security_group_names = ["my-security-group"] +managed_identity_resource_group_id = "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/msi-rg" +``` + +Pre-requisites: + +- A resource group to hold the Managed Identity has been pre-created +- The user is an owner of the security enabled Entra ID group to add the Managed Identity to + #### App registration with FIC and ITSM metadata This creates an Entra ID app registration with IT service reference and notes fields populated as well as specifying co-owners: diff --git a/terraform/azure-devops/create-service-connection/main.tf b/terraform/azure-devops/create-service-connection/main.tf index 91639e2..bfa0186 100644 --- a/terraform/azure-devops/create-service-connection/main.tf +++ b/terraform/azure-devops/create-service-connection/main.tf @@ -1,7 +1,7 @@ data azurerm_client_config current {} data azurerm_subscription current {} data azurerm_subscription target { - subscription_id = split("/",tolist(local.azure_role_assignments)[0].scope)[2] + subscription_id = length(local.azure_role_assignments) > 0 ? split("/",tolist(local.azure_role_assignments)[0].scope)[2] : data.azurerm_subscription.current.subscription_id } # Random resource suffix, this will prevent name collisions when creating resources in parallel @@ -21,7 +21,7 @@ locals { azdo_project_url = "${local.azdo_organization_url}/${urlencode(var.azdo_project_name)}" # azdo_service_connection_name = "${replace(data.azurerm_subscription.target.display_name,"/ +/","-")}-${var.azdo_creates_identity ? "aut" : "man"}-${var.create_managed_identity ? "msi" : "sp"}-${var.create_federation ? "oidc" : "secret"}${terraform.workspace == "default" ? "" : format("-%s",terraform.workspace)}-${local.resource_suffix}" azdo_service_connection_name = "${replace(data.azurerm_subscription.target.display_name,"/ +/","-")}${terraform.workspace == "default" ? "" : format("-%s",terraform.workspace)}-${local.resource_suffix}" - azure_role_assignments = length(var.azure_role_assignments) > 0 ? var.azure_role_assignments : [ + azure_role_assignments = var.azure_role_assignments != null ? var.azure_role_assignments : [ { # Default role assignment role = "Contributor" @@ -91,19 +91,6 @@ module entra_app { count = var.create_managed_identity || var.azdo_creates_identity ? 0 : 1 } -module azure_role_assignments { - providers = { - azurerm = azurerm.target - } - source = "./modules/azure-access" - create_role_assignment = !var.azdo_creates_identity - identity_object_id = local.principal_id - resource_id = each.value.scope - role = each.value.role - - for_each = { for ra in local.azure_role_assignments : format("%s-%s", ra.scope, ra.role) => ra } -} - module service_connection { source = "./modules/service-connection" application_id = local.application_id @@ -115,4 +102,4 @@ module service_connection { service_connection_name = local.azdo_service_connection_name subscription_id = data.azurerm_subscription.target.subscription_id subscription_name = data.azurerm_subscription.target.display_name -} +} \ No newline at end of file diff --git a/terraform/azure-devops/create-service-connection/variables.tf b/terraform/azure-devops/create-service-connection/variables.tf index 1ef3b22..5d772b8 100644 --- a/terraform/azure-devops/create-service-connection/variables.tf +++ b/terraform/azure-devops/create-service-connection/variables.tf @@ -16,9 +16,9 @@ variable azdo_project_name { } variable azure_role_assignments { - default = [] + default = null description = "Role assignments to create for the service connection's identity. If this is empty, the Contributor role will be assigned on the azurerm provider subscription." - nullable = false + nullable = true type = set(object({scope=string, role=string})) } @@ -46,6 +46,12 @@ variable entra_app_owner_object_ids { type = list(string) } +variable entra_security_group_names { + default = null + description = "Names of the security groups to add the service connection identity to" + type = list(string) +} + variable entra_secret_expiration_days { description = "Secret expiration in days" default = 90