# Terraform module reference: GCP

Generated reference for the GCP per-cluster building-block module. For the guided setup, see [Provision on GCP with Terraform](/deploy/provision-gcp).

### Requirements

| Name | Version |
| ---- | ------- |
| terraform | >= 1.14 |
| google | ~> 7.42.0 |
| google-beta | ~> 7.42.0 |
| helm | ~> 3.0 |
| kubernetes | ~> 3.0 |
| random | ~> 3.0 |
| time | ~> 0.13 |

### Providers

| Name | Version |
| ---- | ------- |
| google | 7.42.0 |
| kubernetes | 3.2.1 |
| random | 3.9.0 |
| time | 0.14.0 |

### Modules

| Name | Source | Version |
| ---- | ------ | ------- |
| cloud\_nat | terraform-google-modules/cloud-nat/google | 7.0.0 |
| cloud\_sql | GoogleCloudPlatform/sql-db/google//modules/postgresql | 28.2.0 |
| keystore\_service\_account | terraform-google-modules/service-accounts/google | 4.7.0 |
| network | ./network-gcp | n/a |
| node\_service\_account | terraform-google-modules/service-accounts/google | 4.7.0 |
| orchestrator\_service\_account | terraform-google-modules/service-accounts/google | 4.7.0 |
| policy\_reader\_service\_account | terraform-google-modules/service-accounts/google | 4.7.0 |
| policy\_writer\_service\_account | terraform-google-modules/service-accounts/google | 4.7.0 |
| secrets | GoogleCloudPlatform/secret-manager/google | 0.9.0 |

### Resources

