Skip to content

Commit 20d9691

Browse files
committed
chore: custom role for administrative units
- also modify docs and add entries to changelog
1 parent 935adec commit 20d9691

File tree

7 files changed

+138
-64
lines changed

7 files changed

+138
-64
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [v0.13.0]
11+
12+
### Added
13+
14+
- Harden permissions for administrative units
15+
- Support for dynamic membership rules in administrative units
16+
1017
## [v0.12.0]
1118

1219
### Added
@@ -98,7 +105,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
98105

99106
- Initial Release
100107

101-
[unreleased]: https://github.com/meshcloud/terraform-azure-meshplatform/compare/v0.12.0...HEAD
108+
[unreleased]: https://github.com/meshcloud/terraform-azure-meshplatform/compare/v0.13.0...HEAD
109+
[v0.13.0]: https://github.com/meshcloud/terraform-azure-meshplatform/releases/tag/v0.13.0
102110
[v0.12.0]: https://github.com/meshcloud/terraform-azure-meshplatform/releases/tag/v0.12.0
103111
[v0.11.0]: https://github.com/meshcloud/terraform-azure-meshplatform/releases/tag/v0.11.0
104112
[v0.1.0]: https://github.com/meshcloud/terraform-azure-meshplatform/releases/tag/v0.1.0

