Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions modules/azuread-group/.terraform-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
formatter: "markdown"

version: ""

header-from: docs/header.md
footer-from: docs/footer.md

recursive:
enabled: false
path: modules
include-main: true

sections:
hide: []
show: []

content: ""

output:
file: "README.md"
mode: inject
template: |-
<!-- BEGIN_TF_DOCS -->
{{ .Content }}
<!-- END_TF_DOCS -->

output-values:
enabled: false
from: ""

sort:
enabled: true
by: name

settings:
anchor: true
color: true
default: true
description: false
escape: true
hide-empty: false
html: true
indent: 2
lockfile: true
read-comments: true
required: true
sensitive: true
type: true
102 changes: 78 additions & 24 deletions modules/azuread-group/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
## Requirements
<!-- BEGIN_TF_DOCS -->
# Azure AD Group Module

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.7.0 |
| <a name="requirement_azuread"></a> [azuread](#requirement\_azuread) | ~> 2.52.0 |
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | = 4.16.0 |
This Terraform module creates and manages Azure Active Directory (AD) groups, including role assignments, PIM (Privileged Identity Management), owners, and members. It supports configuration via YAML for easier management and reproducibility.

Comment on lines 1 to 5
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

terraform-docs content is being appended at the end of the README instead of replacing the existing generated docs at the top, leaving two separate documentation blocks in the same file. To align with other modules, move the <!-- BEGIN_TF_DOCS --> marker to the beginning of the README (and <!-- END_TF_DOCS --> to the end) so the whole README is managed by terraform-docs, and delete the older duplicated content outside the markers.

Copilot uses AI. Check for mistakes.
### Provisioner actor and permissions

The provisioner actor must be a Service Principal due to a bug in the provider. For more details, check this [issue](https://github.com/hashicorp/terraform-provider-azuread/issues/1386).

To make it work, you need to grant permissions to the Service Principal using a PowerShell console. You can open a terminal in the Azure console panel.
> The provisioner actor must be a Service Principal due to a bug in the provider. See [issue #1386](https://github.com/hashicorp/terraform-provider-azuread/issues/1386).

![image](https://github.com/prefapp/tfm/assets/91343444/5096b774-1cc9-4ab2-88c1-0d246d916955)

To execute the following scripts, you should act as Global Administrator.

Execute the following scripts in the powershell terminal.
To make it work, you need to grant permissions to the Service Principal using a PowerShell console as a Global Administrator. Example scripts:

**1. PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup**
```powershell
Expand Down Expand Up @@ -80,26 +71,80 @@ $GraphServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$GraphAp
$AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"}; New-AzureAdServiceAppRoleAssignment -ObjectId $MSI -PrincipalId $MSI -ResourceId $GraphServicePrincipal.ObjectId -Id $AppRole.Id
```

## Features
- Create Azure AD groups with custom name and description
- Assign directory and subscription roles
- Manage group owners and members (users, service principals)
- Enable and configure PIM for groups
- YAML-driven configuration for scalable examples

## Minimal usage example

**values.yaml**
```yaml
name: example-group-1
description: Minimal test group
members:
- type: user
email: user-2@example.com
directory_roles: []
subscription_roles: []
```

**main.tf**
```hcl
locals {
values = yamldecode(file("./values.yaml"))
}

module "azuread-group" {
source = "../.."
name = local.values.name
description = local.values.description
members = local.values.members
directory_roles = local.values.directory_roles
subscription_roles = local.values.subscription_roles
}
```

> For a more complete example configuration, see the `_examples/with_yaml_file` folder in this repository. Ensure that provider versions in the example align with the Requirements section above.
## Known issues
- Removing a `azuread_privileged_access_group_eligibility_schedule` resource may crash the provider ([issue #1399](https://github.com/hashicorp/terraform-provider-azuread/issues/1399)).
- Updating a `azuread_privileged_access_group_eligibility_schedule` may show a wrong log error; sometimes you must remove and recreate the resource ([issue #1412](https://github.com/hashicorp/terraform-provider-azuread/issues/1412)).

## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.7.0 |
| <a name="requirement_azuread"></a> [azuread](#requirement\_azuread) | ~> 2.52.0 |
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | = 4.16.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_azuread"></a> [azuread](#provider\_azuread) | ~> 2.52.0 |
| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | = 4.16.0 |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [azuread_directory_role_assignment.this](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/directory_role_assignment) | resource |
| [azuread_group.this](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group) | resource |
| [azuread_group_member.this](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group_member) | resource |
| [azuread_group_role_management_policy.members](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group_role_management_policy) | resource |
| [azuread_group_role_management_policy.owners](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group_role_management_policy) | resource |
| [azuread_privileged_access_group_assignment_schedule.members](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/privileged_access_group_assignment_schedule) | resource |
| [azuread_privileged_access_group_assignment_schedule.owners](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/privileged_access_group_assignment_schedule) | resource |
| [azuread_privileged_access_group_eligibility_schedule.members](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/privileged_access_group_eligibility_schedule) | resource |
| [azuread_privileged_access_group_eligibility_schedule.owners](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/privileged_access_group_eligibility_schedule) | resource |
| [azurerm_role_assignment.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource |
| [azurerm_role_assignment.this](https://registry.terraform.io/providers/hashicorp/azurerm/4.16.0/docs/resources/role_assignment) | resource |
| [azuread_directory_roles.current](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/directory_roles) | data source |
| [azuread_groups.members_from_display_names](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/groups) | data source |
| [azuread_groups.members_from_object_ids](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/groups) | data source |
Expand All @@ -119,26 +164,35 @@ $AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $Permiss
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_assignable_to_role"></a> [assignable\_to\_role](#input\_assignable\_to\_role) | Indicates if the group is assignable to a role | `bool` | `true` | no |
| <a name="input_enable_pim"></a> [enable\_pim](#input\_enable\_pim) | Enable PIM for groups | `bool` | `false` | no |
| <a name="input_default_pim_duration"></a> [default\_pim\_duration](#input\_default\_pim\_duration) | The default duration for PIM role assignments | `string` | `"12"` | no |
| <a name="input_description"></a> [description](#input\_description) | The description of the Azure AD group | `string` | n/a | yes |
| <a name="input_directory_roles"></a> [directory\_roles](#input\_directory\_roles) | The list of directory roles to assign to the group | <pre>list(object({<br> <br> role_name = string<br> <br> }))</pre> | n/a | yes |
| <a name="input_directory_roles"></a> [directory\_roles](#input\_directory\_roles) | The list of directory roles to assign to the group | <pre>list(object({<br/> role_name = string<br/> }))</pre> | n/a | yes |
| <a name="input_enable_pim"></a> [enable\_pim](#input\_enable\_pim) | Enable Privileged Identity Management (PIM) for the group | `bool` | `false` | no |
| <a name="input_expiration_required"></a> [expiration\_required](#input\_expiration\_required) | Indicates if the expiration is required for the PIM eligible role assignments | `bool` | `false` | no |
| <a name="input_members"></a> [members](#input\_members) | The list of Azure AD users, groups or service principals to assign to the group | <pre>list(object({<br> <br> type = string<br> <br> email = optional(string)<br><br> display_name = optional(string)<br> <br> object_id = optional(string)<br><br> pim = optional(object({<br> <br> type = optional(string)<br> <br> expiration_hours = optional(string)<br><br> permanent_assignment = optional(bool)<br> }),<br> {<br> type = "disabled"<br><br> permanent_assignment = false<br> })<br> }))</pre> | n/a | yes |
| <a name="input_members"></a> [members](#input\_members) | The list of Azure AD users, groups or service principals to assign to the group | <pre>list(object({<br/> type = string<br/> email = optional(string)<br/> display_name = optional(string)<br/> object_id = optional(string)<br/> pim = optional(object({<br/> type = optional(string)<br/> expiration_hours = optional(string)<br/> permanent_assignment = optional(bool)<br/> }),<br/> {<br/> type = "disabled"<br/> permanent_assignment = false<br/> })<br/> }))</pre> | `[]` | no |
| <a name="input_name"></a> [name](#input\_name) | The name of the Azure AD group | `string` | n/a | yes |
| <a name="input_owners"></a> [owners](#input\_owners) | The list of Azure AD users or service principal owners of the group | <pre>list(object({<br> <br> type = string<br> <br> email = optional(string)<br><br> display_name = optional(string)<br><br> object_id = optional(string)<br><br> pim = optional(object({<br><br> type = optional(string)<br><br> expiration_hours = optional(string)<br><br> permanent_assignment = optional(bool)<br><br> }), <br> {<br> expiration_hours = null<br><br> type = "disabled"<br> <br> permanent_assignment = false<br><br> })<br> <br> }))</pre> | `[]` | no |
| <a name="input_owners"></a> [owners](#input\_owners) | The list of Azure AD users or service principal owners of the group | <pre>list(object({<br/> type = string<br/> email = optional(string)<br/> display_name = optional(string)<br/> object_id = optional(string)<br/> pim = optional(object({<br/> type = optional(string)<br/> expiration_hours = optional(string)<br/> permanent_assignment = optional(bool)<br/> }),<br/> {<br/> expiration_hours = null<br/> type = "disabled"<br/> permanent_assignment = false<br/> })<br/> }))</pre> | `[]` | no |
| <a name="input_pim_maximum_duration_hours"></a> [pim\_maximum\_duration\_hours](#input\_pim\_maximum\_duration\_hours) | The maximum duration for PIM role assignments | `string` | `"8"` | no |
| <a name="input_pim_require_justification"></a> [pim\_require\_justification](#input\_pim\_require\_justification) | Indicates if the justification is required for the eligible PIM role assignments | `bool` | `true` | no |
| <a name="input_subscription"></a> [subscription](#input\_subscription) | The subscription id | `string` | `null` | no |
| <a name="input_subscription_roles"></a> [subscription\_roles](#input\_subscription\_roles) | The list of built-in roles to assign to the group | <pre>list(object({<br> <br> role_name = string<br> <br> resources_scopes = list(string)<br> <br> }))</pre> | n/a | yes |
| <a name="input_subscription_roles"></a> [subscription\_roles](#input\_subscription\_roles) | The list of built-in roles to assign to the group | <pre>list(object({<br/> role_name = string<br/> resources_scopes = list(string)<br/> }))</pre> | n/a | yes |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_group_id"></a> [group\_id](#output\_group\_id) | group id |
| <a name="output_group_id"></a> [group\_id](#output\_group\_id) | n/a |

## Examples

### Known issues
For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/azuread-group/_examples):

- [basic](https://github.com/prefapp/tfm/tree/main/modules/azuread-group/_examples) - Example showing group creation, members and PIM configuration.
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README Examples section lists a basic example but links to the _examples root and the module only contains _examples/with_yaml_file. This link should be updated (likely via docs/footer.md) so the README points to real example directories.

Suggested change
- [basic](https://github.com/prefapp/tfm/tree/main/modules/azuread-group/_examples) - Example showing group creation, members and PIM configuration.
- [basic](https://github.com/prefapp/tfm/tree/main/modules/azuread-group/_examples/with_yaml_file) - Example showing group creation, members and PIM configuration.

Copilot uses AI. Check for mistakes.

## Resources
- [Terraform AzureAD Provider: group](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group)
- [Terraform AzureAD Provider: privileged access group](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/privileged_access_group_assignment_schedule)

1. Sometimes, if you try to remove a `azuread_privileged_access_group_eligibility_schedule` resource, the provider crashes, we are waiting for a fix. Check the [issue](https://github.com/hashicorp/terraform-provider-azuread/issues/1399).
2. If you want to update a `azuread_privileged_access_group_eligibility_schedule`, the provider shows a wrong log error. You should remove from terraform the resource and then recreate it. But sometimes has conflicts with the previous point. Check the [issue](https://github.com/hashicorp/terraform-provider-azuread/issues/1412).
## Support
For issues, questions, or contributions related to this module, please visit the [repository's issue tracker](https://github.com/prefapp/tfm/issues).
<!-- END_TF_DOCS -->
12 changes: 12 additions & 0 deletions modules/azuread-group/docs/footer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Examples

For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/azuread-group/_examples):

- [basic](https://github.com/prefapp/tfm/tree/main/modules/azuread-group/_examples) - Example showing group creation, members and PIM configuration.
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Examples section lists a basic example, but the link points to the _examples root and there is no basic example directory in this module (only _examples/with_yaml_file). Update the link text and URL to match the actual example folder(s).

Suggested change
- [basic](https://github.com/prefapp/tfm/tree/main/modules/azuread-group/_examples) - Example showing group creation, members and PIM configuration.
- [with_yaml_file](https://github.com/prefapp/tfm/tree/main/modules/azuread-group/_examples/with_yaml_file) - Example showing group creation, members and PIM configuration.

Copilot uses AI. Check for mistakes.

## Resources
- [Terraform AzureAD Provider: group](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group)
- [Terraform AzureAD Provider: privileged access group](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/privileged_access_group_assignment_schedule)

## Support
For issues, questions, or contributions related to this module, please visit the [repository's issue tracker](https://github.com/prefapp/tfm/issues).
113 changes: 113 additions & 0 deletions modules/azuread-group/docs/header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Azure AD Group Module

This Terraform module creates and manages Azure Active Directory (AD) groups, including role assignments, PIM (Privileged Identity Management), owners, and members. It supports configuration via YAML for easier management and reproducibility.


### Provisioner actor and permissions

> The provisioner actor must be a Service Principal due to a bug in the provider. See [issue #1386](https://github.com/hashicorp/terraform-provider-azuread/issues/1386).

To make it work, you need to grant permissions to the Service Principal using a PowerShell console as a Global Administrator. Example scripts:

**1. PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup**
```powershell
$TenantID="<your-tenant-id>"

$GraphAppId = "00000003-0000-0000-c000-000000000000"

$MSI="<your-service-principal-object-id>"

$PermissionName = "PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup"

# Install the module

Install-Module AzureAD

Connect-AzureAD -TenantId $TenantID

$GraphServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$GraphAppId'"

$AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"}; New-AzureAdServiceAppRoleAssignment -ObjectId $MSI -PrincipalId $MSI -ResourceId $GraphServicePrincipal.ObjectId -Id $AppRole.Id
```
**2. RoleManagementPolicy.ReadWrite.AzureADGroup**
```powershell
$TenantID="<your-tenant-id>"

$GraphAppId = "00000003-0000-0000-c000-000000000000"

$MSI="<your-service-principal-object-id>"

$PermissionName = "RoleManagementPolicy.ReadWrite.AzureADGroup"

# Install the module

Install-Module AzureAD

Connect-AzureAD -TenantId $TenantID

$GraphServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$GraphAppId'"

$AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"}; New-AzureAdServiceAppRoleAssignment -ObjectId $MSI -PrincipalId $MSI -ResourceId $GraphServicePrincipal.ObjectId -Id $AppRole.Id
```

**3. PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup**
```powershell
$TenantID="<your-tenant-id>"

$GraphAppId = "00000003-0000-0000-c000-000000000000"

$MSI="<your-service-principal-object-id>"

$PermissionName = "PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup"

# Install the module

Install-Module AzureAD

Connect-AzureAD -TenantId $TenantID

$GraphServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$GraphAppId'"

$AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"}; New-AzureAdServiceAppRoleAssignment -ObjectId $MSI -PrincipalId $MSI -ResourceId $GraphServicePrincipal.ObjectId -Id $AppRole.Id
```

## Features
- Create Azure AD groups with custom name and description
- Assign directory and subscription roles
- Manage group owners and members (users, service principals)
- Enable and configure PIM for groups
- YAML-driven configuration for scalable examples

## Minimal usage example

**values.yaml**
```yaml
name: example-group-1
description: Minimal test group
members:
- type: user
email: user-2@example.com
directory_roles: []
subscription_roles: []
```

**main.tf**
```hcl
locals {
values = yamldecode(file("./values.yaml"))
}

module "azuread-group" {
source = "../.."
name = local.values.name
description = local.values.description
members = local.values.members
directory_roles = local.values.directory_roles
subscription_roles = local.values.subscription_roles
}
Comment on lines 83 to 107
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Minimal usage example” is missing required module inputs. In variables.tf, both directory_roles and subscription_roles have no defaults, so omitting them will cause Terraform to fail. Update the YAML and module invocation to provide these (e.g., empty lists for a true minimal example, or a small realistic role sample).

Copilot uses AI. Check for mistakes.
```

> For a more complete example configuration, see the `_examples/with_yaml_file` folder in this repository. Ensure that provider versions in the example align with the Requirements section above.
## Known issues
- Removing a `azuread_privileged_access_group_eligibility_schedule` resource may crash the provider ([issue #1399](https://github.com/hashicorp/terraform-provider-azuread/issues/1399)).
- Updating a `azuread_privileged_access_group_eligibility_schedule` may show a wrong log error; sometimes you must remove and recreate the resource ([issue #1412](https://github.com/hashicorp/terraform-provider-azuread/issues/1412)).