Skip to content

Commit c958b66

Browse files
committed
feat: workload identity federation
Allow metering and replicator service principals to use workload identity federation instead of passwords.
1 parent 2ae3425 commit c958b66

File tree

13 files changed

+170
-67
lines changed

13 files changed

+170
-67
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Added privilege escalation prevention policy for replicator
13+
- New configuration option `workload_identity_federation` to create federated credentials. Unset by default.
14+
- New configuration option `create_passwords` to enable/disable creation of password credentials. Defaults to `true`.
1315

1416
## [v0.2.0]
1517

README.md

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -96,58 +96,63 @@ Check [examples](./examples/) for different use cases. As a quick start we recom
9696

9797
| Name | Version |
9898
|------|---------|
99-
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.1 |
100-
| <a name="requirement_azuread"></a> [azuread](#requirement\_azuread) | 2.18.0 |
101-
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | 3.3.0 |
99+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | > 1.1 |
100+
| <a name="requirement_azuread"></a> [azuread](#requirement\_azuread) | 2.46.0 |
101+
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | 3.81.0 |
102102

103103
## Providers
104104

105105
| Name | Version |
106106
|------|---------|
107-
| <a name="provider_azuread"></a> [azuread](#provider\_azuread) | 2.18.0 |
108-
| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | 3.3.0 |
107+
| <a name="provider_azuread"></a> [azuread](#provider\_azuread) | 2.46.0 |
108+
| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | 3.81.0 |
109109

110110
## Modules
111111

112112
| Name | Source | Version |
113113
|------|--------|---------|
114-
| <a name="module_idp_lookup_service_principal"></a> [idp\_lookup\_service\_principal](#module\_idp\_lookup\_service\_principal) | ./modules/meshcloud-idp-lookup-service-principal/ | n/a |
115114
| <a name="module_metering_service_principal"></a> [metering\_service\_principal](#module\_metering\_service\_principal) | ./modules/meshcloud-metering-service-principal/ | n/a |
116115
| <a name="module_replicator_service_principal"></a> [replicator\_service\_principal](#module\_replicator\_service\_principal) | ./modules/meshcloud-replicator-service-principal/ | n/a |
117-
| <a name="module_uami_blueprint_user_principal"></a> [uami\_blueprint\_user\_principal](#module\_uami\_blueprint\_user\_principal) | ./modules/uami-blueprint-user-principal/ | n/a |
116+
| <a name="module_sso_service_principal"></a> [sso\_service\_principal](#module\_sso\_service\_principal) | ./modules/meshcloud-sso/ | n/a |
118117

119118
## Resources
120119

121120
| Name | Type |
122121
|------|------|
123-
| [azuread_client_config.current](https://registry.terraform.io/providers/hashicorp/azuread/2.18.0/docs/data-sources/client_config) | data source |
124-
| [azurerm_management_group.root](https://registry.terraform.io/providers/hashicorp/azurerm/3.3.0/docs/data-sources/management_group) | data source |
122+
| [azuread_client_config.current](https://registry.terraform.io/providers/hashicorp/azuread/2.46.0/docs/data-sources/client_config) | data source |
123+
| [azurerm_management_group.metering_assignment_scopes](https://registry.terraform.io/providers/hashicorp/azurerm/3.81.0/docs/data-sources/management_group) | data source |
124+
| [azurerm_management_group.replicator_assignment_scopes](https://registry.terraform.io/providers/hashicorp/azurerm/3.81.0/docs/data-sources/management_group) | data source |
125+
| [azurerm_management_group.replicator_custom_role_scope](https://registry.terraform.io/providers/hashicorp/azurerm/3.81.0/docs/data-sources/management_group) | data source |
125126

126127
## Inputs
127128

128129
| Name | Description | Type | Default | Required |
129130
|------|-------------|------|---------|:--------:|
130131
| <a name="input_additional_permissions"></a> [additional\_permissions](#input\_additional\_permissions) | Additional Subscription-Level Permissions the Service Principal needs. | `list(string)` | `[]` | no |
131132
| <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 |
132-
| <a name="input_idplookup_enabled"></a> [idplookup\_enabled](#input\_idplookup\_enabled) | Whether to create idplookup Service Principal or not. | `bool` | `true` | no |
133+
| <a name="input_create_passwords"></a> [create\_passwords](#input\_create\_passwords) | Create passwords for service principals. | `bool` | `true` | no |
134+
| <a name="input_metering_assignment_scopes"></a> [metering\_assignment\_scopes](#input\_metering\_assignment\_scopes) | Names or UUIDs of the Management Groups that kraken should collect costs for. | `list(string)` | n/a | yes |
133135
| <a name="input_metering_enabled"></a> [metering\_enabled](#input\_metering\_enabled) | Whether to create Metering Service Principal or not. | `bool` | `true` | no |
134-
| <a name="input_mgmt_group_name"></a> [mgmt\_group\_name](#input\_mgmt\_group\_name) | The name or UUID of the Management Group. | `string` | n/a | yes |
136+
| <a name="input_metering_service_principal_name"></a> [metering\_service\_principal\_name](#input\_metering\_service\_principal\_name) | Service principal for collecting cost data. Kraken ist the name of the meshStack component. Name must be unique per Entra ID. | `string` | `"kraken"` | no |
137+
| <a name="input_replicator_assignment_scopes"></a> [replicator\_assignment\_scopes](#input\_replicator\_assignment\_scopes) | Names or UUIDs of the Management Groups which replicator should manage. | `list(string)` | n/a | yes |
138+
| <a name="input_replicator_custom_role_scope"></a> [replicator\_custom\_role\_scope](#input\_replicator\_custom\_role\_scope) | Name or UUID of the Management Group of the replicator custom role definition. The custom role definition must be available for all assignment scopes. | `string` | n/a | yes |
135139
| <a name="input_replicator_enabled"></a> [replicator\_enabled](#input\_replicator\_enabled) | Whether to create replicator Service Principal or not. | `bool` | `true` | no |
136-
| <a name="input_replicator_rg_enabled"></a> [replicator\_rg\_enabled](#input\_replicator\_rg\_enabled) | Enables the replicator service principal to be used for Azure Resource Group replication. Implicitly enables the `replicator_enabled` flag. | `bool` | `true` | no |
137-
| <a name="input_service_principal_name_suffix"></a> [service\_principal\_name\_suffix](#input\_service\_principal\_name\_suffix) | Service principal name suffix. Make sure this is unique. | `string` | n/a | yes |
138-
| <a name="input_subscriptions"></a> [subscriptions](#input\_subscriptions) | The scope to which UAMI blueprint service principal role assignment is applied. | `list(any)` | `[]` | no |
140+
| <a name="input_replicator_rg_enabled"></a> [replicator\_rg\_enabled](#input\_replicator\_rg\_enabled) | Whether the created replicator Service Principal should be usable for Azure Resource Group based replication. Implicitly enables replicator\_enabled if set to true. | `bool` | `false` | no |
141+
| <a name="input_replicator_service_principal_name"></a> [replicator\_service\_principal\_name](#input\_replicator\_service\_principal\_name) | Service principal for managing subscriptions. Replicator is the name of the meshStack component. Name must be unique per Entra ID. | `string` | `"replicator"` | no |
142+
| <a name="input_sso_enabled"></a> [sso\_enabled](#input\_sso\_enabled) | Whether to create SSO Service Principal or not. | `bool` | `true` | no |
143+
| <a name="input_sso_meshstack_redirect_uri"></a> [sso\_meshstack\_redirect\_uri](#input\_sso\_meshstack\_redirect\_uri) | Redirect URI that was provided by meshcloud. It is individual per meshStack. | `string` | `"<replace with uri>"` | no |
144+
| <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` | `"sso"` | no |
145+
| <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 |
139146

140147
## Outputs
141148

142149
| Name | Description |
143150
|------|-------------|
144151
| <a name="output_azure_ad_tenant_id"></a> [azure\_ad\_tenant\_id](#output\_azure\_ad\_tenant\_id) | The Azure AD tenant id. |
145-
| <a name="output_idp_lookup_service_principal"></a> [idp\_lookup\_service\_principal](#output\_idp\_lookup\_service\_principal) | IDP Lookup Service Principal. |
146-
| <a name="output_idp_lookup_service_principal_password"></a> [idp\_lookup\_service\_principal\_password](#output\_idp\_lookup\_service\_principal\_password) | Password for IDP Lookup Service Principal. |
147152
| <a name="output_metering_service_principal"></a> [metering\_service\_principal](#output\_metering\_service\_principal) | Metering Service Principal. |
148153
| <a name="output_metering_service_principal_password"></a> [metering\_service\_principal\_password](#output\_metering\_service\_principal\_password) | Password for Metering Service Principal. |
149154
| <a name="output_replicator_service_principal"></a> [replicator\_service\_principal](#output\_replicator\_service\_principal) | Replicator Service Principal. |
150155
| <a name="output_replicator_service_principal_password"></a> [replicator\_service\_principal\_password](#output\_replicator\_service\_principal\_password) | Password for Replicator Service Principal. |
151-
| <a name="output_uami_blueprint_user_principal"></a> [uami\_blueprint\_user\_principal](#output\_uami\_blueprint\_user\_principal) | UAMI Blueprint Assignment Service Principal. |
152-
| <a name="output_uami_blueprint_user_principal_password"></a> [uami\_blueprint\_user\_principal\_password](#output\_uami\_blueprint\_user\_principal\_password) | Password for UAMI Blueprint Assignment Service Principal. |
156+
| <a name="output_sso_service_principal"></a> [sso\_service\_principal](#output\_sso\_service\_principal) | SSO Service Principal. |
157+
| <a name="output_sso_service_principal_password"></a> [sso\_service\_principal\_password](#output\_sso\_service\_principal\_password) | Password for SSO Service Principal. |
153158
<!-- END_TF_DOCS -->

main.tf

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@ locals {
3535
]
3636
}
3737

38-
data "azuread_client_config" "current" {}
38+
data "azuread_client_config" "current" {
39+
# This precondition doesn't have anything to do with the data source but we want to check this condition on the top level.
40+
lifecycle {
41+
precondition {
42+
condition = anytrue([var.create_passwords, var.workload_identity_federation != null])
43+
error_message = "Set at least one of `create_passwords` and `workload_identity_federation`."
44+
}
45+
}
46+
}
3947

4048
module "replicator_service_principal" {
4149
count = var.replicator_enabled || var.replicator_rg_enabled ? 1 : 0
@@ -47,6 +55,12 @@ module "replicator_service_principal" {
4755

4856
additional_required_resource_accesses = var.additional_required_resource_accesses
4957
additional_permissions = var.additional_permissions
58+
59+
create_password = var.create_passwords
60+
workload_identity_federation = var.workload_identity_federation == null ? null : {
61+
issuer = var.workload_identity_federation.issuer,
62+
subject = var.workload_identity_federation.replicator_subject
63+
}
5064
}
5165

5266
module "metering_service_principal" {
@@ -55,6 +69,12 @@ module "metering_service_principal" {
5569

5670
service_principal_name = var.metering_service_principal_name
5771
assignment_scopes = local.metering_assignment_scopes
72+
73+
create_password = var.create_passwords
74+
workload_identity_federation = var.workload_identity_federation == null ? null : {
75+
issuer = var.workload_identity_federation.issuer,
76+
subject = var.workload_identity_federation.kraken_subject
77+
}
5878
}
5979

6080
module "sso_service_principal" {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//---------------------------------------------------------------------------
2+
// Create a password for the Enterprise application
3+
//---------------------------------------------------------------------------
4+
resource "time_rotating" "replicator_secret_rotation" {
5+
count = var.create_password ? 1 : 0
6+
7+
rotation_days = 365
8+
}
9+
10+
resource "azuread_application_password" "application_pw" {
11+
count = var.create_password ? 1 : 0
12+
13+
application_id = azuread_application.meshcloud_metering.id
14+
rotate_when_changed = {
15+
rotation = time_rotating.replicator_secret_rotation[0].id
16+
}
17+
}
18+
19+
moved {
20+
from = azurerm_role_assignment.meshcloud_kraken
21+
to = azurerm_role_assignment.meshcloud_metering
22+
}
23+
24+
moved {
25+
from = azuread_application.meshcloud_kraken
26+
to = azuread_application.meshcloud_metering
27+
}
28+
29+
moved {
30+
from = azuread_service_principal.meshcloud_kraken
31+
to = azuread_service_principal.meshcloud_metering
32+
}
33+
34+
//---------------------------------------------------------------------------
35+
// Create federated identity credentials
36+
//---------------------------------------------------------------------------
37+
resource "azuread_application_federated_identity_credential" "meshcloud_metering" {
38+
count = var.workload_identity_federation == null ? 0 : 1
39+
40+
application_id = azuread_application.meshcloud_metering.id
41+
display_name = var.service_principal_name
42+
audiences = ["api://AzureADTokenExchange"]
43+
issuer = var.workload_identity_federation.issuer
44+
subject = var.workload_identity_federation.subject
45+
}

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

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -54,32 +54,3 @@ resource "azuread_service_principal" "meshcloud_metering" {
5454
enterprise = true
5555
}
5656
}
57-
58-
//---------------------------------------------------------------------------
59-
// Create a password for the Enterprise application
60-
//---------------------------------------------------------------------------
61-
resource "time_rotating" "replicator_secret_rotation" {
62-
rotation_days = 365
63-
}
64-
65-
resource "azuread_application_password" "application_pw" {
66-
application_id = azuread_application.meshcloud_metering.id
67-
rotate_when_changed = {
68-
rotation = time_rotating.replicator_secret_rotation.id
69-
}
70-
}
71-
72-
moved {
73-
from = azurerm_role_assignment.meshcloud_kraken
74-
to = azurerm_role_assignment.meshcloud_metering
75-
}
76-
77-
moved {
78-
from = azuread_application.meshcloud_kraken
79-
to = azuread_application.meshcloud_metering
80-
}
81-
82-
moved {
83-
from = azuread_service_principal.meshcloud_kraken
84-
to = azuread_service_principal.meshcloud_metering
85-
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ output "credentials" {
33
value = {
44
Enterprise_Application_Object_ID = azuread_service_principal.meshcloud_metering.id
55
Application_Client_ID = azuread_application.meshcloud_metering.client_id
6-
Client_Secret = "Execute `terraform output metering_client_secret` to see the password"
6+
Client_Secret = var.create_password ? "Execute `terraform output metering_service_principal_password` to see the password" : "No password was created"
77
}
88
}
99

1010
output "application_client_secret" {
1111
description = "Client Secret Of the Application."
12-
value = azuread_application_password.application_pw.value
12+
value = var.create_password ? azuread_application_password.application_pw[0].value : null
1313
sensitive = true
1414
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,14 @@ variable "assignment_scopes" {
77
type = list(string)
88
description = "The scopes to which Service Principal permissions should be assigned to. Usually this is the management group id of form `/providers/Microsoft.Management/managementGroups/<tenantId>` that sits atop the subscriptions."
99
}
10+
11+
variable "create_password" {
12+
type = bool
13+
description = "Create a password for the enterprise application."
14+
}
15+
16+
variable "workload_identity_federation" {
17+
default = null
18+
description = "Enable workload identity federation instead of using a password by providing these additional settings. Usually you should receive the required settings when attempting to configure a platform with workload identity federation in meshStack."
19+
type = object({ issuer = string, subject = string })
20+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//---------------------------------------------------------------------------
2+
// Create new client secret and associate it with the application
3+
//---------------------------------------------------------------------------
4+
resource "time_rotating" "replicator_secret_rotation" {
5+
count = var.create_password ? 1 : 0
6+
7+
rotation_days = 365
8+
}
9+
10+
resource "azuread_application_password" "application_pw" {
11+
count = var.create_password ? 1 : 0
12+
13+
application_id = azuread_application.meshcloud_replicator.id
14+
rotate_when_changed = {
15+
rotation = time_rotating.replicator_secret_rotation[0].id
16+
}
17+
}
18+
19+
moved {
20+
from = time_rotating.replicator_secret_rotation
21+
to = time_rotating.replicator_secret_rotation[0]
22+
}
23+
24+
moved {
25+
from = azuread_application_password.application_pw
26+
to = azuread_application_password.application_pw[0]
27+
}
28+
29+
//---------------------------------------------------------------------------
30+
// Create federated identity credentials
31+
//---------------------------------------------------------------------------
32+
resource "azuread_application_federated_identity_credential" "meshcloud_replicator" {
33+
count = var.workload_identity_federation == null ? 0 : 1
34+
35+
application_id = azuread_application.meshcloud_replicator.id
36+
display_name = var.service_principal_name
37+
audiences = ["api://AzureADTokenExchange"]
38+
issuer = var.workload_identity_federation.issuer
39+
subject = var.workload_identity_federation.subject
40+
}

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

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,20 +132,7 @@ resource "azuread_application" "meshcloud_replicator" {
132132
}
133133

134134
//---------------------------------------------------------------------------
135-
// Create new client secret and associate it with the previous application
136-
//---------------------------------------------------------------------------
137-
resource "time_rotating" "replicator_secret_rotation" {
138-
rotation_days = 365
139-
}
140-
resource "azuread_application_password" "application_pw" {
141-
application_id = azuread_application.meshcloud_replicator.id
142-
rotate_when_changed = {
143-
rotation = time_rotating.replicator_secret_rotation.id
144-
}
145-
}
146-
147-
//---------------------------------------------------------------------------
148-
// Create new Enterprise Application and associate it with the previous application
135+
// Create new Enterprise Application and associate it with the application
149136
//---------------------------------------------------------------------------
150137
resource "azuread_service_principal" "meshcloud_replicator" {
151138
client_id = azuread_application.meshcloud_replicator.client_id

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ output "credentials" {
33
value = {
44
Enterprise_Application_Object_ID = azuread_service_principal.meshcloud_replicator.id
55
Application_Client_ID = azuread_application.meshcloud_replicator.client_id
6-
Client_Secret = "Execute `terraform output replicator_client_secret` to see the password"
6+
Client_Secret = var.create_password ? "Execute `terraform output replicator_service_principal_password` to see the password" : "No password was created"
77
}
88
}
99

1010
output "application_client_secret" {
1111
description = "Client Secret Of the Application."
12-
value = azuread_application_password.application_pw.value
12+
value = var.create_password ? azuread_application_password.application_pw[0].value : null
1313
sensitive = true
1414
}

0 commit comments

Comments
 (0)