README.md

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,29 @@ module "meshplatform" {
107107
}
108108
```
109109

110+
### Using Pre-provisioned Subscriptions
111+
112+
meshStack will need to be able to read subscriptions at the source location
113+
(typically the root of your management group hierarchy) and then have permission to rename them.
114+
Please include the following `additional_permission` when configuring this terraform module.
115+
116+
```hcl
117+
additional_permissions = ["Microsoft.Subscription/rename/action"]
118+
```
119+
120+
### Using Azure Administrative Units
121+
122+
To use Azure Administrative Units with this module, you can set the `administrative_unit_name` and `administrative_unit_membership_rule` variables.
123+
124+
```hcl
125+
module "meshplatform" {
126+
source = "meshcloud/meshplatform/azure"
127+
# required inputs
128+
administrative_unit_name = "my-administrative-unit"
129+
administrative_unit_membership_rule = "(user.accountEnabled -eq true)" # Include all active users
130+
}
131+
```
132+
110133
## Workload Identity Federation for Multiple Environments
111134

112135
When using multiple MCA service principals with Workload Identity Federation (WIF), you can configure per-service-principal subjects to support different Kubernetes namespaces or environments.
@@ -142,15 +165,6 @@ workload_identity_federation = {
142165

143166
This approach allows each service principal to have its own custom subject when configuring WIF.
144167

145-
### Using Pre-provisioned Subscriptions
146-
147-
meshStack will need to be able to read subscriptions at the source location
148-
(typically the root of your management group hierarchy) and then have permission to rename them.
149-
Please include the following `additional_permission` when configuring this terraform module.
150-
151-
```hcl
152-
additional_permissions = ["Microsoft.Subscription/rename/action"]
153-
```
154168

155169
### Enabling Azure Functions for Landing Zones
156170

@@ -234,6 +248,7 @@ Before opening a Pull Request, please do the following:
234248
|------|-------------|------|---------|:--------:|
235249
| <a name="input_additional_permissions"></a> [additional\_permissions](#input\_additional\_permissions) | Additional Subscription-Level Permissions the Service Principal needs. | `list(string)` | `[]` | no |
236250
| <a name="input_additional_required_resource_accesses"></a> [additional\_required\_resource\_accesses](#input\_additional\_required\_resource\_accesses) | Additional AAD-Level Resource Accesses the replicator Service Principal needs. | `list(object({ resource_app_id = string, resource_accesses = list(object({ id = string, type = string })) }))` | `[]` | no |
251+
| <a name="input_administrative_unit_membership_rule"></a> [administrative\_unit\_membership\_rule](#input\_administrative\_unit\_membership\_rule) | Dynamic membership rule for the Administrative Unit. Required when administrative\_unit\_name is set.<br><br>Suggested default: "(user.accountEnabled -eq true)"<br>NOTE: This rule will include ALL active users in your tenant. Consider more restrictive rules for production use.<br><br>Examples for more restrictive rules:<br>- "(user.companyName -eq \"MyCompany\") and (user.accountEnabled -eq true)" - Active users from specific company<br>- "(user.userType -eq \"Member\") and (user.accountEnabled -eq true)" - Active member users only<br><br>For more information on membership rules, see:<br>https://learn.microsoft.com/en-us/entra/identity/users/groups-dynamic-membership | `string` | `null` | no |
237252
| <a name="input_administrative_unit_name"></a> [administrative\_unit\_name](#input\_administrative\_unit\_name) | Display name of the adminstration-unit name where the user groups are managed. | `string` | `null` | no |
238253
| <a name="input_application_owners"></a> [application\_owners](#input\_application\_owners) | List of user principals that should be added as owners to the created service principals. | `list(string)` | `[]` | no |
239254
| <a name="input_can_cancel_subscriptions_in_scopes"></a> [can\_cancel\_subscriptions\_in\_scopes](#input\_can\_cancel\_subscriptions\_in\_scopes) | The scopes to which Service Principal cancel subscription permission is assigned to. List of management group id of form `/providers/Microsoft.Management/managementGroups/<mgmtGroupId>/`. | `list(string)` | `[]` | no |

main.tf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ module "replicator_service_principal" {
6262
can_cancel_subscriptions_in_scopes = var.can_cancel_subscriptions_in_scopes
6363
can_delete_rgs_in_scopes = var.can_delete_rgs_in_scopes
6464

65-
administrative_unit_name = var.administrative_unit_name
65+
administrative_unit_name = var.administrative_unit_name
66+
administrative_unit_membership_rule = var.administrative_unit_membership_rule
67+
6668
additional_required_resource_accesses = var.additional_required_resource_accesses
6769
additional_permissions = var.additional_permissions
6870

modules/meshcloud-replicator-service-principal/README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
| Name | Version |
55
|------|---------|
66
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | > 1.0 |
7-
| <a name="requirement_azapi"></a> [azapi](#requirement\_azapi) | >=1.13.1 |
87
| <a name="requirement_azuread"></a> [azuread](#requirement\_azuread) | >=3.0.2 |
98
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | >=4.11.0 |
109

@@ -17,17 +16,15 @@ No modules.
1716
| Name | Type |
1817
|------|------|
1918
| [azuread_administrative_unit.meshcloud_replicator_au](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/administrative_unit) | resource |
20-
| [azuread_administrative_unit_role_member.groups_admin_assignment](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/administrative_unit_role_member) | resource |
21-
| [azuread_administrative_unit_role_member.user_admin_assignment](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/administrative_unit_role_member) | resource |
2219
| [azuread_app_role_assignment.meshcloud_replicator-administrativeunit](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/app_role_assignment) | resource |
2320
| [azuread_app_role_assignment.meshcloud_replicator-directory](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/app_role_assignment) | resource |
2421
| [azuread_app_role_assignment.meshcloud_replicator-group](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/app_role_assignment) | resource |
2522
| [azuread_app_role_assignment.meshcloud_replicator-user](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/app_role_assignment) | resource |
2623
| [azuread_application.meshcloud_replicator](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/application) | resource |
2724
| [azuread_application_federated_identity_credential.meshcloud_replicator](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/application_federated_identity_credential) | resource |
2825
| [azuread_application_password.application_pw](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/application_password) | resource |
29-
| [azuread_directory_role.groups_administrator](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/directory_role) | resource |
30-
| [azuread_directory_role.user_administrator](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/directory_role) | resource |
26+
| [azuread_custom_directory_role.meshcloud_replicator_au_role](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/custom_directory_role) | resource |
27+
| [azuread_directory_role_assignment.meshcloud_replicator_au_assignment](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/directory_role_assignment) | resource |
3128
| [azuread_service_principal.meshcloud_replicator](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/service_principal) | resource |
3229
| [azurerm_management_group_policy_assignment.privilege-escalation-prevention](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/management_group_policy_assignment) | resource |
3330
| [azurerm_policy_definition.privilege_escalation_prevention](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/policy_definition) | resource |
@@ -38,6 +35,7 @@ No modules.
3835
| [azurerm_role_definition.meshcloud_replicator_rg_deleter](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_definition) | resource |
3936
| [azurerm_role_definition.meshcloud_replicator_subscription_canceler](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_definition) | resource |
4037
| [terraform_data.allowed_assignments](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource |
38+
| [terraform_data.patch_admin_unit](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource |
4139
| [time_rotating.replicator_secret_rotation](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/rotating) | resource |
4240
| [azuread_application_published_app_ids.well_known](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/application_published_app_ids) | data source |
4341
| [azuread_application_template.enterprise_app](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/application_template) | data source |
@@ -49,6 +47,7 @@ No modules.
4947
|------|-------------|------|---------|:--------:|
5048
| <a name="input_additional_permissions"></a> [additional\_permissions](#input\_additional\_permissions) | Additional Subscription-Level Permissions the Service Principal needs. | `list(string)` | `[]` | no |
5149
| <a name="input_additional_required_resource_accesses"></a> [additional\_required\_resource\_accesses](#input\_additional\_required\_resource\_accesses) | Additional AAD-Level Resource Accesses the Service Principal needs. | `list(object({ resource_app_id = string, resource_accesses = list(object({ id = string, type = string })) }))` | `[]` | no |
50+
| <a name="input_administrative_unit_membership_rule"></a> [administrative\_unit\_membership\_rule](#input\_administrative\_unit\_membership\_rule) | Dynamic membership rule for the Administrative Unit. Required when administrative\_unit\_name is set.<br><br>Suggested default: "(user.accountEnabled -eq true)"<br>NOTE: This rule will include ALL active users in your tenant. Consider more restrictive rules for production use.<br><br>Examples for more restrictive rules:<br>- "(user.companyName -eq \"MyCompany\") and (user.accountEnabled -eq true)" - Active users from specific company<br>- "(user.userType -eq \"Member\") and (user.accountEnabled -eq true)" - Active member users only<br><br>For more information on membership rules, see:<br>https://learn.microsoft.com/en-us/entra/identity/users/groups-dynamic-membership | `string` | `null` | no |
5251
| <a name="input_administrative_unit_name"></a> [administrative\_unit\_name](#input\_administrative\_unit\_name) | Display name of the adminstration-unit name where the user groups are managed. | `string` | `null` | no |
5352
| <a name="input_application_owners"></a> [application\_owners](#input\_application\_owners) | List of user principals that should be added as owners to the replicator service principal. | `list(string)` | `[]` | no |
5453
| <a name="input_assignment_scopes"></a> [assignment\_scopes](#input\_assignment\_scopes) | The scopes to which Service Principal permissions is assigned to. List of management group id of form `/providers/Microsoft.Management/managementGroups/<mgmtGroupId>/`. | `list(string)` | n/a | yes |

modules/meshcloud-replicator-service-principal/module.tf

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ terraform {
1212
source = "hashicorp/azuread"
1313
version = ">=3.0.2"
1414
}
15-
azapi = {
16-
source = "Azure/azapi"
17-
version = ">=1.13.1"
18-
}
1915
}
2016
}
2117

@@ -133,29 +129,6 @@ resource "azuread_application" "meshcloud_replicator" {
133129
access_token_issuance_enabled = false
134130
}
135131
}
136-
dynamic "required_resource_access" {
137-
for_each = var.administrative_unit_name == null ? [] : [1]
138-
139-
content {
140-
resource_app_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph
141-
142-
resource_access {
143-
id = data.azuread_service_principal.msgraph.app_role_ids["AdministrativeUnit.Read.All"]
144-
type = "Role"
145-
}
146-
147-
# resource_access {
148-
# id = data.azuread_service_principal.msgraph.app_role_ids["Group.create"]
149-
# type = "Role"
150-
# }
151-
152-
# resource_access {
153-
# id = data.azuread_service_principal.msgraph.app_role_ids["User.Invite.All"]
154-
# type = "Role"
155-
# }
156-
}
157-
}
158-
159132

160133
dynamic "required_resource_access" {
161134
for_each = var.additional_required_resource_accesses == null ? [] : var.additional_required_resource_accesses
@@ -327,9 +300,9 @@ resource "azurerm_management_group_policy_assignment" "privilege-escalation-prev
327300
]
328301
}
329302

330-
//--------------------------------------------------------------------------
303+
//---------------------------------------------------------------------------
331304
// Administrative Unit
332-
//--------------------------------------------------------------------------
305+
//---------------------------------------------------------------------------
333306

334307
resource "azuread_administrative_unit" "meshcloud_replicator_au" {
335308
count = var.administrative_unit_name == null ? 0 : 1
@@ -338,33 +311,73 @@ resource "azuread_administrative_unit" "meshcloud_replicator_au" {
338311
}
339312

340313
//--------------------------------------------------------------------------
341-
// Built-in AU-scoped Roles
314+
// Update AU properties via Microsoft Graph API using az rest
342315
//--------------------------------------------------------------------------
343316

344-
resource "azuread_directory_role" "user_administrator" {
345-
count = var.administrative_unit_name == null ? 0 : 1
346-
display_name = "User Administrator"
317+
resource "terraform_data" "patch_admin_unit" {
318+
count = var.administrative_unit_name == null ? 0 : 1
319+
320+
lifecycle {
321+
precondition {
322+
condition = var.administrative_unit_membership_rule != null
323+
error_message = "When administrative_unit_name is set, administrative_unit_membership_rule must also be provided. Suggested value: \"(user.accountEnabled -eq true)\""
324+
}
325+
}
326+
327+
provisioner "local-exec" {
328+
command = <<-EOT
329+
az rest \
330+
--method PATCH \
331+
--url "https://graph.microsoft.com/v1.0/directory/administrativeUnits/${azuread_administrative_unit.meshcloud_replicator_au[0].object_id}" \
332+
--body '{
333+
"displayName": "${var.administrative_unit_name}",
334+
"membershipType": "Dynamic",
335+
"membershipRule": "${var.administrative_unit_membership_rule}",
336+
"membershipRuleProcessingState": "On"
337+
}'
338+
EOT
339+
}
340+
341+
triggers_replace = {
342+
au_id = azuread_administrative_unit.meshcloud_replicator_au[0].object_id
343+
display_name = var.administrative_unit_name
344+
membership_rule = var.administrative_unit_membership_rule
345+
}
346+
347+
depends_on = [azuread_administrative_unit.meshcloud_replicator_au]
347348
}
349+
//--------------------------------------------------------------------------
350+
// Custom AU-scoped Role
351+
//--------------------------------------------------------------------------
348352

349-
resource "azuread_directory_role" "groups_administrator" {
353+
resource "azuread_custom_directory_role" "meshcloud_replicator_au_role" {
350354
count = var.administrative_unit_name == null ? 0 : 1
351-
display_name = "Groups Administrator"
355+
display_name = "meshStack Replicator AU Role"
356+
description = "Custom role for meshStack replicator with limited User and Group permissions"
357+
enabled = true
358+
version = "1.0"
359+
360+
# users permissions
361+
permissions {
362+
allowed_resource_actions = [
363+
"microsoft.directory/users/standard/read",
364+
"microsoft.directory/groups/create",
365+
"microsoft.directory/groups/standard/read",
366+
"microsoft.directory/groups/members/update",
367+
"microsoft.directory/groups/members/read",
368+
"microsoft.directory/groups/memberOf/read",
369+
"microsoft.directory/administrativeUnits/members/read",
370+
"microsoft.directory/administrativeUnits/members/update",
371+
]
372+
}
352373
}
353374

354375
//--------------------------------------------------------------------------
355-
// AU Role Assignments for meshcloud_replicator
376+
// AU Role Assignment for meshcloud_replicator
356377
//--------------------------------------------------------------------------
357378

358-
resource "azuread_administrative_unit_role_member" "user_admin_assignment" {
359-
count = var.administrative_unit_name == null ? 0 : 1
360-
role_object_id = azuread_directory_role.user_administrator[0].object_id
361-
administrative_unit_object_id = azuread_administrative_unit.meshcloud_replicator_au[0].object_id
362-
member_object_id = azuread_service_principal.meshcloud_replicator.object_id
363-
}
364-
365-
resource "azuread_administrative_unit_role_member" "groups_admin_assignment" {
366-
count = var.administrative_unit_name == null ? 0 : 1
367-
role_object_id = azuread_directory_role.groups_administrator[0].object_id
368-
administrative_unit_object_id = azuread_administrative_unit.meshcloud_replicator_au[0].object_id
369-
member_object_id = azuread_service_principal.meshcloud_replicator.object_id
379+
resource "azuread_directory_role_assignment" "meshcloud_replicator_au_assignment" {
380+
count = var.administrative_unit_name == null ? 0 : 1
381+
role_id = azuread_custom_directory_role.meshcloud_replicator_au_role[0].object_id
382+
principal_object_id = azuread_service_principal.meshcloud_replicator.object_id
370383
}

modules/meshcloud-replicator-service-principal/variables.tf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,21 @@ variable "application_owners" {
6565
description = "List of user principals that should be added as owners to the replicator service principal."
6666
default = []
6767
}
68+
69+
variable "administrative_unit_membership_rule" {
70+
type = string
71+
default = null
72+
description = <<-EOT
73+
Dynamic membership rule for the Administrative Unit. Required when administrative_unit_name is set.
74+
75+
Suggested default: "(user.accountEnabled -eq true)"
76+
NOTE: This rule will include ALL active users in your tenant. Consider more restrictive rules for production use.
77+
78+
Examples for more restrictive rules:
79+
- "(user.companyName -eq \"MyCompany\") and (user.accountEnabled -eq true)" - Active users from specific company
80+
- "(user.userType -eq \"Member\") and (user.accountEnabled -eq true)" - Active member users only
81+
82+
For more information on membership rules, see:
83+
https://learn.microsoft.com/en-us/entra/identity/users/groups-dynamic-membership
84+
EOT
85+
}

0 commit comments

Comments
 (0)