From c52e5f790dbdded0507ce785cf3f20479b4d9cf5 Mon Sep 17 00:00:00 2001 From: kewalaka Date: Sun, 5 May 2024 10:35:31 +1200 Subject: [PATCH 1/2] feat: support for azure AD groups #155 --- README.md | 69 ++++++++++++++++ main.aadgroup.tf | 13 +++ modules/aadgroup/README.md | 151 ++++++++++++++++++++++++++++++++++ modules/aadgroup/footer.md | 0 modules/aadgroup/header.md | 52 ++++++++++++ modules/aadgroup/locals.tf | 14 ++++ modules/aadgroup/main.tf | 53 ++++++++++++ modules/aadgroup/terraform.tf | 9 ++ modules/aadgroup/variables.tf | 60 ++++++++++++++ variables.aadgroup.tf | 59 +++++++++++++ 10 files changed, 480 insertions(+) create mode 100644 main.aadgroup.tf create mode 100644 modules/aadgroup/README.md create mode 100644 modules/aadgroup/footer.md create mode 100644 modules/aadgroup/header.md create mode 100644 modules/aadgroup/locals.tf create mode 100644 modules/aadgroup/main.tf create mode 100644 modules/aadgroup/terraform.tf create mode 100644 modules/aadgroup/variables.tf create mode 100644 variables.aadgroup.tf diff --git a/README.md b/README.md index 279e8455..eec1e9c9 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,12 @@ The following requirements are needed by this module: The following Modules are called: +### [aadgroup](#module\_aadgroup) + +Source: ./modules/aadgroup + +Version: + ### [budget](#module\_budget) Source: ./modules/budget @@ -213,6 +219,69 @@ Type: `string` The following input variables are optional (have default values): +### [aad\_groups](#input\_aad\_groups) + +Description: A map defining the configuration for an Entra ID (Azure Active Directory) group. + +- `name` - The display name of the group. + +**Optional Parameters:** + +- `administrative_unit_ids` - (optional) A list of object IDs of administrative units for group membership. +- `assignable_to_role` - (optional) Whether the group can be assigned to an Azure AD role (default: false). +- `description` - (optional) The description for the group (default: ""). +- `ignore_owner_and_member_changes` - (optional) If true, changes to ownership and membership will be ignored (default: false). +- `members` - (optional) A set of members (Users, Groups, or Service Principals). +- `owners` - (optional) A list of object IDs of owners (Users or Service Principals) (default: current user). +- `prevent_duplicate_names` - (optional) If true, throws an error on duplicate names (default: true). +- `add_deployment_user_as_owner` - (optional) If true, adds the current service principal the terraform deployment is running as to the owners, useful if further management by terraform is required (default: false). + +- `role_assignments` - (optional) A map defining role assignments for the group. + - `definition` - The name of the role to assign. + - `relative_scope` - The scope of the role assignment relative to the subscription + - `description` - (optional) Description for the role assignment. + - `skip_service_principal_aad_check` - (optional) If true, skips the Azure AD check for service principal (default: false). + - `condition` - (optional) The condition for the role assignment. + - `condition_version` - (optional) The condition version for the role assignment. + - `delegated_managed_identity_resource_id` - (optional) The resource ID of the delegated managed identity. + +Type: + +```hcl +map(object({ + name = string + + administrative_unit_ids = optional(list(string), null) + assignable_to_role = optional(bool, false) + description = optional(string, null) + ignore_owner_and_member_changes = optional(bool, false) + members = optional(map(list(string)), null) + owners = optional(map(list(string)), null) + prevent_duplicate_names = optional(bool, true) + add_deployment_user_as_owner = optional(bool, false) + role_assignments = optional(map(object({ + definition = string + relative_scope = string + description = optional(string, null) + skip_service_principal_aad_check = optional(bool, false) + condition = optional(string, null) + condition_version = optional(string, null) + delegated_managed_identity_resource_id = optional(string, null) + })), {}) + })) +``` + +Default: `{}` + +### [aadgroup\_enabled](#input\_aadgroup\_enabled) + +Description: Whether to create Entra ID (Azure AD) groups. +If enabled, supply the list of aadgroups in `var.aadgroups`. + +Type: `bool` + +Default: `false` + ### [budget\_enabled](#input\_budget\_enabled) Description: Whether to create budgets. diff --git a/main.aadgroup.tf b/main.aadgroup.tf new file mode 100644 index 00000000..29fe0f7b --- /dev/null +++ b/main.aadgroup.tf @@ -0,0 +1,13 @@ +module "aadgroup" { + source = "./modules/aadgroup" + count = var.aadgroup_enabled ? 1 : 0 + depends_on = [ + module.resourcegroup_networkwatcherrg, + module.resourcegroup, + module.subscription, + module.usermanagedidentity, + module.virtualnetwork, + ] + aad_groups = var.aad_groups + subscription_id = local.subscription_id +} diff --git a/modules/aadgroup/README.md b/modules/aadgroup/README.md new file mode 100644 index 00000000..4e335282 --- /dev/null +++ b/modules/aadgroup/README.md @@ -0,0 +1,151 @@ + +# Landing zone Entra ID (AAD) Group submodule + +## Overview + +Creates groups in Entra ID and role assignments for resources. + +## Notes + +See [README.md](https://github.com/Azure/terraform-azurerm-lz-vending#readme) in the parent module for more information. + +## Example + +```terraform +module "aadgroup" { + source = "Azure/lz-vending/azurerm/modules/aadgroup" + version = "" # change this to your desired version, https://www.terraform.io/language/expressions/version-constraints + + aad_groups = { + contributor_group = { + name = "my-ad-group-name" + + # optional parameters + description = "the description for my ad group" + members = { + object_ids = [ + "e64a9602-6a56-4d45-a4b0-7a7fe605f89d", + "8c537ad4-0289-41f5-84b7-3d1450c04643", + ] + } + owners = { + object_ids = ["1f32f09d-bae9-4f02-8905-1ae0a5d97d2f"] + } + + # optional role assignment + role_assignments = { + rg_contributor = { + definition = "Contributor" + relative_scope = "/resourceGroups/rg-some-resource-group" + } + } + + # optionally tell Terraform to ignore changes to owners & members + ignore_owner_and_member_changes = true + + # optionally add the deployment user to the owners to allow subsequent membership updates + add_deployment_user_as_owner = true + } + } + + subscription_id = "00000000-0000-0000-0000-000000000000" +} +``` + +## Documentation + + +## Requirements + +The following requirements are needed by this module: + +- [terraform](#requirement\_terraform) (>= 1.3.0) + +- [azuread](#requirement\_azuread) (~> 2.47) + +## Modules + +No modules. + + +## Required Inputs + +The following input variables are required: + +### [aad\_groups](#input\_aad\_groups) + +Description: A map defining the configuration for an Entra ID (Azure Active Directory) group. + +- `name` - The display name of the group. + +**Optional Parameters:** + +- `administrative_unit_ids` - (optional) A list of object IDs of administrative units for group membership. +- `assignable_to_role` - (optional) Whether the group can be assigned to an Azure AD role (default: false). +- `description` - (optional) The description for the group (default: ""). +- `ignore_owner_and_member_changes` - (optional) If true, changes to ownership and membership will be ignored (default: false). +- `members` - (optional) A set of members (Users, Groups, or Service Principals). +- `owners` - (optional) A list of object IDs of owners (Users or Service Principals) (default: current user). +- `prevent_duplicate_names` - (optional) If true, throws an error on duplicate names (default: true). +- `add_deployment_user_as_owner` - (optional) If true, adds the current service principal the terraform deployment is running as to the owners, useful if further management by terraform is required (default: false). + +- `role_assignments` - (optional) A map defining role assignments for the group. + - `definition` - The name of the role to assign. + - `relative_scope` - The scope of the role assignment relative to the subscription + - `description` - (optional) Description for the role assignment. + - `skip_service_principal_aad_check` - (optional) If true, skips the Azure AD check for service principal (default: false). + - `condition` - (optional) The condition for the role assignment. + - `condition_version` - (optional) The condition version for the role assignment. + - `delegated_managed_identity_resource_id` - (optional) The resource ID of the delegated managed identity. + +Type: + +```hcl +map(object({ + name = string + + administrative_unit_ids = optional(list(string), null) + assignable_to_role = optional(bool, false) + description = optional(string, null) + ignore_owner_and_member_changes = optional(bool, false) + members = optional(map(list(string)), null) + owners = optional(map(list(string)), null) + prevent_duplicate_names = optional(bool, true) + add_deployment_user_as_owner = optional(bool, false) + role_assignments = optional(map(object({ + definition = string + relative_scope = string + description = optional(string, null) + skip_service_principal_aad_check = optional(bool, false) + condition = optional(string, null) + condition_version = optional(string, null) + delegated_managed_identity_resource_id = optional(string, null) + })), {}) + })) +``` + +### [subscription\_id](#input\_subscription\_id) + +Description: The subscription ID of the subscriptions where group role assignments are applied. + +Type: `string` + +## Optional Inputs + +No optional inputs. + +## Resources + +The following resources are used by this module: + +- [azuread_group.ignore_owner_and_member_changes](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group) (resource) +- [azuread_group.this](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group) (resource) +- [azurerm_role_assignment.groups](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) (resource) +- [azurerm_client_config.current](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/client_config) (data source) + +## Outputs + +No outputs. + + + \ No newline at end of file diff --git a/modules/aadgroup/footer.md b/modules/aadgroup/footer.md new file mode 100644 index 00000000..e69de29b diff --git a/modules/aadgroup/header.md b/modules/aadgroup/header.md new file mode 100644 index 00000000..4dcc5ed5 --- /dev/null +++ b/modules/aadgroup/header.md @@ -0,0 +1,52 @@ +# Landing zone Entra ID (AAD) Group submodule + +## Overview + +Creates groups in Entra ID and role assignments for resources. + +## Notes + +See [README.md](https://github.com/Azure/terraform-azurerm-lz-vending#readme) in the parent module for more information. + +## Example + +```terraform +module "aadgroup" { + source = "Azure/lz-vending/azurerm/modules/aadgroup" + version = "" # change this to your desired version, https://www.terraform.io/language/expressions/version-constraints + + aad_groups = { + contributor_group = { + name = "my-ad-group-name" + + # optional parameters + description = "the description for my ad group" + members = { + object_ids = [ + "e64a9602-6a56-4d45-a4b0-7a7fe605f89d", + "8c537ad4-0289-41f5-84b7-3d1450c04643", + ] + } + owners = { + object_ids = ["1f32f09d-bae9-4f02-8905-1ae0a5d97d2f"] + } + + # optional role assignment + role_assignments = { + rg_contributor = { + definition = "Contributor" + relative_scope = "/resourceGroups/rg-some-resource-group" + } + } + + # optionally tell Terraform to ignore changes to owners & members + ignore_owner_and_member_changes = true + + # optionally add the deployment user to the owners to allow subsequent membership updates + add_deployment_user_as_owner = true + } + } + + subscription_id = "00000000-0000-0000-0000-000000000000" +} +``` diff --git a/modules/aadgroup/locals.tf b/modules/aadgroup/locals.tf new file mode 100644 index 00000000..36238972 --- /dev/null +++ b/modules/aadgroup/locals.tf @@ -0,0 +1,14 @@ +locals { + aad_groups_role_assignments = { for ra in flatten([ + for k_group, v_group in var.aad_groups : [ + for k_role, v_role in v_group.role_assignments : { + group_key = k_group + ra_key = k_role + role_assignment = v_role + ignore_changes = try(v_group.ignore_owner_and_member_changes, false) + } + ] + ]) : "${ra.group_key}-${ra.ra_key}" => ra } + + role_definition_resource_substring = "/providers/Microsoft.Authorization/roleDefinitions" +} diff --git a/modules/aadgroup/main.tf b/modules/aadgroup/main.tf new file mode 100644 index 00000000..493d34fd --- /dev/null +++ b/modules/aadgroup/main.tf @@ -0,0 +1,53 @@ +data "azurerm_client_config" "current" {} + +resource "azuread_group" "this" { + for_each = { for key, value in var.aad_groups : key => value if !value.ignore_owner_and_member_changes } + + display_name = each.value.name + + administrative_unit_ids = each.value.administrative_unit_ids + assignable_to_role = each.value.assignable_to_role + description = each.value.description + + security_enabled = true + members = each.value.members.object_ids + prevent_duplicate_names = each.value.prevent_duplicate_names + owners = try(each.value.add_deployment_user_as_owner, false) ? setunion(each.value.owners.object_ids, [data.azurerm_client_config.current.object_id]) : each.value.owners.object_ids + visibility = "Private" +} + +resource "azuread_group" "ignore_owner_and_member_changes" { + for_each = { for key, value in var.aad_groups : key => value if value.ignore_owner_and_member_changes } + + display_name = each.value.name + + administrative_unit_ids = each.value.administrative_unit_ids + assignable_to_role = each.value.assignable_to_role + description = each.value.description + + security_enabled = true + members = each.value.members.object_ids + prevent_duplicate_names = each.value.prevent_duplicate_names + owners = try(each.value.add_deployment_user_as_owner, false) ? setunion(each.value.owners.object_ids, [data.azurerm_client_config.current.object_id]) : each.value.owners.object_ids + visibility = "Private" + + lifecycle { + ignore_changes = [ + members, + owners + ] + } +} + +resource "azurerm_role_assignment" "groups" { + for_each = local.aad_groups_role_assignments + + principal_id = each.value.ignore_changes ? azuread_group.ignore_owner_and_member_changes[each.value.group_key].object_id : azuread_group.this[each.value.group_key].object_id + scope = "/subscriptions/${var.subscription_id}${each.value.role_assignment.relative_scope}" + condition = each.value.role_assignment.condition + condition_version = each.value.role_assignment.condition_version + delegated_managed_identity_resource_id = each.value.role_assignment.delegated_managed_identity_resource_id + role_definition_id = strcontains(lower(each.value.role_assignment.definition), lower(local.role_definition_resource_substring)) ? each.value.role_assignment.definition : null + role_definition_name = strcontains(lower(each.value.role_assignment.definition), lower(local.role_definition_resource_substring)) ? null : each.value.role_assignment.definition + skip_service_principal_aad_check = each.value.role_assignment.skip_service_principal_aad_check +} diff --git a/modules/aadgroup/terraform.tf b/modules/aadgroup/terraform.tf new file mode 100644 index 00000000..051fc5dd --- /dev/null +++ b/modules/aadgroup/terraform.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.3.0" + required_providers { + azuread = { + source = "hashicorp/azuread" + version = "~> 2.47" + } + } +} diff --git a/modules/aadgroup/variables.tf b/modules/aadgroup/variables.tf new file mode 100644 index 00000000..57ff70f6 --- /dev/null +++ b/modules/aadgroup/variables.tf @@ -0,0 +1,60 @@ +variable "aad_groups" { + type = map(object({ + name = string + + administrative_unit_ids = optional(list(string), null) + assignable_to_role = optional(bool, false) + description = optional(string, null) + ignore_owner_and_member_changes = optional(bool, false) + members = optional(map(list(string)), null) + owners = optional(map(list(string)), null) + prevent_duplicate_names = optional(bool, true) + add_deployment_user_as_owner = optional(bool, false) + role_assignments = optional(map(object({ + definition = string + relative_scope = string + description = optional(string, null) + skip_service_principal_aad_check = optional(bool, false) + condition = optional(string, null) + condition_version = optional(string, null) + delegated_managed_identity_resource_id = optional(string, null) + })), {}) + })) + nullable = false + description = < Date: Sun, 5 May 2024 14:56:51 +1200 Subject: [PATCH 2/2] chore: make docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eec1e9c9..69cd78a4 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ The following input variables are optional (have default values): ### [aad\_groups](#input\_aad\_groups) -Description: A map defining the configuration for an Entra ID (Azure Active Directory) group. +Description: A map defining the configuration for Entra ID (AAD) groups. - `name` - The display name of the group.