Skip to content

Commit 2271ee8

Browse files
committed
feat: support WIF for MCA SPs
1 parent 3d181e9 commit 2271ee8

File tree

7 files changed

+148
-11
lines changed

7 files changed

+148
-11
lines changed

CHANGELOG.md

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

88
## [Unreleased]
99

10+
## [v0.12.0]
11+
12+
### Added
13+
14+
- Optional administrative unit support
15+
- Support workload identity federation for MCA service principals
16+
17+
## [v0.11.0]
18+
19+
### Added
20+
21+
- Billing scope to outputs
22+
- Admin consent for delegated permission in SSO module
23+
24+
### Changed
25+
1026
- Upgraded minimum terraform provider versions
1127

1228
## [v0.10.0]
@@ -82,7 +98,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8298

8399
- Initial Release
84100

85-
[unreleased]: https://github.com/meshcloud/terraform-azure-meshplatform/compare/v0.10.0...HEAD
101+
[unreleased]: https://github.com/meshcloud/terraform-azure-meshplatform/compare/v0.12.0...HEAD
102+
[v0.12.0]: https://github.com/meshcloud/terraform-azure-meshplatform/releases/tag/v0.12.0
103+
[v0.11.0]: https://github.com/meshcloud/terraform-azure-meshplatform/releases/tag/v0.11.0
86104
[v0.1.0]: https://github.com/meshcloud/terraform-azure-meshplatform/releases/tag/v0.1.0
87105
[v0.2.0]: https://github.com/meshcloud/terraform-azure-meshplatform/releases/tag/v0.2.0
88106
[v0.3.0]: https://github.com/meshcloud/terraform-azure-meshplatform/releases/tag/v0.3.0

README.md

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ To run this module, you need the following:
1414

