fix(project-factory): Correctly interpolate IAM principals in tags (#3704)
* fix(project-factory): Correctly interpolate IAM principals in tags Moves the processing of `tags` and `tag_bindings` from the `projects` module instance to the `projects-iam` instance. This fixes a bug where IAM principals for automation service accounts, referenced via `$iam_principals:service_accounts/...`, were not being interpolated within `tags` IAM definitions. The `projects` module was called before the automation service account context was available, leading to the literal string being used instead of the service account email. Processing tags in the `projects-iam` module ensures the full context is available for interpolation. Adds new tests for both the `project` and `project-factory` modules to validate the fix. * fix(project-factory): Tag creation is now done in 2 steps. 1st step(projects): Creation of the tags without IAM bindings 2nd step(projects-iam): IAM bindings without creating the tags again That way we are more backwards compatible as tags and tags values are back to be under module.project-factory.module.projects["*"].google_tags_tag_* * fix(modules/project-factory): introduce fix suggested by @ludoo, fix logs * fix(modules/project-factory): fix linting --------- Co-authored-by: Ludovico Magnocavallo <ludomagno@google.com>
This commit is contained in:
@@ -47,6 +47,7 @@ The code is meant to be executed by a high level service account with powerful p
|
||||
- [Variables](#variables)
|
||||
- [Outputs](#outputs)
|
||||
- [Tests](#tests)
|
||||
- [Tags with $iam_principals interpolation](#tags-with-iam_principals-interpolation)
|
||||
<!-- END TOC -->
|
||||
|
||||
## Folder hierarchy
|
||||
@@ -927,3 +928,50 @@ services:
|
||||
- storage.googleapis.com
|
||||
# tftest-file id=test-2 path=data/projects/test-2.yaml
|
||||
```
|
||||
|
||||
### Tags with $iam_principals interpolation
|
||||
|
||||
This test validates that `$iam_principals:service_accounts/...` interpolation works correctly
|
||||
within tags IAM definitions when referencing automation service accounts created by the same
|
||||
project-factory.
|
||||
|
||||
```hcl
|
||||
module "project-factory" {
|
||||
source = "./fabric/modules/project-factory"
|
||||
data_defaults = {
|
||||
billing_account = "012345-67890A-ABCDEF"
|
||||
locations = {
|
||||
storage = "eu"
|
||||
}
|
||||
}
|
||||
data_overrides = {
|
||||
prefix = "test-pf"
|
||||
}
|
||||
factories_config = {
|
||||
projects = "data/projects"
|
||||
}
|
||||
}
|
||||
# tftest modules=5 resources=9 files=tags-iam-test inventory=tags_iam_principals_bug.yaml
|
||||
```
|
||||
|
||||
```yaml
|
||||
parent: folders/1234567890
|
||||
services:
|
||||
- resourcemanager.googleapis.com
|
||||
automation:
|
||||
project: test-pf-teams-iac-0
|
||||
service_accounts:
|
||||
rw:
|
||||
description: Read/write automation service account.
|
||||
tags:
|
||||
allow-key-creation:
|
||||
description: Allow key creation for automation service account
|
||||
values:
|
||||
allow:
|
||||
description: Allow key creation
|
||||
iam:
|
||||
roles/resourcemanager.tagUser:
|
||||
- $iam_principals:service_accounts/tags-iam-test/automation/rw
|
||||
# tftest-file id=tags-iam-test path=data/projects/tags-iam-test.yaml
|
||||
```
|
||||
|
||||
|
||||
@@ -133,7 +133,10 @@ module "projects" {
|
||||
tag_bindings = merge(
|
||||
each.value.tag_bindings, var.data_merges.tag_bindings
|
||||
)
|
||||
tags = each.value.tags
|
||||
tags = each.value.tags
|
||||
tags_config = {
|
||||
ignore_iam = true
|
||||
}
|
||||
universe = each.value.universe
|
||||
vpc_sc = each.value.vpc_sc
|
||||
workload_identity_pools = each.value.workload_identity_pools
|
||||
@@ -186,5 +189,9 @@ module "projects-iam" {
|
||||
)
|
||||
shared_vpc_host_config = each.value.shared_vpc_host_config
|
||||
shared_vpc_service_config = each.value.shared_vpc_service_config
|
||||
universe = each.value.universe
|
||||
tags = each.value.tags
|
||||
tags_config = {
|
||||
force_context_ids = true
|
||||
}
|
||||
universe = each.value.universe
|
||||
}
|
||||
|
||||
44
tests/modules/project/tags_iam_principals.tfvars
Normal file
44
tests/modules/project/tags_iam_principals.tfvars
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Test for $iam_principals interpolation within tags
|
||||
# This test demonstrates the bug where if a service account created by the same
|
||||
# project-factory is referenced via $iam_principals:service_accounts/... in tags,
|
||||
# the interpolation fails because the context doesn't have the key at the time
|
||||
# tags is processed (tags are processed in module "projects", but iam_principals
|
||||
# for the project's own service accounts are only added in module "projects-iam").
|
||||
|
||||
# Case 1: iam_principals key IS present in context - should work
|
||||
context = {
|
||||
iam_principals = {
|
||||
# Simulate service accounts created by project-factory with nested paths
|
||||
"service_accounts/my-project/automation/rw" = "serviceAccount:my-project-rw@my-project.iam.gserviceaccount.com"
|
||||
}
|
||||
}
|
||||
|
||||
tags = {
|
||||
allow-key-creation = {
|
||||
description = "Allow key creation for automation service account"
|
||||
values = {
|
||||
allow = {
|
||||
description = "Allow key creation"
|
||||
iam = {
|
||||
"roles/resourcemanager.tagUser" = [
|
||||
"$iam_principals:service_accounts/my-project/automation/rw"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
tests/modules/project/tags_iam_principals.yaml
Normal file
44
tests/modules/project/tags_iam_principals.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This test verifies that $iam_principals interpolation works correctly
|
||||
# in tags when using nested paths like service_accounts/project/automation/rw
|
||||
#
|
||||
# Expected: The member should resolve to the service account email from context
|
||||
|
||||
values:
|
||||
google_project.project[0]:
|
||||
name: my-project
|
||||
google_tags_tag_key.default["allow-key-creation"]:
|
||||
parent: projects/my-project
|
||||
purpose: null
|
||||
purpose_data: null
|
||||
short_name: allow-key-creation
|
||||
timeouts: null
|
||||
google_tags_tag_value.default["allow-key-creation/allow"]:
|
||||
short_name: allow
|
||||
timeouts: null
|
||||
google_tags_tag_value_iam_binding.default["allow-key-creation/allow:roles/resourcemanager.tagUser"]:
|
||||
condition: []
|
||||
members:
|
||||
# This is the expected behavior - the interpolation should resolve
|
||||
# to the actual service account email from context.iam_principals
|
||||
- serviceAccount:my-project-rw@my-project.iam.gserviceaccount.com
|
||||
role: roles/resourcemanager.tagUser
|
||||
|
||||
counts:
|
||||
google_project: 1
|
||||
google_tags_tag_key: 1
|
||||
google_tags_tag_value: 1
|
||||
google_tags_tag_value_iam_binding: 1
|
||||
@@ -31,4 +31,5 @@ tests:
|
||||
service_encryption_keys:
|
||||
service_agents:
|
||||
service_agents_universe:
|
||||
tags_iam_principals:
|
||||
universe:
|
||||
|
||||
@@ -438,7 +438,7 @@ values:
|
||||
description: My value 3
|
||||
short_name: my-value-2
|
||||
timeouts: null
|
||||
? module.project-factory.module.projects["dev-ta-app0-be"].google_tags_tag_value_iam_binding.default["my-tag-key-1/my-value-2:roles/resourcemanager.tagUser"]
|
||||
? module.project-factory.module.projects-iam["dev-ta-app0-be"].google_tags_tag_value_iam_binding.default["my-tag-key-1/my-value-2:roles/resourcemanager.tagUser"]
|
||||
: condition: []
|
||||
members:
|
||||
- user:user@example.com
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Test for $iam_principals interpolation bug in tags
|
||||
#
|
||||
# BUG DESCRIPTION:
|
||||
# When a project uses tags with IAM that references an automation service account
|
||||
# via $iam_principals:service_accounts/PROJECT/automation/SA, the interpolation
|
||||
# fails because:
|
||||
#
|
||||
# 1. In projects.tf, `module "projects"` (line ~78) processes `tags` but passes
|
||||
# a context that does NOT include the project's own automation service accounts
|
||||
#
|
||||
# 2. In projects.tf, `module "projects-iam"` (line ~141) has the correct context
|
||||
# with automation service accounts merged, but it does NOT process `tags`
|
||||
#
|
||||
# RESULT: The literal string "$iam_principals:service_accounts/..." is used
|
||||
# as the member instead of being interpolated to the actual service account email.
|
||||
#
|
||||
# This test expects the CORRECT behavior (interpolated value).
|
||||
# Currently, it will FAIL until the bug is fixed.
|
||||
|
||||
values:
|
||||
module.project-factory.module.projects["tags-iam-test"].google_project.project[0]:
|
||||
name: test-pf-tags-iam-test
|
||||
module.project-factory.module.projects["tags-iam-test"].google_tags_tag_key.default["allow-key-creation"]:
|
||||
description: Allow key creation for automation service account
|
||||
short_name: allow-key-creation
|
||||
module.project-factory.module.projects["tags-iam-test"].google_tags_tag_value.default["allow-key-creation/allow"]:
|
||||
description: Allow key creation
|
||||
short_name: allow
|
||||
? module.project-factory.module.projects-iam["tags-iam-test"].google_tags_tag_value_iam_binding.default["allow-key-creation/allow:roles/resourcemanager.tagUser"]
|
||||
: condition: []
|
||||
members:
|
||||
# EXPECTED: This should be the interpolated service account email
|
||||
# BUG: Currently this is the literal "$iam_principals:service_accounts/tags-iam-test/automation/rw"
|
||||
- serviceAccount:tags-iam-test-rw@test-pf-teams-iac-0.iam.gserviceaccount.com
|
||||
role: roles/resourcemanager.tagUser
|
||||
module.project-factory.module.automation-service-accounts["tags-iam-test/automation/rw"].google_service_account.service_account[0]:
|
||||
account_id: tags-iam-test-rw
|
||||
email: tags-iam-test-rw@test-pf-teams-iac-0.iam.gserviceaccount.com
|
||||
project: test-pf-teams-iac-0
|
||||
|
||||
counts:
|
||||
google_project: 1
|
||||
google_service_account: 1
|
||||
google_tags_tag_key: 1
|
||||
google_tags_tag_value: 1
|
||||
google_tags_tag_value_iam_binding: 1
|
||||
modules: 5
|
||||
Reference in New Issue
Block a user