Files
hunfabric/modules/secret-manager/README.md
Julio Castillo 2eaa0d5e27 Add support for dynamic tags (#3897)
* 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>
2026-04-24 20:45:45 +00:00

8.4 KiB

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

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_file instructs the module to read version data from a file (data is then used as the file path)
  • data_config.is_base64 instructs the provider to treat data as Base64
  • data_config.write_only_version instructs 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.admin or
  • 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