Skip to content

Commit 0baa81a

Browse files
committed
chore: support additional subject for wif backplane (gcp and azure)
1 parent a00b0b5 commit 0baa81a

File tree

11 files changed

+107
-86
lines changed

11 files changed

+107
-86
lines changed

modules/aws/s3_bucket/backplane/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ No modules.
6565

6666
| Name | Description | Type | Default | Required |
6767
|------|-------------|------|---------|:--------:|
68-
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Set these options to add a trusted identity provider from meshStack to allow workload identity federation for authentication which can be used instead of access keys. Supports multiple subjects for migration paths and wildcard patterns (e.g., 'system:serviceaccount:namespace:*'). | <pre>object({<br> issuer = string,<br> audience = string,<br> subjects = list(string)<br> })</pre> | `null` | no |
68+
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Set these options to add a trusted identity provider from meshStack to allow workload identity federation for authentication which can be used instead of access keys. Supports multiple subjects and wildcard patterns (e.g., 'system:serviceaccount:namespace:*'). | <pre>object({<br> issuer = string,<br> audience = string,<br> subjects = list(string)<br> })</pre> | `null` | no |
6969

7070
## Outputs
7171

modules/aws/s3_bucket/backplane/variables.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ variable "workload_identity_federation" {
55
subjects = list(string)
66
})
77
default = null
8-
description = "Set these options to add a trusted identity provider from meshStack to allow workload identity federation for authentication which can be used instead of access keys. Supports multiple subjects for migration paths and wildcard patterns (e.g., 'system:serviceaccount:namespace:*')."
8+
description = "Set these options to add a trusted identity provider from meshStack to allow workload identity federation for authentication which can be used instead of access keys. Supports multiple subjects and wildcard patterns (e.g., 'system:serviceaccount:namespace:*')."
99
}

modules/azure/storage-account/backplane/.terraform.lock.hcl

Lines changed: 0 additions & 42 deletions
This file was deleted.