| Name | Type |
| ---- | ---- |
| [google\_compute\_address.shard\_ilb](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_address) | resource |
| [google\_compute\_global\_address.psa](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_global_address) | resource |
| [google\_container\_cluster.primary](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster) | resource |
| [google\_container\_node\_pool.confidential](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool) | resource |
| [google\_container\_node\_pool.general](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool) | resource |
| [google\_kms\_crypto\_key.keystore](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/kms_crypto_key) | resource |
| [google\_kms\_crypto\_key\_iam\_member.keystore](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/kms_crypto_key_iam_member) | resource |
| [google\_kms\_crypto\_key\_iam\_member.pd\_cmek](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/kms_crypto_key_iam_member) | resource |
| [google\_kms\_key\_ring.keystore](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/kms_key_ring) | resource |
| [google\_project\_iam\_member.cloud\_sql\_client](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google\_project\_iam\_member.cloud\_sql\_instance\_user](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google\_project\_iam\_member.keystore\_kms\_random\_bytes](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google\_project\_iam\_member.node](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/project_iam_member) | resource |
| [google\_service\_account\_iam\_member.keystore\_wi](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource |
| [google\_service\_account\_iam\_member.orchestrator\_wi](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource |
| [google\_service\_account\_iam\_member.policy\_reader\_wi](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource |
| [google\_service\_account\_iam\_member.policy\_writer\_wi](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource |
| [google\_service\_networking\_connection.psa](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_networking_connection) | resource |
| [kubernetes\_config\_map\_v1.db\_bootstrap\_script](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/config_map_v1) | resource |
| [kubernetes\_job\_v1.db\_bootstrap](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/job_v1) | resource |
| [kubernetes\_namespace\_v1.crypto\_vault](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace_v1) | resource |
| [kubernetes\_secret\_v1.artifactory\_dockerconfig](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
| [kubernetes\_secret\_v1.db\_bootstrap\_admin](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
| [kubernetes\_secret\_v1.ui\_google\_oauth\_client\_secret](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource |
| [kubernetes\_service\_account\_v1.db\_bootstrap](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service_account_v1) | resource |
| [kubernetes\_service\_v1.shard\_ilb](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/service_v1) | resource |
| [kubernetes\_storage\_class\_v1.pd](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/storage_class_v1) | resource |
| [random\_password.db\_admin](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
| [random\_password.db\_app](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
| [time\_sleep.wait\_cluster\_access](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
| [time\_sleep.wait\_internal\_lb\_discovery](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource |
| [google\_compute\_forwarding\_rules.shard](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/compute_forwarding_rules) | data source |
| [google\_project.pd\_cmek\_consumer](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/project) | data source |

### Inputs

| Name | Description | Type | Default | Required |
| ---- | ----------- | ---- | ------- | :------: |
| app\_name | Base name for GCP resources (network, GKE cluster, Cloud SQL instance, KMS key ring, service accounts). Acts as a prefix and DNS-safe label. Typically distinct per cluster, e.g. "crypto-vault-0" / "-1" / "-2". Every generated GSA account ID must fit GCP's 30-character limit, and each Cloud SQL IAM username (`<account-id>@<project-id>.iam`) must fit PostgreSQL's 63-byte identifier limit; for the current longest suffixes this means `length(app_name) + length(gcp_project) <= 49`. | `string` | n/a | yes |
| artifactory\_password | JFrog Artifactory password / access token. | `string` | n/a | yes |
| artifactory\_username | JFrog Artifactory username for pulling the chart's private images. | `string` | n/a | yes |
| gcp\_project | GCP project ID that owns every resource in this cluster invocation (GKE, Cloud KMS, Cloud SQL, Secret Manager, the VPC). One project per shard mirrors the AWS one-sub-account-per-shard trust boundary. It is part of the chart's canonical fully-qualified `gcp.kmsKey` resource name. | `string` | n/a | yes |
| gcp\_region | GCP region for the regional GKE cluster and all regional resources (subnet, Cloud Router/NAT, Cloud SQL, internal load balancers, regional Cloud KMS key ring). A regional cluster spreads its node pools across the region's zones for zonal HA. | `string` | n/a | yes |
| mpc\_key\_store | THIS cluster's MPC keystore shard identity. Only the local shard is configured here — remote shards are wired by URL via `remote_mpc_key_stores`, so a cluster's Terraform never carries another shard's key material. NOTE: the MPC keypair is self-generated by the keystore on first boot (persisted write-once in its Postgres) and the peer set is propagated by the orchestrator — neither is configured here. | <pre>object(\{<br />    name                     = string<br />    backupEncryptorPublicKey = string<br />    importedKeyEncryptorPem  = optional(string, "")<br />  })</pre> | n/a | yes |
| pods\_secondary\_range\_cidr | REQUIRED VPC-native alias-IP range for Pods (the subnet secondary range named exactly "pods"). Size it for the whole cluster's pod population across every node pool — GKE carves a /24 per node by default, so a /16 supports ~256 nodes. No AWS analog; must not overlap subnet\_cidr, services\_secondary\_range\_cidr, or any peer VPC. | `string` | n/a | yes |
| policy\_server | THIS cluster's policy-server identity. Only the local shard is configured here — remote shards' readers/writers are wired by URL via `remote_policy_servers`. The reader/writer identity keypairs are self-generated on first boot and their verifying keys are propagated via the cluster-identity set at bootstrap — nothing key-related is configured here. | <pre>object(\{<br />    writerName = string<br />    readerName = string<br />    # ES256 (P-256 ECDSA) private key in PKCS#8 PEM format. Used by the<br />    # reader to sign requests forwarded to an external rule server. Required<br />    # by the policy-server binary (parsed via policy\_engine::SigningKey::es256).<br />    externalRuleServerSigningKey = optional(string, "")<br />  })</pre> | n/a | yes |
| relay\_api\_key | API key the MPC keystores present to Relay. | `string` | n/a | yes |
| services\_secondary\_range\_cidr | REQUIRED VPC-native alias-IP range for Kubernetes Services / ClusterIPs (the subnet secondary range named exactly "services"). No AWS analog; must not overlap subnet\_cidr, pods\_secondary\_range\_cidr, or any peer VPC. | `string` | n/a | yes |
| shard\_index | Zero-based index of this cluster's MPC shard (0/1/2 in the three-cluster deployment). Drives the shard-global component names (`mpc-ks-<index>`, `policy-server-reader-<index>`, `policy_db_<index>`) that must agree across clusters, and labels per-cluster resources. | `number` | n/a | yes |
| app\_image\_pull\_policy | imagePullPolicy for the orchestrator/policy-server/UI containers (sets the chart-wide imagePullPolicy). null keeps the chart default (IfNotPresent); set "Always" for a mutable tag like latest. | `string` | `null` | no |
| azure\_oauth\_client\_id | Entra ID (Azure AD) client ID for the vault UI. | `string` | `""` | no |
| azure\_tenant\_id | Entra ID tenant ID for the vault UI. | `string` | `""` | no |
| bootstrap\_approval\_threshold | Policy-server signature quorum. The deployment is hard-pinned to 1. | `number` | `1` | no |
| confidential\_node\_machine\_type | N2D machine type for the SEV-SNP Confidential-Node pool that runs only MPC keystore pods (tainted + `cloud.google.com/gke-confidential-nodes` label). Defaults to n2d-standard-4 (4 vCPU / 16 GiB), the confidential-capable counterpart of the AWS c5.xlarge keystore node. | `string` | `"n2d-standard-4"` | no |
| confidential\_node\_max\_count | Maximum nodes TOTAL across the regional Confidential-Node pool. | `number` | `3` | no |
| confidential\_node\_min\_count | Minimum nodes TOTAL across the regional Confidential-Node pool. Defaults to 2, matching the HA keystore replica count and the AWS floor of two Nitro nodes per cluster; GKE distributes the total across zones with location\_policy=BALANCED. | `number` | `2` | no |
| database\_flags | Extra Cloud SQL database flags (name → value) merged into the instance settings, on top of the module-managed `cloudsql.iam_authentication` flag (set automatically from db\_use\_iam\_auth). Use this for logging/tuning flags (e.g. log\_min\_duration\_statement) — the GCP counterpart of the AWS db\_cloudwatch\_logs\_exports, since Cloud SQL ships logs to Cloud Logging automatically and log verbosity is flag-driven. | `map(string)` | `{}` | no |
| db\_app\_username | Generated-password compatibility login and database owner used only when db\_use\_iam\_auth=false. On the default IAM path each workload connects under its own GSA-derived Cloud SQL IAM username, but this value remains a known migration source for auth-mode changes. Renaming it on an existing stack requires a maintenance-window manual migration: record the old value before changing it, then after Terraform provisions the new role and before workloads resume, REASSIGN and DROP OWNED the recorded old role in every application database. Terraform cannot infer the former username after this input changes. | `string` | `"crypto_vault"` | no |
| db\_availability\_type | Cloud SQL availability: "REGIONAL" (default — synchronous standby in another zone, the Multi-AZ equivalent of the AWS db\_multi\_az=true) or "ZONAL" (single zone, cheaper, no automatic failover). | `string` | `"REGIONAL"` | no |
| db\_backup\_enabled | Enable Cloud SQL automated backups (with point-in-time recovery / transaction-log retention). Analog of a non-zero AWS db\_backup\_retention\_period. | `bool` | `true` | no |
| db\_backup\_retention\_count | Number of automated backups Cloud SQL retains (backup\_retention\_settings.retained\_backups, retention\_unit COUNT). Analog of the AWS db\_backup\_retention\_period (which is day-based; Cloud SQL retains a COUNT of backups). | `number` | `7` | no |
| db\_backup\_start\_time | Daily UTC start time for the Cloud SQL automated-backup window, "HH:MM". Analog of the AWS db\_backup\_window (Cloud SQL manages the maintenance window separately, see db\_maintenance\_window). | `string` | `"03:00"` | no |
| db\_deletion\_protection | Enable Cloud SQL deletion protection (both the instance-level flag and the Terraform-resource deletion\_protection guard). Should be true in production; set false for throwaway/CI stacks so repeated create/destroy cycles work without manual intervention. Final-backup behavior is controlled separately by db\_final\_backup\_enabled and db\_retain\_backups\_on\_delete, matching AWS's separate deletion-protection and final-snapshot controls. | `bool` | `true` | no |
| db\_disk\_autoresize\_limit | Upper bound (GB) for Cloud SQL automatic storage growth (disk\_autoresize is enabled with this limit). 0 = no limit. Analog of the AWS db\_max\_allocated\_storage. | `number` | `100` | no |
| db\_disk\_size | Initial data-disk size (GB) for the Cloud SQL instance. Analog of the AWS db\_allocated\_storage. | `number` | `20` | no |
| db\_edition | Cloud SQL edition: ENTERPRISE (default — required by the db-custom-\* tiers) or ENTERPRISE\_PLUS. Must be consistent with db\_tier: db-custom-\* tiers are only valid under ENTERPRISE. | `string` | `"ENTERPRISE"` | no |
| db\_final\_backup\_enabled | Create a final Cloud SQL backup when the instance is deleted. Defaults to true, matching the AWS module's production-safe final-snapshot posture. Set false for throwaway/CI stacks to avoid retaining orphaned backups. | `bool` | `true` | no |
| db\_final\_backup\_retention\_days | Days to retain the final Cloud SQL backup after instance deletion (1-365). Used when db\_final\_backup\_enabled is true. | `number` | `30` | no |
| db\_maintenance\_window | Weekly maintenance window for the Cloud SQL instance: day (1 = Monday .. 7 = Sunday) and hour (0-23, UTC). Analog of the AWS db\_maintenance\_window (reshaped to Cloud SQL's day/hour form). | <pre>object(\{<br />    day  = number<br />    hour = number<br />  })</pre> | <pre>\{<br />  "day": 1,<br />  "hour": 4<br />}</pre> | no |
| db\_psa\_cidr | CIDR of the Private Service Access range Cloud SQL draws its private IP from (the reserved global address in database.tf), e.g. "10.0.96.0/19". null (default) lets Google auto-allocate any free RFC-1918 /16 in the VPC — the size this module has always reserved, so an existing deployment is never forced to replace its range (which would rebuild the instance's private IP). PIN THIS IN ANY MULTI-VPC DEPLOYMENT: the PSA reservation happens BEFORE the hub\<->spoke peerings exist, so it cannot see the peers' ranges — an auto-allocated /16 can land squarely on a sibling cluster's whole IP plan, and VPC peering then refuses the pairing because the subnet routes overlap (the `peering-gcp` module fails, after Cloud SQL is already built on the bad range). The example pins one disjoint range per cluster; see its local.networks IP plan for the shape. Service Networking shares one allocated range across every PSA-backed service in the VPC, and Cloud SQL requires at least a /24, so do not size it below what the instances actually need. | `string` | `null` | no |
| db\_query\_insights\_enabled | Enable Cloud SQL Query Insights (insights\_config.query\_insights\_enabled). Analog of the AWS db\_performance\_insights\_enabled. | `bool` | `true` | no |
| db\_retain\_backups\_on\_delete | Retain Cloud SQL automated and on-demand backups after instance deletion. Defaults to true for recoverability; set false for throwaway/CI stacks to avoid retained backup cost. Automated backups remain subject to db\_backup\_retention\_count. | `bool` | `true` | no |
| db\_ssl\_mode | Cloud SQL INSTANCE connection SSL policy (ip\_configuration.ssl\_mode): ENCRYPTED\_ONLY (default — reject plaintext) or ALLOW\_UNENCRYPTED\_AND\_ENCRYPTED. Analog of the AWS db\_force\_ssl. This governs the instance's own policy; the Cloud SQL Auth Proxy always speaks TLS to the instance, so the chart's per-component sslMode stays "" regardless. | `string` | `"ENCRYPTED_ONLY"` | no |
| db\_tier | Cloud SQL machine tier for the per-cluster PostgreSQL instance (analog of the AWS db.t3.small). Defaults to db-custom-1-3840 (1 vCPU / 3.75 GiB) — a small dedicated-core tier that supports REGIONAL (Multi-AZ-equivalent) availability. NOTE: db-custom-\* tiers require db\_edition = ENTERPRISE; shared-core tiers (db-g1-small / db-f1-micro) are cheaper but do NOT support high availability. | `string` | `"db-custom-1-3840"` | no |
| db\_use\_iam\_auth | Use Cloud SQL IAM database authentication for application workloads (default, true). On the IAM path each DB-using pod carries a Cloud SQL Auth Proxy sidecar (--auto-iam-authn) under that workload's GSA-derived IAM username with an EMPTY password; Terraform creates a distinct CLOUD\_IAM\_SERVICE\_ACCOUNT `google_sql_user` for keystore, policy reader/writer, and the hub orchestrator. Set false to fall back to one generated-password `google_sql_user` (mirrors the AWS RDS master-secret pattern). The bootstrap automatically migrates ownership in either auth-mode direction when db\_app\_username is unchanged. Either way the built-in `postgres` admin (Secret-Manager-stored generated password) is used only by the minimal provisioning Job for database creation, ownership migration, and grants. | `bool` | `true` | no |
| db\_version | PostgreSQL database version for the Cloud SQL instance, in the provider's enum form (e.g. "POSTGRES\_17"). Analog of the AWS db\_engine\_version "17". | `string` | `"POSTGRES_17"` | no |
| general\_node\_machine\_type | Machine type for the general (untainted, non-confidential) node pool that runs everything except the keystore pods — orchestrator, policy-server, NATS, relay, UI, controllers. Defaults to e2-standard-4 (4 vCPU / 16 GiB): the orchestrator and relay-router are each CPU-heavy on the signing hot path and want ~2 cores apiece, so 4 vCPU gives the hot-path pods room and reduces node sprawl (parity with the AWS m5.xlarge general default). | `string` | `"e2-standard-4"` | no |
| general\_node\_max\_count | Maximum nodes PER ZONE in the regional general node pool (cluster-wide ceiling = this times the zone count). | `number` | `6` | no |
| general\_node\_min\_count | Minimum nodes PER ZONE in the regional general node pool. The pool spans the region's zones, so the cluster-wide floor is this times the zone count (default 1 → ~3 nodes across 3 zones) — this keeps ≥1 schedulable node in every zone for zonal Persistent Disks (NATS JetStream), the GKE analog of the AWS per-AZ node-group split. | `number` | `1` | no |
| gke\_deletion\_protection | Enable the GKE cluster's Terraform-resource deletion\_protection guard, which makes `terraform destroy` refuse to delete the cluster (and every workload, node pool and PVC on it). Should be true in production; set false for throwaway/CI stacks so repeated create/destroy cycles work without manual intervention. The Cloud SQL counterpart is db\_deletion\_protection — leaving EITHER on wedges an unattended teardown. | `bool` | `true` | no |
| gke\_endpoint\_public\_access | Expose the GKE API server's public endpoint. The private endpoint always exists. Defaults to false, matching the AWS module's production-safe private-only control plane; Terraform must then run in the VPC, a peered VPC, or over VPN. Set true only for an apply-from-outside PoC/CI path and restrict master\_authorized\_networks. | `bool` | `false` | no |
| gke\_node\_pool\_delete\_timeout | Timeout supplied to each GKE node-pool deletion phase. The 2h default preserves the pinned Google provider's behavior; short-lived CI stacks may lower it so an unstable managed instance group fails within the harness's bounded retry budget. | `string` | `"2h"` | no |
| gke\_version | GKE version target. Leave "" (default) to track the chosen release\_channel and receive auto-upgrades. With RAPID/REGULAR/STABLE/EXTENDED, a concrete value is only the control-plane floor and node pools continue following the channel. For optional fixed-version mode, pair a full version with release\_channel=UNSPECIFIED; this pins both node pools and disables their auto-upgrade. | `string` | `""` | no |
| google\_admin\_emails | Initial Setup Admin Google emails. Becomes the chart's `googleAuth.adminEmails`, which the policy-server-writer renders into `[[initial_admins]]` entries (the live setup-admin enrollment path). | `list(string)` | `[]` | no |
| google\_oauth\_client\_id | Google OAuth client ID used by the vault UI and policy-server initial-admin matching. Hub-side; ignored on spoke clusters. | `string` | `""` | no |
| helm\_release\_name | Helm release name the operator will use for `helm install`. Terraform pre-creates internal LoadBalancer Services whose Pod selectors include this release instance label, so the operator MUST install with this exact release name. | `string` | `"crypto-vault"` | no |
| ilb\_allow\_global\_access | Whether GKE's internal LoadBalancer Services accept clients from other regions. Keep enabled for cross-region PSC or peered hub-and-spoke deployments. | `bool` | `true` | no |
| image\_tags | Image tags for each component. The chart pins all images via the chart version; setting these forces specific tags (typically left null in production). | <pre>object(\{<br />    mpc\_key\_store = optional(string, null)<br />    policy\_server = optional(string, null)<br />    orchestrator  = optional(string, null)<br />    ui            = optional(string, null)<br />  })</pre> | `{}` | no |
| internal\_lb\_source\_cidrs | Exact client CIDRs allowed to reach each enabled Terraform-managed internal LoadBalancer Service, keyed by role (ks, pol\_reader, pol\_writer, nats, relay). The combined example passes the matching PSC producer NAT subnet for each role; health-check ranges are added internally. | `map(list(string))` | `{}` | no |
| is\_hub | Whether this cluster runs the hub-cluster services (orchestrator, NATS, vault UI) in addition to the per-cluster KS+POL. In the three-cluster deployment exactly one module invocation sets this to true. | `bool` | `false` | no |
| kms\_deletion\_protection | Protect the keystore Cloud KMS key from Terraform deletion by setting its provider-native deletion\_policy to PREVENT. Defaults to true for production. Set false when CREATING a throwaway/CI stack. For an existing protected stack, set false and apply once before destroy; Cloud KMS still only schedules key versions for destruction and permanently reserves the key/key-ring names. | `bool` | `true` | no |
| kms\_key\_protection\_level | Where the keystore crypto key's material lives: "HSM" (default — generated in, and never leaving, a FIPS 140-2 Level 3 Cloud HSM, the posture of the AWS sibling's aws\_kms\_key) or "SOFTWARE" (the upstream kms module's own default — cheaper per key version, but the material is handled in software). ⚠️ CHANGING THIS ON AN EXISTING DEPLOYMENT REPLACES THE CRYPTO KEY: protection\_level lives in the key's version\_template, which the API cannot update in place, so Terraform destroys and recreates the key — and every MPC key share already wrapped under the old one becomes permanently unrecoverable. Choose before the first apply. Cloud HSM is not offered in every KMS location, so confirm gcp\_region supports it — a region without HSM fails the key create. | `string` | `"HSM"` | no |
| ks\_internal\_lb | Whether to expose THIS cluster's local unified keystore through a Terraform-owned, GKE-managed internal LoadBalancer Service so a remote hub orchestrator can dial it over HTTP. Typically set on spokes. The TF caller layers a PSC service attachment on top and feeds the endpoint back into the hub via `remote_mpc_key_stores`. | <pre>object(\{<br />    enabled = bool<br />  })</pre> | <pre>\{<br />  "enabled": false<br />}</pre> | no |
| kubernetes\_namespace | Kubernetes namespace into which the crypto-vault chart is installed. Used by the Workload-Identity trust binding for the MPC keystore service account (the KSA lives here). | `string` | `"crypto-vault"` | no |
| labels | Extra GCP resource labels merged into every resource that supports labeling. GCP labels are stricter than AWS tags: keys and values must be lowercase letters, digits, hyphens or underscores (the module lowercases its own derived labels to match). | `map(string)` | `{}` | no |
| master\_authorized\_networks | CIDR blocks authorized to reach the GKE control plane. Analog of the AWS eks\_endpoint\_public\_access\_cidrs. This allowlist does not turn on the public endpoint; gke\_endpoint\_public\_access must also be true. Defaults to empty for the private-only production posture. | <pre>list(object(\{<br />    cidr\_block   = string<br />    display\_name = optional(string, "")<br />  }))</pre> | `[]` | no |
| master\_ipv4\_cidr\_block | RFC-1918 /28 range for the GKE control-plane's private endpoint (the Google-managed peered VPC that hosts the API server). Required for a private cluster; must not overlap subnet\_cidr, the secondary ranges, or any peer VPC. GKE mandates a /28. | `string` | `"172.16.0.0/28"` | no |
| mpc\_key\_store\_image\_repository | Container image repository for the non-AWS MPC keystore build used by GCP. Rendered into the chart's `mpcKeyStoreImage.nonAws.repository`. | `string` | `"repo.sodot.dev/sodot-docker-images/mpc-key-store"` | no |
| mpc\_key\_store\_image\_tag | Tag for the non-AWS MPC keystore build used by GCP (rendered into `mpcKeyStoreImage.nonAws.tag`). | `string` | `"0.4.0"` | no |
| name\_nonce | Optional short suffix appended to ONLY the GCP name families that stay RESERVED after deletion: the Cloud SQL instance (`<app_name>-postgres-<nonce>`) and the Cloud KMS key ring + crypto key (`<app_name>-keystore-<nonce>` / `<app_name>-storage-encryptor-<nonce>`). Cloud SQL holds a deleted instance ID for up to a week; Cloud KMS holds a key-ring/key name FOREVER (destroy only schedules the key VERSIONS for destruction). Everything else derived from app\_name — the GKE cluster, VPC/subnet, the three service accounts, secrets, internal LBs — is freely re-creatable and deliberately keeps its clean, deterministic name. EMPTY (the default) appends NOTHING: the three names are byte-identical to what this module produced before this variable existed, so an existing deployment sees no diff — which matters because a name change on any of them is a RESOURCE REPLACEMENT, and for the KMS key an unrecoverable one. Set it only on a throwaway/CI stack that creates and destroys the same app\_name repeatedly. | `string` | `""` | no |
| nats\_box\_enabled | Deploy the nats-box debug/CLI utility pod (for running `nats` commands against the cluster). Off by default — it has no runtime role and is just extra surface in production; enable it for an ops/debug session. Wires the chart's `nats.natsBox.enabled`. | `bool` | `false` | no |
| nats\_hub\_url | NATS connection URL the spoke cluster's KS+POL use to reach the hub. Required when `is_hub = false`. Resolved through the customer's private cross-cluster network (PSC / VPC peering / VPN). | `string` | `""` | no |
| nats\_internal\_lb | Hub-only: create a Terraform-owned, GKE-managed internal LoadBalancer Service in front of the hub's NATS. The TF caller then layers PSC (a service attachment on the discovered forwarding rule) or relies on direct VPC-peering routing. REQUIRED on the hub; ignored on spokes. | <pre>object(\{<br />    enabled = bool<br />  })</pre> | <pre>\{<br />  "enabled": false<br />}</pre> | no |
| observability | OpenTelemetry/OTLP export for the NON-enclave services (orchestrator and<br />policy-server reader+writer). They run as ordinary pods and speak OTLP<br />DIRECTLY to your collector — no sidecar. (On GCP the keystore also runs as an<br />ordinary pod on a Confidential node; unlike the AWS in-enclave keystore it has<br />no separate OTLP-exporter sidecar boundary.)<br /><br />Export is OFF until you set otlp\_endpoint; until then these services log only<br />to their container stdout. The transport's standard endpoint handling applies<br />(grpc: verbatim; http/protobuf: /v1/logs appended), and the URL scheme selects<br />TLS (https:// negotiates TLS, http:// stays plaintext).<br /><br />Auth headers (a bearer token, typically) come from a Secret YOU create in the<br />release namespace, never from a ConfigMap: set otlp\_headers\_secret\_name +<br />otlp\_headers\_secret\_key to the Secret and the key holding the header value,<br />taken verbatim as a comma-separated key=value list (e.g. "authorization=Bearer<br />abc123"). Leave the name empty to send no auth. Credentials require an https://<br />endpoint — the app refuses to send them over plaintext.<br /><br />RUST\_LOG sets only the stdout filter; use otlp\_log\_filter (VAULT\_OTLP\_LOG\_FILTER)<br />to tune the verbosity of the logs exported over OTLP. | <pre>object(\{<br />    otlp\_endpoint            = optional(string, "")<br />    otlp\_protocol            = optional(string, "grpc") # "grpc" or "http/protobuf"<br />    otlp\_console             = optional(bool, true)     # also keep logging to stdout<br />    otlp\_timeout             = optional(string, "")     # OTEL\_EXPORTER\_OTLP\_TIMEOUT (ms), e.g. "10000"; empty = SDK default<br />    otlp\_resource\_attributes = optional(string, "")     # OTEL\_RESOURCE\_ATTRIBUTES, comma-separated key=value<br />    otlp\_log\_filter          = optional(string, "")     # VAULT\_OTLP\_LOG\_FILTER, tracing EnvFilter syntax; empty = app default. RUST\_LOG sets only the stdout filter.<br />    otlp\_headers\_secret\_name = optional(string, "")     # existing Secret (empty = no auth headers)<br />    otlp\_headers\_secret\_key  = optional(string, "")     # key within that Secret holding the header string<br />  })</pre> | `{}` | no |
| orchestrator\_image\_repository | Orchestrator image repository. Defaults to the sodot-docker-oci production registry; takes effect when image\_tags.orchestrator is set. | `string` | `"repo.sodot.dev/sodot-docker-oci/orchestrator"` | no |
| orchestrator\_ingress | Hub-only public ingress for the orchestrator REST API, via a GKE Ingress (className "gce") with an optional Google-managed TLS certificate. When enabled the module renders the orchestrator's `ingress.{enabled,className,host}` (+ managed cert). Set `host` to a DNS name the customer owns and resolves to the Ingress IP. | <pre>object(\{<br />    enabled     = bool<br />    host        = string<br />    class\_name  = optional(string, "gce")<br />    annotations = optional(map(string), \{})<br />    # TLS via a BYO Kubernetes Secret (mutually exclusive with managed\_certificate,<br />    # which wins when both are set — see locals.tf). Leave both defaulted for the<br />    # Google-managed-cert path.<br />    tls\_enabled = optional(bool, false)<br />    tls\_secret  = optional(string, "")<br />    # Provision a Google-managed TLS certificate for `host` (the default, GCP-native<br />    # front-door path). Renders `gcpManagedCertificate.enabled` in the chart values.<br />    managed\_certificate = optional(bool, true)<br />    # Name of a reserved global static IP (google\_compute\_global\_address) to pin the<br />    # Ingress to. Mapped to the `kubernetes.io/ingress.global-static-ip-name`<br />    # annotation in locals.tf so the front-door IP + Cloud DNS record + managed cert<br />    # can all bind to a stable address. Empty ("") lets GKE allocate an ephemeral IP.<br />    global\_static\_ip\_name = optional(string, "")<br />  })</pre> | <pre>\{<br />  "enabled": false,<br />  "host": ""<br />}</pre> | no |
| pd\_cmek\_key | Optional customer-managed encryption key (CMEK) for Persistent Disks provisioned by the module's dedicated NATS StorageClass, as a full key resource ID (projects/\<p>/locations/\<l>/keyRings/\<r>/cryptoKeys/\<k>). Empty (default) uses Google-managed encryption. When set, the module grants this cluster project's Compute Engine service agent key-scoped Encrypter/Decrypter access; the Terraform caller therefore needs permission to change that key's IAM policy, including for a key in another project. This is the storage-class CMEK counterpart of the AWS storage\_class\_encrypted (which was a plain boolean over EBS default encryption). | `string` | `""` | no |
| pol\_internal\_lb | Whether to expose THIS cluster's local policy-server reader and writer through Terraform-owned, GKE-managed internal LoadBalancer Services so a remote hub orchestrator can dial them over HTTP. Typically set on spokes alongside `ks_internal_lb`. | <pre>object(\{<br />    enabled = bool<br />  })</pre> | <pre>\{<br />  "enabled": false<br />}</pre> | no |
| policy\_server\_image\_repository | Policy-server (reader + writer) image repository. Defaults to the sodot-docker-oci production registry; takes effect when image\_tags.policy\_server is set. | `string` | `"repo.sodot.dev/sodot-docker-oci/policy-server"` | no |
| private\_cluster | Give the GKE nodes private (RFC-1918-only) IPs with egress via Cloud NAT — the production-safe default (true), mirroring the AWS private-node posture. This controls node addresses only; gke\_endpoint\_public\_access independently controls the API-server endpoint. Set false only for a throwaway cluster that wants public node IPs. | `bool` | `true` | no |
| relay\_address | Relay service address used by MPC keystores + orchestrator. Required on spokes (typically the hub's relay PSC endpoint URL). Ignored on the hub: when `is_hub = true` the module derives the in-cluster URL `http://relay-router:80` because the relay subchart runs in-namespace there. Format: "http://\<host>:\<port>" or "https://\<host>". | `string` | `""` | no |
| relay\_egress\_cidrs | Optional GCP NetworkPolicy destination CIDRs for the relay on spoke clusters. When relay\_address contains a literal IPv4 PSC endpoint, the module derives its /32 automatically; set this explicitly when relay\_address uses private DNS. Ignored on the hub, whose relay is selected by pod labels. | `list(string)` | `[]` | no |
| relay\_internal\_lb | Hub-only: create a Terraform-owned, GKE-managed internal LoadBalancer Service in front of the relay subchart's relay-router; the TF caller then layers a PSC service attachment on it so spoke clusters reach the relay over the private network. REQUIRED on the hub. | <pre>object(\{<br />    enabled = bool<br />  })</pre> | <pre>\{<br />  "enabled": false<br />}</pre> | no |
| release\_channel | GKE release channel governing available versions and auto-upgrade cadence: RAPID (weekly), REGULAR (default, multiple/month), STABLE (every few months), EXTENDED (up to 24-month minor support), or UNSPECIFIED. Use UNSPECIFIED with gke\_version only for optional fixed-version mode; it disables node auto-upgrades. | `string` | `"REGULAR"` | no |
| remote\_mpc\_key\_store\_egress\_cidrs | Optional hub orchestrator NetworkPolicy destinations for remote keystore PSC endpoints. Literal IPv4 hosts in remote\_mpc\_key\_stores\[].url are derived automatically as /32; set this list when those URLs use private DNS. | `list(string)` | `[]` | no |
| remote\_mpc\_key\_stores | Hub-only: list of remote KS shards the hub orchestrator should know about. Each entry contributes one `[[key_store_gw.mpc_key_store_servers]]` URL to the orchestrator config (the signer & writer are unified into one keystore service per shard). The chart renders these into `remoteMpcKeyStores` (URL-only, no Deployment/Service). URLs are typically PSC endpoint DNS names provisioned by the TF caller. `mpc_shard_index` is the remote shard's 0-based index (its `shard_index` output): the chart names the gateway entry `mpc-ks-<mpc_shard_index>` to match that shard's own keystore self-name, which the orchestrator keys its cluster-identity set by. | <pre>list(object(\{<br />    name            = string<br />    url             = string<br />    mpc\_shard\_index = number<br />  }))</pre> | `[]` | no |
| remote\_policy\_server\_egress\_cidrs | Optional hub orchestrator NetworkPolicy destinations for remote policy reader/writer PSC endpoints. Literal IPv4 hosts in remote\_policy\_servers URLs are derived automatically as /32; set this list when those URLs use private DNS. | `list(string)` | `[]` | no |
| remote\_policy\_servers | Hub-only: list of remote policy servers the hub orchestrator should know about. Each entry contributes one `[[policy_gw.readers]]` and one `[[policy_gw.writers]]` to the orchestrator config. The chart renders these into `remotePolicyServers` (URL-only, no Deployment/Service). `name` must equal the remote POL's MPC component name (`policy-server-reader-<shard_index>`) so the orchestrator can verify approval signatures (the verifying key itself is propagated via the cluster-identity set, not configured here). | <pre>list(object(\{<br />    name       = string<br />    reader\_url = string<br />    writer\_url = string<br />  }))</pre> | `[]` | no |
| storage\_class\_fs\_type | Filesystem laid down on volumes from the module's dedicated NATS StorageClass (ext4 or xfs). | `string` | `"ext4"` | no |
| storage\_class\_reclaim\_policy | Reclaim policy for the module's dedicated NATS StorageClass (Delete or Retain). Delete reclaims the Persistent Disk with the PVC; Retain keeps it for manual recovery. | `string` | `"Delete"` | no |
| storage\_class\_replication\_type | pd.csi.storage.gke.io `replication-type` parameter: `none` for a zonal Persistent Disk (default — one zone, cheapest), or `regional-pd` for a synchronously-replicated two-zone disk (survives a single-zone outage; the GKE analog of resilient NATS JetStream storage). Regional PDs require `pd-balanced`/`pd-ssd` and cost roughly 2x. | `string` | `"none"` | no |
| storage\_class\_type | Persistent Disk type for the module's dedicated NATS StorageClass (`pd-balanced`, `pd-ssd`, or `pd-standard`). Defaults to pd-balanced — the cost/perf middle ground, the GKE counterpart of the AWS gp3 default. | `string` | `"pd-balanced"` | no |
| subnet\_cidr | Primary IPv4 range of this cluster's regional subnet — the NODE IP range (VMs, internal LB IPs). Pods and Services get their own secondary ranges below, so this only needs to cover node/ILB addresses. Must not overlap the secondary ranges or any peer VPC meshed via `peering-gcp`. | `string` | `"10.0.0.0/20"` | no |
| ui\_client\_secret\_ref | BYO mode: name of an existing Kubernetes Secret in the chart namespace containing the UI's Google OAuth client secret under key `CLIENT_SECRET`. Use when the customer manages secrets externally (External Secrets Operator, sealed-secrets, etc.). Mutually exclusive with `ui_google_oauth_client_secret` — this takes precedence when both are set. | `string` | `""` | no |
| ui\_google\_oauth\_client\_secret | Convenience mode: literal Google OAuth client secret. When non-empty (and `ui_client_secret_ref` is empty), the module creates a Kubernetes Secret named `vault-ui-google-client-secret` with key `CLIENT_SECRET` and auto-wires `ui.clientSecretRef` to it. Hub-side only; pass via a gitignored tfvars file or `TF_VAR_ui_google_oauth_client_secret` env. | `string` | `""` | no |
| ui\_image\_repository | Vault-UI image repository. Defaults to the sodot-docker-oci production registry; takes effect when image\_tags.ui is set. | `string` | `"repo.sodot.dev/sodot-docker-oci/vault-ui"` | no |
| ui\_ingress | Shared public UI origin for every cluster. Pass the same enabled/host/TLS settings to the hub and all spokes so every policy reader verifies WebAuthn against one relying-party origin. services.vaultUi keeps the workload and GKE Ingress hub-only; on the hub this config renders a className "gce" Ingress with an optional Google-managed certificate. The host drives the Ingress rule, UI REDIRECT\_URI, and WebAuthn origin, so matching DNS must resolve to the hub Ingress IP. | <pre>object(\{<br />    enabled     = bool<br />    host        = string<br />    class\_name  = optional(string, "gce")<br />    annotations = optional(map(string), \{})<br />    # TLS via a BYO Kubernetes Secret (mutually exclusive with managed\_certificate,<br />    # which wins when both are set — see locals.tf). Leave both defaulted for the<br />    # Google-managed-cert path.<br />    tls\_enabled = optional(bool, false)<br />    tls\_secret  = optional(string, "")<br />    # Provision a Google-managed TLS certificate for `host` (the default, GCP-native<br />    # front-door path). Renders `gcpManagedCertificate.enabled` in the chart values.<br />    managed\_certificate = optional(bool, true)<br />    # Name of a reserved global static IP (google\_compute\_global\_address) to pin the<br />    # Ingress to. Mapped to the `kubernetes.io/ingress.global-static-ip-name`<br />    # annotation in locals.tf so the front-door IP + Cloud DNS record + managed cert<br />    # can all bind to a stable address. Empty ("") lets GKE allocate an ephemeral IP.<br />    global\_static\_ip\_name = optional(string, "")<br />  })</pre> | <pre>\{<br />  "enabled": false,<br />  "host": ""<br />}</pre> | no |

### Outputs

| Name | Description |
| ---- | ----------- |
| cluster\_ca\_certificate | Base64-encoded cluster CA bundle for the API server endpoint. |
| cluster\_endpoint | GKE cluster API server endpoint (https). The kubernetes + helm providers target this. |
| cluster\_name | GKE cluster name. |
| database\_names | Logical PostgreSQL database names created on this cluster. |
| database\_service\_account\_emails | Per-workload GSA emails used by application pods and their colocated Cloud SQL Auth Proxy sidecars. The orchestrator entry exists only on the hub. |
| db\_instance\_connection\_name | Cloud SQL instance connection name (project:region:instance). Passed to the Cloud SQL Auth Proxy and useful for out-of-band `gcloud sql connect` / a local proxy. |
| db\_private\_ip | Private IP of the Cloud SQL instance (reachable in-VPC via Private Service Access). The in-cluster Auth Proxy and the DB provisioning Job dial this. |
| deployment\_preflight\_json | Machine-readable expected Workload Identity, PSC-ingress, KMS-key and Cloud SQL contract for internal deployment-handoff verification. |
| gcp\_project | GCP project ID hosting this cluster (used by the peering/PSC helpers and cross-project accepters). |
| gcp\_region | GCP region echo. |
| helm\_release\_name | Helm release name the operator MUST install with (echoes var.helm\_release\_name). The GKE internal LoadBalancer Services select Pods by this release instance label, so a mismatched release name leaves their backends empty. |
| helm\_values\_yaml | The module-rendered crypto-vault chart values as a ready-to-use YAML document — everything the chart needs on this cluster (cloudProvider=gcp, per-workload in-pod Cloud SQL proxies and identities, canonical gcp.kmsKey, the local MPC shard + policy-server identities, googleAuth, relay address/apiKey, GKE ingress, and services.\* role toggles). Write it to a file and use it for the documented two-stage Helm activation with release name = helm\_release\_name. Contains secrets: treat the file like a credential. |
| keystore\_service\_account\_email | Email of the keystore GSA. It has exact-key KMS Encrypt/Decrypt, location-constrained GenerateRandomBytes for the GCP image, and Cloud SQL access; it is shared only by the keystore container and its in-pod proxy sidecar. |
| kms\_crypto\_key\_id | Fully-qualified resource ID of the storage-encryptor Cloud KMS key (projects/\<p>/locations/\<l>/keyRings/\<r>/cryptoKeys/\<k>). Matches the gcp.kmsKey Helm input rendered as canonical kms\_key. |
| kms\_key\_ring\_id | Resource ID of the Cloud KMS key ring holding the storage-encryptor key. |
| ks\_ilb\_forwarding\_rule | Self-link of the keystore internal forwarding rule, or null when the KS internal LB is not enabled. Consumed by psc-gcp as target\_forwarding\_rule. |
| ks\_internal\_lb\_ip | Reserved internal IP fronting this cluster's unified keystore, or null when the KS internal LB is not enabled. Convenience accessor over internal\_lb\_addresses\["ks"]. |
| ks\_shard\_name | Name of this cluster's local MPC keystore shard (mirrors mpc\_key\_store.name). Useful when the hub caller assembles remote\_mpc\_key\_stores entries from a spoke's module outputs. |
| namespace | Kubernetes namespace into which the chart is installed. |
| nats\_ilb\_forwarding\_rule | Hub-only: self-link of the NATS internal forwarding rule (a spoke's nats\_hub\_url reaches it over PSC/peering), or null when not present. |
| nats\_internal\_lb\_ip | Hub-only: reserved internal IP fronting NATS (for a spoke's nats\_hub\_url over PSC/peering), or null when not present. On the hub the local keystore uses the in-cluster Service name instead — this is for CROSS-cluster consumers. |
| network\_name | Name of this cluster's VPC network. |
| network\_self\_link | Self-link of this cluster's VPC network. Feed into `peering-gcp` (both peering resources reference the two networks) and `psc-gcp`. |
| pods\_secondary\_range\_cidr | Alias-IP secondary range used for Pod IPs (echoes var.pods\_secondary\_range\_cidr) — a peer cluster must admit this in its extraIngressCidrs to accept this cluster's cross-cluster Pod traffic. |
| pol\_component\_name | This cluster's local policy-server MPC component name (policy-server-reader-\<shard\_index>). The hub caller uses this as the `name` in a remote\_policy\_servers entry so the orchestrator can verify the POL's approval signatures. |
| pol\_reader\_ilb\_forwarding\_rule | Self-link of the policy-server reader internal forwarding rule, or null when not enabled. |
| pol\_reader\_internal\_lb\_ip | Reserved internal IP fronting the policy-server reader, or null when not enabled. |
| pol\_writer\_ilb\_forwarding\_rule | Self-link of the policy-server writer internal forwarding rule, or null when not enabled. |
| pol\_writer\_internal\_lb\_ip | Reserved internal IP fronting the policy-server writer, or null when not enabled. |
| policy\_server\_port | Port the policy-server reader/writer Services listen on. Used by the hub caller to build the reader\_url / writer\_url for a remote\_policy\_servers entry. |
| relay\_ilb\_forwarding\_rule | Hub-only: self-link of the relay-router internal forwarding rule (a spoke's relay\_address reaches it over PSC/peering), or null when not present. |
| relay\_internal\_lb\_ip | Hub-only: reserved internal IP fronting the relay-router (for a spoke's relay\_address over PSC/peering), or null when not present. |
| services\_secondary\_range\_cidr | Alias-IP secondary range used for ClusterIP Services (echoes var.services\_secondary\_range\_cidr). |
| shard\_index | This cluster's 0-based MPC shard index (echoes var.shard\_index). The hub caller passes it as a remote\_mpc\_key\_stores\[].mpc\_shard\_index so the orchestrator gateway entry is named mpc-ks-\<index>, matching this shard's keystore self-name. |
| subnet\_primary\_cidr | Primary CIDR of the node subnet (the VPC-native trust boundary, mirrored into the chart's networkPolicies.extraIngressCidrs). |
| subnetwork\_self\_link | Self-link of the regional node subnet. Consumed by `psc-gcp` (the consumer forwarding rule + PSC NAT subnet attach to the same network/region) and useful for adding a firewall rule to the same subnet. |