1515
- [Terraform installed](https://learn.hashicorp.com/tutorials/terraform/install-cli) (already installed in Azure Portal)
1616
- [Azure CLI installed](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) (already installed in Azure Portal)
17-
- Permissions on AAD level. If using Microsoft Customer Agreement, AAD level permissions must be set in the Tenant Directory that will create the subscriptions (*Source Tenant*) as well as the Tenant Directory that will receive the subscriptions (*Destination Tenant*). An Azure account with one of the following roles:
17+
- Permissions on Entra ID level. If using Microsoft Customer Agreement, Entra ID level permissions must be set in the Tenant Directory that will create the subscriptions (*Source Tenant*) as well as the Tenant Directory that will receive the subscriptions (*Destination Tenant*). An Azure account with one of the following roles:
1818
1. Global Administrator
1919
2. Privileged Role Administrator AND (Cloud) Application Administrator
2020
- Permissions on Azure Resource Level: User Access Administrator on the Management Group that should be managed by meshStack
@@ -83,11 +83,11 @@ To run this module, you need the following:
8383

8484
**Prerequisites**:
8585

86-
- Ensure you have permissions in the source AAD Tenant for granting access to the billing account used for subscription creation using the `Account Administrator` role
86+
- Ensure you have permissions in the source Entra ID Tenant for granting access to the billing account used for subscription creation using the `Account Administrator` role
8787

8888
**Create MCA service principals**:
8989

90-
> With this module, you can create multiple MCA service principals by passing a list of `mca.service_principal_names`. This is useful for environments with restricted acceses to the AAD tenant holding the MCA license.
90+
> With this module, you can create multiple MCA service principals by passing a list of `mca.service_principal_names`. This is useful for environments with restricted access to the Entra ID tenant holding the MCA license. A sample case would be to have one "source" Entra ID tenant in your organization in which you can create Azure subscriptions, and multiple "destination" tenants.
9191
9292
Add an `mca` block when calling this module.
9393

@@ -107,6 +107,41 @@ module "meshplatform" {
107107
}
108108
```
109109

110+
## Workload Identity Federation for Multiple Environments
111+
112+
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.
113+
114+
### Single Subject (All MCA Service Principals)
115+
116+
Use `mca_subject` when all MCA service principals should use the same Kubernetes service account:
117+
118+
```hcl
119+
workload_identity_federation = {
120+
issuer = "..."
121+
replicator_subject = "system:serviceaccount:meshcloud:replicator"
122+
kraken_subject = "system:serviceaccount:meshcloud:kraken"
123+
mca_subject = "system:serviceaccount:meshcloud:replicator" # with this, all MCA SPs will have this subject in its federated credentials
124+
}
125+
```
126+
127+
### Per-Service-Principal Subjects (Recommended for Multi-Environment)
128+
129+
Use `mca_subjects` to configure different subjects for each MCA service principal
130+
131+
```hcl
132+
workload_identity_federation = {
133+
issuer = "..."
134+
replicator_subject = "system:serviceaccount:meshcloud-dev:replicator"
135+
kraken_subject = "system:serviceaccount:meshcloud-dev:kraken"
136+
mca_subjects = {
137+
"meshcloud-dev" = "system:serviceaccount:meshcloud-dev:replicator"
138+
"meshcloud-prod" = "system:serviceaccount:meshcloud-prod:replicator"
139+
}
140+
}
141+
```
142+
143+
This approach allows each service principal to have its own custom subject when configuring WIF.
144+
110145
### Using Pre-provisioned Subscriptions
111146

112147
meshStack will need to be able to read subscriptions at the source location
@@ -218,7 +253,7 @@ Before opening a Pull Request, please do the following:
218253
| <a name="input_sso_identity_provider_alias"></a> [sso\_identity\_provider\_alias](#input\_sso\_identity\_provider\_alias) | Identity provider alias. This value needs to be passed to meshcloud to configure the identity provider. | `string` | `"oidc"` | no |
219254
| <a name="input_sso_meshstack_idp_domain"></a> [sso\_meshstack\_idp\_domain](#input\_sso\_meshstack\_idp\_domain) | meshStack identity provider domain that was provided by meshcloud. It is individual per meshStack. In most cases it is sso.<portal-domain> | `string` | `"replaceme"` | no |
220255
| <a name="input_sso_service_principal_name"></a> [sso\_service\_principal\_name](#input\_sso\_service\_principal\_name) | Service principal for Entra ID SSO. Name must be unique per Entra ID. | `string` | `"meshcloud SSO"` | no |
221-
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Enable workload identity federation by creating federated credentials for enterprise applications. Usually you'd receive the required settings when attempting to configure a platform with workload identity federation in meshStack. | `object({ issuer = string, replicator_subject = string, kraken_subject = string })` | `null` | no |
256+
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Enable workload identity federation by creating federated credentials for enterprise applications. Usually you'd receive the required settings when attempting to configure a platform with workload identity federation in meshStack. | <pre>object({<br> issuer = string<br> replicator_subject = string<br> kraken_subject = string<br> # For MCA service principals: can be either a single subject for all SPs or a map of SP name to subject<br> mca_subject = optional(string)<br> mca_subjects = optional(map(string))<br> })</pre> | `null` | no |
222257

223258
## Outputs
224259

main.tf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ module "mca_service_principal" {
8484
billing_profile_name = var.mca.billing_profile_name
8585
invoice_section_name = var.mca.invoice_section_name
8686

87+
create_password = var.create_passwords
88+
workload_identity_federation = var.workload_identity_federation == null ? null : {
89+
issuer = var.workload_identity_federation.issuer
90+
# Use per-service-principal subjects if provided, otherwise fall back to single subject
91+
subject = var.workload_identity_federation.mca_subjects == null ? var.workload_identity_federation.mca_subject : null
92+
subjects = var.workload_identity_federation.mca_subjects
93+
}
94+
8795
application_owners = var.application_owners
8896
}
8997

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,45 @@ resource "azapi_resource_action" "remove_role_assignment_subscription_creator" {
8282
// Create new client secret and associate it with the application
8383
//---------------------------------------------------------------------------
8484
resource "time_rotating" "mca_secret_rotation" {
85+
count = var.create_password ? 1 : 0
86+
8587
rotation_days = 365
8688
}
8789

8890
resource "azuread_application_password" "mca" {
89-
for_each = toset(var.service_principal_names)
91+
for_each = var.create_password ? toset(var.service_principal_names) : toset([])
9092

9193
application_id = azuread_application.mca[each.key].id
9294
rotate_when_changed = {
93-
rotation = time_rotating.mca_secret_rotation.id
95+
rotation = time_rotating.mca_secret_rotation[0].id
9496
}
9597
}
98+
99+
//---------------------------------------------------------------------------
100+
// Create federated identity credentials
101+
//---------------------------------------------------------------------------
102+
locals {
103+
# Determine the subject for each service principal
104+
wif_subjects = var.workload_identity_federation == null ? {} : (
105+
var.workload_identity_federation.subjects != null
106+
? var.workload_identity_federation.subjects
107+
: var.workload_identity_federation.subject != null
108+
? { for name in var.service_principal_names : name => var.workload_identity_federation.subject }
109+
: {}
110+
)
111+
}
112+
113+
resource "azuread_application_federated_identity_credential" "mca" {
114+
for_each = length(local.wif_subjects) > 0 ? toset(keys(local.wif_subjects)) : toset([])
115+
116+
application_id = azuread_application.mca[each.key].id
117+
display_name = each.key
118+
audiences = ["api://AzureADTokenExchange"]
119+
issuer = var.workload_identity_federation.issuer
120+
subject = local.wif_subjects[each.key]
121+
}
122+
123+
moved {
124+
from = time_rotating.mca_secret_rotation
125+
to = time_rotating.mca_secret_rotation[0]
126+
}

modules/meshcloud-mca-service-principal/outputs.tf

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ output "credentials" {
88
for name in var.service_principal_names : name => {
99
Enterprise_Application_Object_ID = azuread_service_principal.mca[name].object_id
1010
Application_Client_ID = azuread_application.mca[name].client_id
11-
Client_Secret = "Execute `terraform output mca_service_principal_password` to see the password"
11+
Client_Secret = var.create_password ? "Execute `terraform output mca_service_principal_password` to see the password" : "No password was created"
1212
}
1313
}
1414
}
1515

1616
output "application_client_secret" {
1717
description = "Client Secret Of the Application."
18-
value = { for name in var.service_principal_names : name => azuread_application_password.mca[name].value }
18+
value = var.create_password ? { for name in var.service_principal_names : name => azuread_application_password.mca[name].value } : {}
1919
sensitive = true
2020
}
21-

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,31 @@ variable "application_owners" {
1919
description = "List of user principals that should be added as owners to the mca service principal."
2020
default = []
2121
}
22+
23+
variable "create_password" {
24+
type = bool
25+
description = "Create a password for the enterprise application."
26+
default = true
27+
}
28+
29+
variable "workload_identity_federation" {
30+
default = null
31+
description = "Enable workload identity federation instead of using a password by providing these additional settings. Can be either a single configuration for all service principals, or a map with per-service-principal configuration."
32+
type = object({
33+
issuer = string
34+
# subject can be either a single string (applied to all SPs) or a map of SP name to subject
35+
subject = optional(string)
36+
subjects = optional(map(string))
37+
})
38+
39+
validation {
40+
condition = var.workload_identity_federation == null || (
41+
var.workload_identity_federation.subject != null && var.workload_identity_federation.subjects == null
42+
) || (
43+
var.workload_identity_federation.subject == null && var.workload_identity_federation.subjects != null
44+
) || (
45+
var.workload_identity_federation.subject == null && var.workload_identity_federation.subjects == null
46+
)
47+
error_message = "If using workload_identity_federation for MCA, either 'subject' (for all service principals) or 'subjects' (per service principal) can be provided, but not both. Both can be null if MCA WIF is not needed."
48+
}
49+
}

variables.tf

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,25 @@ variable "create_passwords" {
118118
variable "workload_identity_federation" {
119119
default = null
120120
description = "Enable workload identity federation by creating federated credentials for enterprise applications. Usually you'd receive the required settings when attempting to configure a platform with workload identity federation in meshStack."
121-
type = object({ issuer = string, replicator_subject = string, kraken_subject = string })
121+
type = object({
122+
issuer = string
123+
replicator_subject = string
124+
kraken_subject = string
125+
# For MCA service principals: can be either a single subject for all SPs or a map of SP name to subject
126+
mca_subject = optional(string)
127+
mca_subjects = optional(map(string))
128+
})
129+
130+
validation {
131+
condition = var.workload_identity_federation == null || (
132+
var.workload_identity_federation.mca_subject == null && var.workload_identity_federation.mca_subjects == null
133+
) || (
134+
var.workload_identity_federation.mca_subject != null && var.workload_identity_federation.mca_subjects == null
135+
) || (
136+
var.workload_identity_federation.mca_subject == null && var.workload_identity_federation.mca_subjects != null
137+
)
138+
error_message = "For MCA configuration, either 'mca_subject' (for all service principals) or 'mca_subjects' (per service principal) can be provided, but not both."
139+
}
122140
}
123141

124142
variable "mca" {

0 commit comments

Comments
 (0)