modules/azure/storage-account/backplane/README.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,30 @@ module "storage_account_backplane" {
6161
6262
create_service_principal_name = "deployment-sp"
6363
workload_identity_federation = {
64-
issuer = "https://token.actions.githubusercontent.com"
65-
subject = "repo:my-org/my-repo:ref:refs/heads/main"
64+
issuer = "https://token.actions.githubusercontent.com"
65+
subjects = [
66+
"repo:my-org/my-repo:ref:refs/heads/main",
67+
"repo:my-org/my-repo:environment:production",
68+
]
6669
}
6770
}
6871
```
6972

73+
### Subject Matching
74+
75+
**Only exact matching is supported for subjects.** Each subject in the `subjects` list will create a separate federated identity credential.
76+
77+
When using Kubernetes service accounts, provide the full subject identifier:
78+
```hcl
79+
workload_identity_federation = {
80+
issuer = "https://your-oidc-issuer"
81+
subjects = [
82+
"system:serviceaccount:namespace1:service-account-1",
83+
"system:serviceaccount:namespace1:service-account-2",
84+
]
85+
}
86+
```
87+
7088
### Mixed Usage (Both Existing and New)
7189

7290
```hcl
@@ -90,7 +108,7 @@ module "storage_account_backplane" {
90108
| Name | Version |
91109
|------|---------|
92110
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
93-
| <a name="requirement_azuread"></a> [azuread](#requirement\_azuread) | ~> 3.5.0 |
111+
| <a name="requirement_azuread"></a> [azuread](#requirement\_azuread) | ~> 3.7.0 |
94112
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | 3.116.0 |
95113

96114
## Modules
@@ -117,7 +135,7 @@ No modules.
117135
| <a name="input_existing_principal_ids"></a> [existing\_principal\_ids](#input\_existing\_principal\_ids) | set of existing principal ids that will be granted permissions to deploy the building block | `set(string)` | `[]` | no |
118136
| <a name="input_name"></a> [name](#input\_name) | name of the building block, used for naming resources | `string` | n/a | yes |
119137
| <a name="input_scope"></a> [scope](#input\_scope) | Scope where the building block should be deployable, typically the parent of all Landing Zones. | `string` | n/a | yes |
120-
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Configuration for workload identity federation. If not provided, an application password will be created instead. | <pre>object({<br> issuer = string<br> subject = string<br> })</pre> | `null` | no |
138+
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Configuration for workload identity federation. If not provided, an application password will be created instead. Supports multiple subjects. | <pre>object({<br> issuer = string<br> subjects = list(string)<br> })</pre> | `null` | no |
121139

122140
## Outputs
123141

@@ -132,5 +150,5 @@ No modules.
132150
| <a name="output_role_definition_id"></a> [role\_definition\_id](#output\_role\_definition\_id) | The ID of the role definition that enables deployment of the building block to subscriptions. |
133151
| <a name="output_role_definition_name"></a> [role\_definition\_name](#output\_role\_definition\_name) | The name of the role definition that enables deployment of the building block to subscriptions. |
134152
| <a name="output_scope"></a> [scope](#output\_scope) | The scope where the role definition and role assignments are applied. |
135-
| <a name="output_workload_identity_federation"></a> [workload\_identity\_federation](#output\_workload\_identity\_federation) | Information about the created workload identity federation credential. |
153+
| <a name="output_workload_identity_federation"></a> [workload\_identity\_federation](#output\_workload\_identity\_federation) | Information about the created workload identity federation credentials. |
136154
<!-- END_TF_DOCS -->

modules/azure/storage-account/backplane/main.tf

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
resource "azuread_application" "buildingblock_deploy" {
33
count = var.create_service_principal_name != null ? 1 : 0
44

5-
display_name = "${var.name}-${var.create_service_principal_name}"
5+
display_name = var.create_service_principal_name
66
}
77

88
resource "azuread_service_principal" "buildingblock_deploy" {
@@ -13,17 +13,16 @@ resource "azuread_service_principal" "buildingblock_deploy" {
1313
}
1414

1515

16-
# Create federated identity credentials
16+
# Create federated identity credentials (one per subject)
1717
resource "azuread_application_federated_identity_credential" "buildingblock_deploy" {
18-
count = var.create_service_principal_name != null && var.workload_identity_federation != null ? 1 : 0
18+
for_each = var.create_service_principal_name != null && var.workload_identity_federation != null ? toset(var.workload_identity_federation.subjects) : toset([])
1919

2020
application_id = azuread_application.buildingblock_deploy[0].id
21-
display_name = var.create_service_principal_name
21+
display_name = reverse(split(":", each.value))[0]
2222
audiences = ["api://AzureADTokenExchange"]
2323
issuer = var.workload_identity_federation.issuer
24-
subject = var.workload_identity_federation.subject
24+
subject = each.value
2525
}
26-
2726
# Create application password (when not using workload identity federation)
2827
resource "azuread_application_password" "buildingblock_deploy" {
2928
count = var.create_service_principal_name != null && var.workload_identity_federation == null ? 1 : 0

modules/azure/storage-account/backplane/outputs.tf

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@ output "created_application" {
4242
} : null
4343
description = "Information about the created Azure AD application."
4444
}
45-
4645
output "workload_identity_federation" {
47-
value = var.create_service_principal_name != null && var.workload_identity_federation != null ? {
48-
credential_id = azuread_application_federated_identity_credential.buildingblock_deploy[0].credential_id
49-
display_name = azuread_application_federated_identity_credential.buildingblock_deploy[0].display_name
50-
issuer = azuread_application_federated_identity_credential.buildingblock_deploy[0].issuer
51-
subject = azuread_application_federated_identity_credential.buildingblock_deploy[0].subject
52-
audiences = azuread_application_federated_identity_credential.buildingblock_deploy[0].audiences
53-
} : null
54-
description = "Information about the created workload identity federation credential."
46+
value = var.create_service_principal_name != null && var.workload_identity_federation != null ? [
47+
for wif in azuread_application_federated_identity_credential.buildingblock_deploy : {
48+
credential_id = wif.credential_id
49+
display_name = wif.display_name
50+
issuer = wif.issuer
51+
subject = wif.subject
52+
audiences = wif.audiences
53+
}] : null
54+
description = "Information about the created workload identity federation credentials."
5555
}
5656

5757
output "application_password" {

modules/azure/storage-account/backplane/variables.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ variable "create_service_principal_name" {
3333

3434
variable "workload_identity_federation" {
3535
type = object({
36-
issuer = string
37-
subject = string
36+
issuer = string
37+
subjects = list(string)
3838
})
3939
default = null
40-
description = "Configuration for workload identity federation. If not provided, an application password will be created instead."
40+
description = "Configuration for workload identity federation. If not provided, an application password will be created instead. Supports multiple subjects."
4141
}

modules/azure/storage-account/backplane/versions.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ terraform {
88
}
99
azuread = {
1010
source = "hashicorp/azuread"
11-
version = "~> 3.5.0"
11+
version = "~> 3.7.0"
1212
}
1313
}
1414
}

modules/gcp/storage-bucket/backplane/README.md

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,58 @@ This module provisions the necessary IAM resources for the GCP Storage Bucket bu
88
module "gcp_storage_bucket_backplane" {
99
source = "git::https://github.com/meshcloud/meshstack-hub.git//modules/gcp/storage-bucket/backplane"
1010
11-
project_id = "your-gcp-project-id"
12-
service_account_id = "your-service-account-id" # Optional, defaults to "buildingblock-storage-sa"
11+
project_id = "your-gcp-project-id"
12+
service_account_id = "your-service-account-id" # Optional, defaults to "buildingblock-storage-sa"
1313
workload_identity_federation = {
1414
workload_identity_pool_identifier = "your-pool-identifier"
1515
audience = "your-audience"
1616
issuer = "https://your-oidc-issuer"
17-
subject = "system:serviceaccount:your-namespace:your-service-account-name"
18-
subject_token_file_path = "/path/to/your/token/file"
19-
} # Optional, if not provided, workload identity federation will not be set up and a service account key will be created
17+
subjects = [
18+
"system:serviceaccount:your-namespace:your-service-account-name",
19+
"system:serviceaccount:your-namespace:another-service-account",
20+
]
21+
subject_token_file_path = "/path/to/your/token/file"
22+
} # Optional, if not provided, a service account key will be created instead
2023
}
2124
```
2225

26+
## Workload Identity Federation
27+
28+
When `workload_identity_federation` is configured, the module grants access to the entire workload identity pool at the IAM level, then uses attribute conditions at the provider level to restrict which identities can actually authenticate.
29+
30+
### Subject Matching
31+
32+
The module supports both exact matching and partial matching for subjects:
33+
34+
**Exact matching** - Grant access to specific subjects:
35+
```hcl
36+
workload_identity_federation = {
37+
issuer = "https://your-oidc-issuer"
38+
subjects = [
39+
"system:serviceaccount:namespace1:service-account-1",
40+
"system:serviceaccount:namespace1:service-account-2",
41+
]
42+
}
43+
```
44+
45+
**Partial matching** - Use `startsWith()` to match multiple subjects with a common prefix. Note: The module doesn't use special syntax for this; instead, pass the prefix pattern as-is and it will be matched using CEL's `startsWith()` function:
46+
47+
```hcl
48+
workload_identity_federation = {
49+
issuer = "https://your-oidc-issuer"
50+
subjects = [
51+
"system:serviceaccount:namespace1:", # Matches all service accounts in namespace1
52+
]
53+
}
54+
```
55+
56+
This configuration will accept any subject that starts with `system:serviceaccount:namespace1:`, allowing all service accounts in that namespace to authenticate without listing each one individually.
57+
58+
**How it works:**
59+
- IAM binding grants access to the entire workload identity pool (`principalSet://iam.googleapis.com/.../pools/POOL_ID/*`)
60+
- Attribute conditions in the provider filter which tokens are accepted based on the `google.subject` claim
61+
- Subjects are evaluated as exact matches first, then partial matches via `startsWith()` checking
62+
2363
<!-- BEGIN_TF_DOCS -->
2464
## Requirements
2565

@@ -40,7 +80,7 @@ No modules.
4080
| [google_iam_workload_identity_pool_provider.meshstack](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider) | resource |
4181
| [google_project_iam_member.storage_admin](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
4282
| [google_service_account.buildingblock_storage_sa](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource |
43-
| [google_service_account_iam_member.workload_identity_binding](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource |
83+
| [google_service_account_iam_binding.workload_identity_binding](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_binding) | resource |
4484
| [google_service_account_key.buildingblock_storage_key](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_key) | resource |
4585

4686
## Inputs
@@ -49,7 +89,7 @@ No modules.
4989
|------|-------------|------|---------|:--------:|
5090
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | The GCP project ID | `string` | n/a | yes |
5191
| <a name="input_service_account_id"></a> [service\_account\_id](#input\_service\_account\_id) | The ID of the service account to create | `string` | `"buildingblock-storage-sa"` | no |
52-
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Configuration for workload identity federation | <pre>object({<br> workload_identity_pool_identifier = string // Identifier for the workload identity pool<br> audience = string // Audience for the OIDC tokens<br> issuer = string // OIDC issuer URL<br> subject = string // Subject for workload identity federation (e.g., system:serviceaccount:namespace:service-account-name)<br> subject_token_file_path = string // Path to the file containing the OIDC token<br> })</pre> | `null` | no |
92+
| <a name="input_workload_identity_federation"></a> [workload\_identity\_federation](#input\_workload\_identity\_federation) | Configuration for workload identity federation. Supports multiple subjects with exact matching and partial matching using startsWith(). | <pre>object({<br> workload_identity_pool_identifier = string // Identifier for the workload identity pool<br> audience = string // Audience for the OIDC tokens<br> issuer = string // OIDC issuer URL<br> subjects = list(string) // Subjects for workload identity federation - can use exact matches or startsWith patterns<br> subject_token_file_path = string // Path to the file containing the OIDC token<br> })</pre> | `null` | no |
5393

5494
## Outputs
5595

modules/gcp/storage-bucket/backplane/iam.tf

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,20 @@ resource "google_iam_workload_identity_pool_provider" "meshstack" {
1616
description = "OIDC identity provider for meshStack building blocks"
1717

1818
oidc {
19-
allowed_audiences = [var.workload_identity_federation.audience]
2019
issuer_uri = var.workload_identity_federation.issuer
20+
allowed_audiences = [var.workload_identity_federation.audience]
2121
}
2222

23+
# Map the OIDC token's `sub` claim to google.subject
2324
attribute_mapping = {
2425
"google.subject" = "assertion.sub"
2526
}
2627

27-
attribute_condition = "google.subject == '${var.workload_identity_federation.subject}'"
28+
# Restrict token acceptance to configured subjects
29+
attribute_condition = join(" || ", [
30+
for subject in var.workload_identity_federation.subjects :
31+
"google.subject.startsWith('${subject}')"
32+
])
2833
}
2934

3035
resource "google_service_account" "buildingblock_storage_sa" {
@@ -33,12 +38,13 @@ resource "google_service_account" "buildingblock_storage_sa" {
3338
description = "Service account for storage bucket building block"
3439
}
3540

36-
resource "google_service_account_iam_member" "workload_identity_binding" {
41+
resource "google_service_account_iam_binding" "workload_identity_binding" {
3742
count = var.workload_identity_federation == null ? 0 : 1
3843

3944
service_account_id = google_service_account.buildingblock_storage_sa.name
4045
role = "roles/iam.workloadIdentityUser"
41-
member = "principal://iam.googleapis.com/${google_iam_workload_identity_pool.meshstack[0].name}/subject/${var.workload_identity_federation.subject}"
46+
47+
members = ["principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.meshstack[0].name}/*"]
4248
}
4349

4450
resource "google_project_iam_member" "storage_admin" {

0 commit comments

Comments
 (0)