* Allow creation of dynamic tags * Extend project factory and related modules to support dynamic values * Extend folder and organization modules * project and organization readme * Simplify dynamic tag support and remove unnecessary restrictions • Schemas & Validations: Removed the restriction that forbade combining IAM fields with allowed_values_regex on tags. Updated validations in project and organization modules, and simplified all relevant JSON schemas. • Module Tag Bindings: Simplified the tag_value assignment in folder , project , gcs , bigquery-dataset , and kms modules by removing the defensive can(regex(...)) check and calling templatestring directly. • Outputs: Removed the tags_dynamic output from project and organization modules, as the same information is now available in tag_keys . • Project Factory: Updated tag_vars_projects in projects.tf to use the native namespaced_name attribute and filtered manually for dynamic tags. * fix(organization, project): fix linting and tests for dynamic tag support - Align allowed_values_regex and description extraction in _tags_merged locals to use lookup() for consistency with other fields. - Fix spacing in project context variable (alphabetical ordering). - Update organization tags test to include the new cost_center tag key with allowed_values_regex. - Update project tags test to include the new cost_center tag key and reflect the resolved allowed_values_regex on environment. * refactor(gcs): refine tag bindings and fix context test - Add _tag_bindings local to pre-resolve context references, enabling templatestring to receive a direct map reference (required by Terraform). - Use var.context.tag_vars instead of the non-existent local.ctx.tag_vars. - Fix HCL syntax in context.tfvars (escaped inner quotes). - Update context test inventory to reflect 3 tag bindings including a dynamic value resolved via templatestring. * refactor: align modules with tag binding context pattern - Add _tag_bindings local + templatestring dance to cloud-run-v2, compute-vm, folder, kms modules (bigquery-dataset already had it) - Exclude tag_vars from local.ctx in cloud-run-v2, compute-vm, folder, kms, project modules (bigquery-dataset already had it) - Add tag_vars to context variable in cloud-run-v2, compute-vm modules (others already had it) - Update all context tests with dynamic tag binding values using var.context.tag_vars * docs: add module-level tftest.yaml test instructions to GEMINI.md * docs: regenerate READMEs after tag-regex alignment - Regenerate variable tables in 7 module READMEs to reflect line number shifts from prior tag-regex changes - Add tag_vars exclusion to gcs ctx local - Fix whitespace alignment in iam-service-account and project-factory tag_vars blocks - Update tftest resource counts for organization and project - Remove tags_dynamic from organization/project output tables * fix(project-factory): update test inventory for tag_bindings module split - Move tag binding address from folder-2 to folder-2-iam in test inventory (tag_bindings moved from creation to IAM modules) - Update module instance count from 34 to 35 - Regenerate README tables after terraform fmt line shifts - Apply terraform fmt to variables.tf * refactor(project-factory): remove unnecessary depends_on from folder-iam modules Folder IAM modules depend on their own folder creation modules, not on module.projects. The explicit depends_on was leftover from an earlier design. * FAST stages * Address review comments. - FAST Stages: - Added tag_keys to output-files.tf in 0-org-setup to pass org tags via tfvars. - Sorted tag_keys and tag_values in output-files.tf. - Updated project-factory, networking, and security stages to use tag_keys. - Filtered tag_keys for dynamic tags only. - Modules: - Excluded tag_vars from local.ctx in iam-service-account and organization. - Simplified tag_value in iam-service-account. - Tests: - Updated test inventories for 0-org-setup and project-factory. * Fix tf format * Fix tfdoc * docs: add ADR for templatestring vars convention and update status of base path ADR * More tfdoc * Update schemas * Use endswith in context loop * Address review * Update FAST readmes * Update last modules * Terraform fmt * Revert alloydb * Fix whitespace --------- Co-authored-by: Ludovico Magnocavallo <ludo@qix.it>
Google Secret Manager
This module allows managing one or more secrets with versions and IAM bindings. For global secrets, this module optionally supports write-only attributes for versions, which do not save data in state. Write-only attributes are not yet supported in OpenTofu, so this module is only compatible with Terraform until OpenTofu support has been released.
- Global Secrets
- Regional Secrets
- IAM Bindings
- Secret Versions
- Context Interpolations
- Variables
- Outputs
- Requirements
- IAM
- APIs
Global Secrets
Secrets are created as global by default, with auto replication policy. For auto managed replication secrets the kms_key attribute can be used to configure CMEK via a global key.
To configure a secret for user managed replication configure the global_replica_locations attribute. Non-auto secrets ignore the kms_key attribute, but use each element of the locations map to configure keys.
module "secret-manager" {
source = "./fabric/modules/secret-manager"
project_id = var.project_id
secrets = {
test-auto = {}
test-auto-cmek = {
kms_key = "projects/test-0/locations/global/keyRings/test-g/cryptoKeys/sec"
}
test-user = {
global_replica_locations = {
europe-west1 = null
europe-west3 = null
}
}
test-user-cmek = {
global_replica_locations = {
europe-west1 = "projects/test-0/locations/europe-west1/keyRings/test-g/cryptoKeys/sec-ew1"
europe-west3 = "projects/test-0/locations/europe-west3/keyRings/test-g/cryptoKeys/sec-ew3"
}
}
}
}
# tftest modules=1 resources=4 inventory=secret.yaml skip-tofu
Regional Secrets
Regional secrets are identified by having the location attribute defined, and share the same interface with a few exceptions: the global_replica_locations is of course ignored, and versions only support a subset of attributes and can't use write-only attributes.
module "secret-manager" {
source = "./fabric/modules/secret-manager"
project_id = var.project_id
secrets = {
test = {
location = "europe-west1"
}
test-cmek = {
location = "europe-west1"
kms_key = "projects/test-0/locations/global/keyRings/test-g/cryptoKeys/sec"
}
}
}
# tftest modules=1 resources=2 inventory=secret-regional.yaml skip-tofu
IAM Bindings
This module supports the same IAM interface as all other modules in this repository. IAM bindings are defined per secret, if you need cross-secret IAM bindings use project-level ones.
module "secret-manager" {
source = "./fabric/modules/secret-manager"
project_id = var.project_id
secrets = {
test = {
iam = {
"roles/secretmanager.admin" = [
"user:test-0@example.com"
]
}
iam_bindings = {
test = {
role = "roles/secretmanager.secretAccessor"
members = [
"user:test-1@example.com"
]
condition = {
title = "Test."
expression = "resource.matchTag('1234567890/environment', 'test')"
}
}
}
iam_bindings_additive = {
test = {
role = "roles/secretmanager.viewer"
member = "user:test-2@example.com"
}
}
}
}
}
# tftest modules=1 resources=4 inventory=iam.yaml skip-tofu
Secret Versions
Versions are defined per secret via the versions attribute, and by default they accept string data which is stored in state. The data_config attributes allow configuring each secret:
data_config.is_fileinstructs the module to read version data from a file (datais then used as the file path)data_config.is_base64instructs the provider to treat data as Base64data_config.write_only_versioninstructs the module to use write-only attributes so that data is not set in state, each time the write-only version is changed data is reuploaded to the secret version
As mentioned before write-only attributes are only available for global secrets. Regional secrets still use the potentially insecure way of storing data.
module "secret-manager" {
source = "./fabric/modules/secret-manager"
project_id = var.project_id
secrets = {
test = {
versions = {
a = {
# potentially unsafe
data = "foo"
}
b = {
# potentially unsafe, reads from file
data = "test-data/secret-b.txt"
data_config = {
is_file = true
}
}
c = {
# uses safer write-only attribute
data = "bar"
data_config = {
# bump this version when data needs updating
write_only_version = 1
}
}
}
}
}
}
# tftest files=0 modules=1 resources=4 inventory=versions.yaml skip-tofu
foo-secret
# tftest-file id=0 path=test-data/secret-b.txt
Context Interpolations
Similarly to other core modules in this repository, this module also supports context-based interpolations, which are populated via the context variable.
This is a summary table of the available contexts, which can be used whenever an attribute expects the relevant information. Refer to the project factory module for more details on context replacements.
$custom_roles:my_role$iam_principals:my_principal$kms_keys:my_key$locations:my_location$project_ids:my_project$tag_keys:my_key$tag_values:my_value- custom template variables used in IAM conditions
This is a simple example that uses context interpolation.
module "secret-manager" {
source = "./fabric/modules/secret-manager"
context = {
iam_principals = {
mysa = "serviceAccount:test@foo-prod-test-0.iam.gserviceaccount.com"
myuser = "user:test@example.com"
}
kms_keys = {
primary = "projects/test-0/locations/europe-west1/keyRings/test-g/cryptoKeys/sec-ew1"
secondary = "projects/test-0/locations/europe-west3/keyRings/test-g/cryptoKeys/sec-ew3"
}
locations = {
primary = "europe-west1"
secondary = "europe-west3"
}
project_ids = {
test = "foo-prod-test-0"
}
}
project_id = "$project_ids:test"
secrets = {
test-user-cmek = {
global_replica_locations = {
"$locations:primary" = "$kms_keys:primary"
"$locations:secondary" = "$kms_keys:secondary"
}
iam = {
"roles/secretmanager.viewer" = [
"$iam_principals:mysa", "$iam_principals:myuser"
]
}
}
}
}
# tftest modules=1 resources=2 inventory=context.yaml skip-tofu
Variables
| name | description | type | required | default |
|---|---|---|---|---|
| project_id | Project id where the keyring will be created. | string |
✓ | |
| context | Context-specific interpolations. | object({…}) |
{} |
|
| project_number | Project number of var.project_id. Set this to avoid permadiffs when creating tag bindings. | string |
null |
|
| secrets | Map of secrets to manage. Defaults to global secrets unless region is set. | map(object({…})) |
{} |
Outputs
| name | description | sensitive |
|---|---|---|
| ids | Fully qualified secret ids. | |
| secrets | Secret resources. | |
| version_ids | Fully qualified version ids. | |
| version_versions | Version versions. | |
| versions | Version resources. | ✓ |
Requirements
These sections describe requirements for using this module.
IAM
The following roles must be used to provision the resources of this module:
- Cloud KMS Admin:
roles/cloudkms.adminor - Owner:
roles/owner
APIs
A project with the following APIs enabled must be used to host the resources of this module:
- Google Cloud Key Management Service:
cloudkms.googleapis.com