Merge branch 'master' into elia-gcve

This commit is contained in:
Ludovico Magnocavallo 2023-08-14 11:56:47 +02:00 committed by GitHub
commit 5689aacac2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 2156 additions and 1339 deletions

View File

@ -4,10 +4,28 @@ All notable changes to this project will be documented in this file.
<!-- markdownlint-disable MD024 --> <!-- markdownlint-disable MD024 -->
## [Unreleased] ## [Unreleased]
<!-- None < 2023-07-07 16:22:14+00:00 --> <!-- None < 2023-08-09 17:02:13+00:00 -->
### FAST
- [[#1583](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1583)] Fix module path for teams cicd ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 21:41:57+00:00 -->
### MODULES
- [[#1587](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1587)] **incompatible change:** Fix factory rules key in net firewall policy module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-14 05:52:37+00:00 -->
- [[#1578](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1578)] Fix: Instance level stateful disk config ([beardedsamwise](https://github.com/beardedsamwise)) <!-- 2023-08-11 15:25:17+00:00 -->
- [[#1582](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1582)] feat(modules/cloud-run): add gen2 exec env support ([LiuVII](https://github.com/LiuVII)) <!-- 2023-08-09 21:04:17+00:00 -->
### TOOLS
- [[#1585](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1585)] Print inventory path when a test fails ([juliocc](https://github.com/juliocc)) <!-- 2023-08-11 10:28:08+00:00 -->
## [25.0.0] - 2023-08-09
<!-- 2023-08-09 17:02:13+00:00 < 2023-07-07 16:22:14+00:00 -->
### BLUEPRINTS ### BLUEPRINTS
- [[#1581](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1581)] **incompatible change:** Remove firewall policy management from resource management modules ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 11:23:08+00:00 -->
- [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) <!-- 2023-08-06 18:27:59+00:00 --> - [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) <!-- 2023-08-06 18:27:59+00:00 -->
- [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 --> - [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 -->
- [[#1560](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1560)] Removed unused attribute in variable of ha-vpn-over-blueprint blueprint ([apichick](https://github.com/apichick)) <!-- 2023-08-02 11:41:08+00:00 --> - [[#1560](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1560)] Removed unused attribute in variable of ha-vpn-over-blueprint blueprint ([apichick](https://github.com/apichick)) <!-- 2023-08-02 11:41:08+00:00 -->
@ -23,6 +41,7 @@ All notable changes to this project will be documented in this file.
### DOCUMENTATION ### DOCUMENTATION
- [[#1581](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1581)] **incompatible change:** Remove firewall policy management from resource management modules ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 11:23:08+00:00 -->
- [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) <!-- 2023-08-06 18:27:59+00:00 --> - [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) <!-- 2023-08-06 18:27:59+00:00 -->
- [[#1545](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1545)] add dataplex autodq base module ([thinhha](https://github.com/thinhha)) <!-- 2023-08-02 11:16:33+00:00 --> - [[#1545](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1545)] add dataplex autodq base module ([thinhha](https://github.com/thinhha)) <!-- 2023-08-02 11:16:33+00:00 -->
- [[#1557](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1557)] renaming net-vpc-swp to net-swp ([skalolazka](https://github.com/skalolazka)) <!-- 2023-08-01 15:48:22+00:00 --> - [[#1557](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1557)] renaming net-vpc-swp to net-swp ([skalolazka](https://github.com/skalolazka)) <!-- 2023-08-01 15:48:22+00:00 -->
@ -33,6 +52,8 @@ All notable changes to this project will be documented in this file.
### FAST ### FAST
- [[#1579](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1579)] Enable team CI/CD impersonation ([williamsmt](https://github.com/williamsmt)) <!-- 2023-08-09 12:46:24+00:00 -->
- [[#1581](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1581)] **incompatible change:** Remove firewall policy management from resource management modules ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 11:23:08+00:00 -->
- [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 --> - [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 -->
- [[#1566](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1566)] Remove unused ASN numbers from CloudNAT to avoid provider errors ([LucaPrete](https://github.com/LucaPrete)) <!-- 2023-08-04 08:02:12+00:00 --> - [[#1566](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1566)] Remove unused ASN numbers from CloudNAT to avoid provider errors ([LucaPrete](https://github.com/LucaPrete)) <!-- 2023-08-04 08:02:12+00:00 -->
- [[#1563](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1563)] Update FAST CI/CD workflows so it can work with ID_TOKEN and Gitlab 15+ ([LucaPrete](https://github.com/LucaPrete)) <!-- 2023-08-03 16:09:45+00:00 --> - [[#1563](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1563)] Update FAST CI/CD workflows so it can work with ID_TOKEN and Gitlab 15+ ([LucaPrete](https://github.com/LucaPrete)) <!-- 2023-08-03 16:09:45+00:00 -->
@ -42,6 +63,9 @@ All notable changes to this project will be documented in this file.
### MODULES ### MODULES
- [[#1581](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1581)] **incompatible change:** Remove firewall policy management from resource management modules ([ludoo](https://github.com/ludoo)) <!-- 2023-08-09 11:23:08+00:00 -->
- [[#1580](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1580)] Apigee addons ([apichick](https://github.com/apichick)) <!-- 2023-08-09 06:33:20+00:00 -->
- [[#1576](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1576)] **incompatible change:** Refactor firewall policy module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-08 16:57:59+00:00 -->
- [[#1575](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1575)] Expose allow_net_admin feature in gke-cluster-autopilot module ([eunanhardy](https://github.com/eunanhardy)) <!-- 2023-08-07 15:03:51+00:00 --> - [[#1575](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1575)] Expose allow_net_admin feature in gke-cluster-autopilot module ([eunanhardy](https://github.com/eunanhardy)) <!-- 2023-08-07 15:03:51+00:00 -->
- [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 --> - [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) <!-- 2023-08-06 09:25:45+00:00 -->
- [[#1569](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1569)] Add support for cost management to GKE module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-05 11:46:53+00:00 --> - [[#1569](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1569)] Add support for cost management to GKE module ([ludoo](https://github.com/ludoo)) <!-- 2023-08-05 11:46:53+00:00 -->
@ -1438,7 +1462,8 @@ All notable changes to this project will be documented in this file.
- merge development branch with suite of new modules and end-to-end examples - merge development branch with suite of new modules and end-to-end examples
<!-- markdown-link-check-disable --> <!-- markdown-link-check-disable -->
[Unreleased]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v24.0.0...HEAD [Unreleased]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v25.0.0...HEAD
[25.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v24.0.0...v25.0.0
[24.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v23.0.0...v24.0.0 [24.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v23.0.0...v24.0.0
[23.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v22.0.0...v23.0.0 [23.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v22.0.0...v23.0.0
[22.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v21.0.0...v22.0.0 [22.0.0]: https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/compare/v21.0.0...v22.0.0

View File

@ -30,7 +30,7 @@ The current list of modules supports most of the core foundational and networkin
Currently available modules: Currently available modules:
- **foundational** - [billing budget](./modules/billing-budget), [Cloud Identity group](./modules/cloud-identity-group/), [folder](./modules/folder), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [organization](./modules/organization), [project](./modules/project), [projects-data-source](./modules/projects-data-source) - **foundational** - [billing budget](./modules/billing-budget), [Cloud Identity group](./modules/cloud-identity-group/), [folder](./modules/folder), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [organization](./modules/organization), [project](./modules/project), [projects-data-source](./modules/projects-data-source)
- **networking** - [DNS](./modules/dns), [DNS Response Policy](./modules/dns-response-policy/), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [VLAN Attachment](./modules/net-vlan-attachment/), [External Application LB](./modules/net-lb-app-ext/), [External Passthrough Network LB](./modules/net-lb-ext), [Internal Application LB](./modules/net-lb-app-int), [Internal Passthrough Network LB](./modules/net-lb-int), [Internal Proxy Network LB](./modules/net-lb-proxy-int), [IPSec over Interconnect](./modules/net-ipsec-over-interconnect), [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC firewall policy](./modules/net-vpc-firewall-policy), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory), [Secure Web Proxy](./modules/net-swp) - **networking** - [DNS](./modules/dns), [DNS Response Policy](./modules/dns-response-policy/), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [VLAN Attachment](./modules/net-vlan-attachment/), [External Application LB](./modules/net-lb-app-ext/), [External Passthrough Network LB](./modules/net-lb-ext), [Firewall policy](./modules/net-firewall-policy), [Internal Application LB](./modules/net-lb-app-int), [Internal Passthrough Network LB](./modules/net-lb-int), [Internal Proxy Network LB](./modules/net-lb-proxy-int), [IPSec over Interconnect](./modules/net-ipsec-over-interconnect), [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory), [Secure Web Proxy](./modules/net-swp)
- **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster-standard), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool) - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster-standard), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool)
- **data** - [AlloyDB instance](./modules/alloydb-instance), [BigQuery dataset](./modules/bigquery-dataset), [Bigtable instance](./modules/bigtable-instance), [Dataplex](./modules/dataplex), [Dataplex DataScan](./modules/dataplex-datascan/), [Cloud SQL instance](./modules/cloudsql-instance), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Datafusion](./modules/datafusion), [Dataproc](./modules/dataproc), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub) - **data** - [AlloyDB instance](./modules/alloydb-instance), [BigQuery dataset](./modules/bigquery-dataset), [Bigtable instance](./modules/bigtable-instance), [Dataplex](./modules/dataplex), [Dataplex DataScan](./modules/dataplex-datascan/), [Cloud SQL instance](./modules/cloudsql-instance), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Datafusion](./modules/datafusion), [Dataproc](./modules/dataproc), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub)
- **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository) - **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository)

View File

@ -209,5 +209,5 @@ module "test" {
billing_account_id = "123456-123456-123456" billing_account_id = "123456-123456-123456"
} }
} }
# tftest modules=6 resources=38 inventory=simple.yaml # tftest modules=7 resources=38
``` ```

View File

@ -0,0 +1,37 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
priority: 1000
match:
source_ranges:
- rfc1918
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
priority: 1001
match:
source_ranges:
- healthchecks
layer4_configs:
- protocol: tcp
ports: ["80", "443"]
allow-ssh-from-iap:
description: Enable SSH from IAP
priority: 1002
match:
source_ranges:
- 35.235.240.0/20
layer4_configs:
- protocol: tcp
ports: ["22"]
allow-icmp:
description: Enable ICMP
priority: 1003
match:
source_ranges:
- 0.0.0.0/0
layer4_configs:
- protocol: icmp

View File

@ -1,50 +0,0 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
action: allow
priority: 1000
ranges:
- $rfc1918
ports:
all: []
target_resources: null
enable_logging: false
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
direction: INGRESS
action: allow
priority: 1001
ranges:
- $healthchecks
ports:
tcp: ["80", "443"]
target_resources: null
enable_logging: false
allow-ssh-from-iap:
description: Enable SSH from IAP
direction: INGRESS
action: allow
priority: 1002
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
enable_logging: false
allow-icmp:
description: Enable ICMP
direction: INGRESS
action: allow
priority: 1003
ranges:
- 0.0.0.0/0
ports:
icmp: []
target_resources: null
enable_logging: false

View File

@ -78,11 +78,6 @@ module "folder" {
id = var.folder_config.folder_create != null ? null : var.folder_config.folder_id id = var.folder_config.folder_create != null ? null : var.folder_config.folder_id
group_iam = local.group_iam group_iam = local.group_iam
org_policies_data_path = var.data_dir != null ? "${var.data_dir}/org-policies" : null org_policies_data_path = var.data_dir != null ? "${var.data_dir}/org-policies" : null
firewall_policy_factory = var.data_dir != null ? {
cidr_file = "${var.data_dir}/firewall-policies/cidrs.yaml"
policy_name = "${var.prefix}-fw-policy"
rules_file = "${var.data_dir}/firewall-policies/hierarchical-policy-rules.yaml"
} : null
logging_sinks = var.enable_features.log_sink ? { logging_sinks = var.enable_features.log_sink ? {
for name, attrs in var.log_sinks : name => { for name, attrs in var.log_sinks : name => {
bq_partitioned_table = attrs.type == "bigquery" bq_partitioned_table = attrs.type == "bigquery"
@ -93,14 +88,24 @@ module "folder" {
} : null } : null
} }
module "firewall-policy" {
source = "../../../modules/net-firewall-policy"
name = "default"
parent_id = module.folder.id
rules_factory_config = var.data_dir == null ? {} : {
cidr_file_path = "${var.data_dir}/firewall-policies/cidrs.yaml"
ingress_rules_file_path = "${var.data_dir}/firewall-policies/hierarchical-ingress-rules.yaml"
}
}
module "folder-workload" { module "folder-workload" {
source = "../../../modules/folder" source = "../../../modules/folder"
parent = module.folder.id parent = module.folder.id
name = "${var.prefix}-workload" name = "${var.prefix}-workload"
} }
#TODO VPCSC: Access levels
#TODO VPCSC: Access levels
data "google_projects" "folder-projects" { data "google_projects" "folder-projects" {
filter = "parent.id:${split("/", module.folder.id)[1]}" filter = "parent.id:${split("/", module.folder.id)[1]}"

View File

@ -352,6 +352,7 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | <code>iam-service-account</code> · <code>source-repository</code> | | | [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | <code>iam-service-account</code> · <code>source-repository</code> | | | [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | <code>iam-service-account</code> · <code>source-repository</code> | | | [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [cicd-teams.tf](./cicd-teams.tf) | CI/CD resources for individual teams. | <code>iam-service-account</code> · <code>source-repository</code> | |
| [main.tf](./main.tf) | Module-level locals and resources. | | | | [main.tf](./main.tf) | Module-level locals and resources. | | |
| [organization.tf](./organization.tf) | Organization policies. | <code>organization</code> | | | [organization.tf](./organization.tf) | Organization policies. | <code>organization</code> | |
| [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> | | [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | <code>local_file</code> |
@ -378,22 +379,23 @@ Due to its simplicity, this stage lends itself easily to customizations: adding
| [outputs_location](variables.tf#L210) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | | | [outputs_location](variables.tf#L210) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | <code>string</code> | | <code>null</code> | |
| [tag_names](variables.tf#L227) | Customized names for resource management tags. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; org-policies &#61; string&#10; tenant &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; context &#61; &#34;context&#34;&#10; environment &#61; &#34;environment&#34;&#10; org-policies &#61; &#34;org-policies&#34;&#10; tenant &#61; &#34;tenant&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | | [tag_names](variables.tf#L227) | Customized names for resource management tags. | <code title="object&#40;&#123;&#10; context &#61; string&#10; environment &#61; string&#10; org-policies &#61; string&#10; tenant &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; context &#61; &#34;context&#34;&#10; environment &#61; &#34;environment&#34;&#10; org-policies &#61; &#34;org-policies&#34;&#10; tenant &#61; &#34;tenant&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | |
| [tags](variables.tf#L248) | Custome secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [tags](variables.tf#L248) | Custome secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [team_folders](variables.tf#L269) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | | | [team_folders](variables.tf#L269) | Team folders to be created. Format is described in a code comment. | <code title="map&#40;object&#40;&#123;&#10; descriptive_name &#61; string&#10; group_iam &#61; map&#40;list&#40;string&#41;&#41;&#10; impersonation_groups &#61; list&#40;string&#41;&#10; cicd &#61; optional&#40;object&#40;&#123;&#10; branch &#61; string&#10; identity_provider &#61; string&#10; name &#61; string&#10; type &#61; string&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>null</code> | |
| [tenants](variables.tf#L279) | Lightweight tenant definitions. | <code title="map&#40;object&#40;&#123;&#10; admin_group_email &#61; string&#10; descriptive_name &#61; string&#10; billing_account &#61; optional&#40;string&#41;&#10; organization &#61; optional&#40;object&#40;&#123;&#10; customer_id &#61; string&#10; domain &#61; string&#10; id &#61; number&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | | [tenants](variables.tf#L285) | Lightweight tenant definitions. | <code title="map&#40;object&#40;&#123;&#10; admin_group_email &#61; string&#10; descriptive_name &#61; string&#10; billing_account &#61; optional&#40;string&#41;&#10; organization &#61; optional&#40;object&#40;&#123;&#10; customer_id &#61; string&#10; domain &#61; string&#10; id &#61; number&#10; &#125;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | |
| [tenants_config](variables.tf#L295) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | <code title="object&#40;&#123;&#10; core_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tenant_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; top_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | | | [tenants_config](variables.tf#L301) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | <code title="object&#40;&#123;&#10; core_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; tenant_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; top_folder_roles &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | |
## Outputs ## Outputs
| name | description | sensitive | consumers | | name | description | sensitive | consumers |
|---|---|:---:|---| |---|---|:---:|---|
| [cicd_repositories](outputs.tf#L213) | WIF configuration for CI/CD repositories. | | | | [cicd_repositories](outputs.tf#L232) | WIF configuration for CI/CD repositories. | | |
| [dataplatform](outputs.tf#L227) | Data for the Data Platform stage. | | | | [dataplatform](outputs.tf#L246) | Data for the Data Platform stage. | | |
| [gke_multitenant](outputs.tf#L243) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> | | [gke_multitenant](outputs.tf#L262) | Data for the GKE multitenant stage. | | <code>03-gke-multitenant</code> |
| [networking](outputs.tf#L264) | Data for the networking stage. | | | | [networking](outputs.tf#L283) | Data for the networking stage. | | |
| [project_factories](outputs.tf#L273) | Data for the project factories stage. | | | | [project_factories](outputs.tf#L292) | Data for the project factories stage. | | |
| [providers](outputs.tf#L288) | Terraform provider files for this stage and dependent stages. | ✓ | <code>02-networking</code> · <code>02-security</code> · <code>03-dataplatform</code> · <code>xx-sandbox</code> · <code>xx-teams</code> | | [providers](outputs.tf#L307) | Terraform provider files for this stage and dependent stages. | ✓ | <code>02-networking</code> · <code>02-security</code> · <code>03-dataplatform</code> · <code>xx-sandbox</code> · <code>xx-teams</code> |
| [sandbox](outputs.tf#L295) | Data for the sandbox stage. | | <code>xx-sandbox</code> | | [sandbox](outputs.tf#L314) | Data for the sandbox stage. | | <code>xx-sandbox</code> |
| [security](outputs.tf#L309) | Data for the networking stage. | | <code>02-security</code> | | [security](outputs.tf#L328) | Data for the networking stage. | | <code>02-security</code> |
| [teams](outputs.tf#L319) | Data for the teams stage. | | | | [team_cicd_repositories](outputs.tf#L338) | WIF configuration for Team CI/CD repositories. | | |
| [tfvars](outputs.tf#L331) | Terraform variable files for the following stages. | ✓ | | | [teams](outputs.tf#L352) | Data for the teams stage. | | |
| [tfvars](outputs.tf#L364) | Terraform variable files for the following stages. | ✓ | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -90,10 +90,13 @@ module "branch-teams-team-sa" {
display_name = "Terraform team ${each.key} service account." display_name = "Terraform team ${each.key} service account."
prefix = var.prefix prefix = var.prefix
iam = { iam = {
"roles/iam.serviceAccountTokenCreator" = ( "roles/iam.serviceAccountTokenCreator" = concat(
each.value.impersonation_groups == null compact([try(module.branch-teams-team-sa-cicd[each.key].iam_email, null)]),
? [] (
: [for g in each.value.impersonation_groups : "group:${g}"] each.value.impersonation_groups == null
? []
: [for g in each.value.impersonation_groups : "group:${g}"]
)
) )
} }
} }

View File

@ -0,0 +1,93 @@
/**
* Copyright 2022 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.
*/
# tfdoc:file:description CI/CD resources for individual teams.
# source repository
module "branch-teams-team-cicd-repo" {
source = "../../../modules/source-repository"
for_each = {
for k, v in coalesce(local.team_cicd_repositories, {}) : k => v
if v.cicd.type == "sourcerepo"
}
project_id = var.automation.project_id
name = each.value.cicd.name
iam = {
"roles/source.admin" = [module.branch-teams-team-sa[each.key].iam_email]
"roles/source.reader" = [module.branch-teams-team-sa-cicd[each.key].iam_email]
}
triggers = {
"fast-03-team-${each.key}" = {
filename = ".cloudbuild/workflow.yaml"
included_files = ["**/*tf", ".cloudbuild/workflow.yaml"]
service_account = module.branch-teams-team-sa-cicd[each.key].id
substitutions = {}
template = {
project_id = null
branch_name = each.value.cicd.branch
repo_name = each.value.cicd.name
tag_name = null
}
}
}
depends_on = [module.branch-teams-team-sa-cicd]
}
# SA used by CI/CD workflows to impersonate automation SAs
module "branch-teams-team-sa-cicd" {
source = "../../../modules/iam-service-account"
for_each = (
try(local.team_cicd_repositories, null) != null
? local.team_cicd_repositories
: {}
)
project_id = var.automation.project_id
name = "prod-teams-${each.key}-1"
display_name = "Terraform CI/CD team ${each.key} service account."
prefix = var.prefix
iam = (
each.value.cicd.type == "sourcerepo"
# used directly from the cloud build trigger for source repos
? {
"roles/iam.serviceAccountUser" = local.automation_resman_sa_iam
}
# impersonated via workload identity federation for external repos
: {
"roles/iam.workloadIdentityUser" = [
each.value.cicd.branch == null
? format(
local.identity_providers[each.value.cicd.identity_provider].principalset_tpl,
var.automation.federated_identity_pool,
each.value.cicd.name
)
: format(
local.identity_providers[each.value.cicd.identity_provider].principal_tpl,
var.automation.federated_identity_pool,
each.value.cicd.name,
each.value.cicd.branch
)
]
}
)
iam_project_roles = {
(var.automation.project_id) = ["roles/logging.logWriter"]
}
iam_storage_roles = {
(var.automation.outputs_bucket) = ["roles/storage.objectViewer"]
}
}

View File

@ -47,6 +47,21 @@ locals {
fileexists("${path.module}/templates/workflow-${try(v.type, "")}.yaml") fileexists("${path.module}/templates/workflow-${try(v.type, "")}.yaml")
) )
} }
team_cicd_repositories = {
for k, v in coalesce(var.team_folders, {}) : k => v
if(
v != null &&
(
try(v.cicd.type, null) == "sourcerepo"
||
contains(
keys(local.identity_providers),
coalesce(try(v.cicd.identity_provider, null), ":")
)
) &&
fileexists("${path.module}/templates/workflow-${try(v.cicd.type, "")}.yaml")
)
}
cicd_workflow_var_files = { cicd_workflow_var_files = {
stage_2 = [ stage_2 = [
"0-bootstrap.auto.tfvars.json", "0-bootstrap.auto.tfvars.json",

View File

@ -35,7 +35,7 @@ resource "local_file" "tfvars" {
} }
resource "local_file" "workflows" { resource "local_file" "workflows" {
for_each = var.outputs_location == null ? {} : local.cicd_workflows for_each = var.outputs_location == null ? {} : merge(local.cicd_workflows, local.team_cicd_workflows)
file_permission = "0644" file_permission = "0644"
filename = "${local.outputs_location}/workflows/${replace(each.key, "_", "-")}-workflow.yaml" filename = "${local.outputs_location}/workflows/${replace(each.key, "_", "-")}-workflow.yaml"
content = try(each.value, null) content = try(each.value, null)

View File

@ -30,7 +30,7 @@ resource "google_storage_bucket_object" "tfvars" {
} }
resource "google_storage_bucket_object" "workflows" { resource "google_storage_bucket_object" "workflows" {
for_each = local.cicd_workflows for_each = merge(local.cicd_workflows, local.team_cicd_workflows)
bucket = var.automation.outputs_bucket bucket = var.automation.outputs_bucket
name = "workflows/${replace(each.key, "_", "-")}-workflow.yaml" name = "workflows/${replace(each.key, "_", "-")}-workflow.yaml"
content = each.value content = each.value

View File

@ -201,6 +201,25 @@ locals {
for k, v in module.branch-teams-team-sa : "team-${k}" => v.email for k, v in module.branch-teams-team-sa : "team-${k}" => v.email
}, },
) )
team_cicd_workflows = {
for k, v in local.team_cicd_repositories : k => templatefile(
"${path.module}/templates/workflow-${v.cicd.type}.yaml",
merge(local.team_cicd_workflow_attrs[k], {
identity_provider = try(
local.identity_providers[v.cicd.identity_provider].name, null
)
outputs_bucket = var.automation.outputs_bucket
stage_name = k
})
)
}
team_cicd_workflow_attrs = {
for k, v in local.team_cicd_repositories : k => {
service_account = try(module.branch-teams-team-sa-cicd[k].email, null)
tf_providers_file = "3-teams-${k}-providers.tf"
tf_var_files = local.cicd_workflow_var_files.stage_3
}
}
tfvars = { tfvars = {
folder_ids = local.folder_ids folder_ids = local.folder_ids
service_accounts = local.service_accounts service_accounts = local.service_accounts
@ -316,6 +335,20 @@ output "security" {
} }
} }
output "team_cicd_repositories" {
description = "WIF configuration for Team CI/CD repositories."
value = {
for k, v in local.team_cicd_repositories : k => {
branch = v.cicd.branch
name = v.cicd.name
provider = try(
local.identity_providers[v.cicd.identity_provider].name, null
)
service_account = local.team_cicd_workflow_attrs[k].service_account
} if v.cicd != null
}
}
output "teams" { output "teams" {
description = "Data for the teams stage." description = "Data for the teams stage."
value = { value = {

View File

@ -272,6 +272,12 @@ variable "team_folders" {
descriptive_name = string descriptive_name = string
group_iam = map(list(string)) group_iam = map(list(string))
impersonation_groups = list(string) impersonation_groups = list(string)
cicd = optional(object({
branch = string
identity_provider = string
name = string
type = string
}))
})) }))
default = null default = null
} }

View File

@ -172,7 +172,7 @@ Static routes are defined in `vpc-*.tf` files, in the `routes` section of each `
**VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `vpc-*.tf` file and leverage a resource factory to massively create rules. **VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `vpc-*.tf` file and leverage a resource factory to massively create rules.
To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/landing](./data/firewall-rules/landing) and can be easily customised. To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/landing](./data/firewall-rules/landing) and can be easily customised.
**Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf`, and managed through a policy factory implemented by the `folder` module, which applies the defined hierarchical to the `Networking` folder, which contains all the core networking infrastructure. Policies are defined in the `rules_file` file - to define a new one simply use the instructions found on "[Firewall policy factory](../../../modules/organization#firewall-policy-factory)". Sample hierarchical firewall policies are shipped in [data/hierarchical-policy-rules.yaml](./data/hierarchical-policy-rules.yaml) and can be easily customised. **Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf` and managed through a policy factory implemented by the `net-firewall-policy` module, which is then applied to the `Networking` folder containing all the core networking infrastructure. Policies are defined in the `rules_file` file, to define a new one simply use the [firewall policy module documentation](../../../modules/net-firewall-policy/README.md#factory)". Sample hierarchical firewall rules are shipped in [data/hierarchical-ingress-rules.yaml](./data/hierarchical-ingress-rules.yaml) and can be easily customised.
### DNS architecture ### DNS architecture
@ -378,7 +378,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | | | [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | |
| [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> | |
| [landing.tf](./landing.tf) | Landing VPC and related resources. | <code>net-cloudnat</code> · <code>net-vpc</code> · <code>net-vpc-firewall</code> · <code>project</code> | | | [landing.tf](./landing.tf) | Landing VPC and related resources. | <code>net-cloudnat</code> · <code>net-vpc</code> · <code>net-vpc-firewall</code> · <code>project</code> | |
| [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> | | | [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> · <code>net-firewall-policy</code> | |
| [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> | | [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> |
| [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | | <code>google_storage_bucket_object</code> · <code>local_file</code> | | [outputs.tf](./outputs.tf) | Module outputs. | | <code>google_storage_bucket_object</code> · <code>local_file</code> |

View File

@ -0,0 +1,37 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
priority: 1000
match:
source_ranges:
- rfc1918
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
priority: 1001
match:
source_ranges:
- healthchecks
layer4_configs:
- protocol: tcp
ports: ["80", "443"]
allow-ssh-from-iap:
description: Enable SSH from IAP
priority: 1002
match:
source_ranges:
- 35.235.240.0/20
layer4_configs:
- protocol: tcp
ports: ["22"]
allow-icmp:
description: Enable ICMP
priority: 1003
match:
source_ranges:
- 0.0.0.0/0
layer4_configs:
- protocol: icmp

View File

@ -1,49 +0,0 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
action: allow
priority: 1000
ranges:
- $rfc1918
ports:
all: []
target_resources: null
enable_logging: false
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
direction: INGRESS
action: allow
priority: 1001
ranges:
- $healthchecks
ports:
tcp: ["80", "443"]
target_resources: null
enable_logging: false
allow-ssh-from-iap:
description: Enable SSH from IAP
direction: INGRESS
action: allow
priority: 1002
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
enable_logging: false
allow-icmp:
description: Enable ICMP
direction: INGRESS
action: allow
priority: 1003
ranges:
- 0.0.0.0/0
ports:
icmp: []
target_resources: null
enable_logging: false

View File

@ -45,13 +45,18 @@ module "folder" {
name = "Networking" name = "Networking"
folder_create = var.folder_ids.networking == null folder_create = var.folder_ids.networking == null
id = var.folder_ids.networking id = var.folder_ids.networking
firewall_policy_factory = { firewall_policy_associations = {
cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" default = module.firewall-policy-default.id
policy_name = var.factories_config.firewall_policy_name }
rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" }
}
firewall_policy_association = { module "firewall-policy-default" {
factory-policy = "factory" source = "../../../modules/net-firewall-policy"
name = "net-default"
parent_id = module.folder.id
rules_factory_config = {
cidr_file_path = "${var.factories_config.data_dir}/cidrs.yaml"
ingress_rules_file_path = "${var.factories_config.data_dir}/hierarchical-ingress-rules.yaml"
} }
} }

View File

@ -186,7 +186,7 @@ BGP sessions for landing-spoke are configured through variable `vpn_spoke_config
**VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `vpc-*.tf` file and leverage a resource factory to massively create rules. **VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `vpc-*.tf` file and leverage a resource factory to massively create rules.
To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/landing](./data/firewall-rules/landing) and can be easily customised. To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/landing](./data/firewall-rules/landing) and can be easily customised.
**Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf`, and managed through a policy factory implemented by the `folder` module, which applies the defined hierarchical to the `Networking` folder, which contains all the core networking infrastructure. Policies are defined in the `rules_file` file - to define a new one simply use the instructions found on "[Firewall policy factory](../../../modules/organization#firewall-policy-factory)". Sample hierarchical firewall policies are shipped in [data/hierarchical-policy-rules.yaml](./data/hierarchical-policy-rules.yaml) and can be easily customised. **Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf` and managed through a policy factory implemented by the `net-firewall-policy` module, which is then applied to the `Networking` folder containing all the core networking infrastructure. Policies are defined in the `rules_file` file, to define a new one simply use the [firewall policy module documentation](../../../modules/net-firewall-policy/README.md#factory)". Sample hierarchical firewall rules are shipped in [data/hierarchical-ingress-rules.yaml](./data/hierarchical-ingress-rules.yaml) and can be easily customised.
### DNS architecture ### DNS architecture
@ -393,7 +393,6 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
<!-- TFDOC OPTS files:1 show_extra:1 --> <!-- TFDOC OPTS files:1 show_extra:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | modules | resources | | name | description | modules | resources |
@ -402,7 +401,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | | | [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | |
| [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> | |
| [landing.tf](./landing.tf) | Landing VPC and related resources. | <code>net-cloudnat</code> · <code>net-vpc</code> · <code>net-vpc-firewall</code> · <code>project</code> | | | [landing.tf](./landing.tf) | Landing VPC and related resources. | <code>net-cloudnat</code> · <code>net-vpc</code> · <code>net-vpc-firewall</code> · <code>project</code> | |
| [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> | | | [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> · <code>net-firewall-policy</code> | |
| [monitoring-vpn.tf](./monitoring-vpn.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> | | [monitoring-vpn.tf](./monitoring-vpn.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> |
| [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | | <code>google_storage_bucket_object</code> · <code>local_file</code> | | [outputs.tf](./outputs.tf) | Module outputs. | | <code>google_storage_bucket_object</code> · <code>local_file</code> |
@ -447,5 +446,4 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [shared_vpc_self_links](outputs.tf#L78) | Shared VPC host projects. | | | | [shared_vpc_self_links](outputs.tf#L78) | Shared VPC host projects. | | |
| [tfvars](outputs.tf#L83) | Terraform variables file for the following stages. | ✓ | | | [tfvars](outputs.tf#L83) | Terraform variables file for the following stages. | ✓ | |
| [vpn_gateway_endpoints](outputs.tf#L89) | External IP Addresses for the GCP VPN gateways. | | | | [vpn_gateway_endpoints](outputs.tf#L89) | External IP Addresses for the GCP VPN gateways. | | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -0,0 +1,37 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
priority: 1000
match:
source_ranges:
- rfc1918
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
priority: 1001
match:
source_ranges:
- healthchecks
layer4_configs:
- protocol: tcp
ports: ["80", "443"]
allow-ssh-from-iap:
description: Enable SSH from IAP
priority: 1002
match:
source_ranges:
- 35.235.240.0/20
layer4_configs:
- protocol: tcp
ports: ["22"]
allow-icmp:
description: Enable ICMP
priority: 1003
match:
source_ranges:
- 0.0.0.0/0
layer4_configs:
- protocol: icmp

View File

@ -1,49 +0,0 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
action: allow
priority: 1000
ranges:
- $rfc1918
ports:
all: []
target_resources: null
enable_logging: false
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
direction: INGRESS
action: allow
priority: 1001
ranges:
- $healthchecks
ports:
tcp: ["80", "443"]
target_resources: null
enable_logging: false
allow-ssh-from-iap:
description: Enable SSH from IAP
direction: INGRESS
action: allow
priority: 1002
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
enable_logging: false
allow-icmp:
description: Enable ICMP
direction: INGRESS
action: allow
priority: 1003
ranges:
- 0.0.0.0/0
ports:
icmp: []
target_resources: null
enable_logging: false

View File

@ -45,13 +45,18 @@ module "folder" {
name = "Networking" name = "Networking"
folder_create = var.folder_ids.networking == null folder_create = var.folder_ids.networking == null
id = var.folder_ids.networking id = var.folder_ids.networking
firewall_policy_factory = { firewall_policy_associations = {
cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" default = module.firewall-policy-default.id
policy_name = var.factories_config.firewall_policy_name }
rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" }
}
firewall_policy_association = { module "firewall-policy-default" {
factory-policy = "factory" source = "../../../modules/net-firewall-policy"
name = "net-default"
parent_id = module.folder.id
rules_factory_config = {
cidr_file_path = "${var.factories_config.data_dir}/cidrs.yaml"
ingress_rules_file_path = "${var.factories_config.data_dir}/hierarchical-ingress-rules.yaml"
} }
} }

View File

@ -254,7 +254,7 @@ BGP sessions for trusted landing to on-premises are configured through the varia
**VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `vpc-*.tf` file and leverage a resource factory to massively create rules. **VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `vpc-*.tf` file and leverage a resource factory to massively create rules.
To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/landing-untrusted](./data/firewall-rules/landing-untrusted) and in [data/firewall-rules/landing-trusted](./data/firewall-rules/landing-trusted), and can be easily customized. To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/landing-untrusted](./data/firewall-rules/landing-untrusted) and in [data/firewall-rules/landing-trusted](./data/firewall-rules/landing-trusted), and can be easily customized.
**Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf`, and managed through a policy factory implemented by the `folder` module, which applies the defined hierarchical to the `Networking` folder, which contains all the core networking infrastructure. Policies are defined in the `rules_file` file - to define a new one simply use the instructions found on "[Firewall policy factory](../../../modules/organization#firewall-policy-factory)". Sample hierarchical firewall policies are shipped in [data/hierarchical-policy-rules.yaml](./data/hierarchical-policy-rules.yaml) and can be easily customized. **Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf` and managed through a policy factory implemented by the `net-firewall-policy` module, which is then applied to the `Networking` folder containing all the core networking infrastructure. Policies are defined in the `rules_file` file, to define a new one simply use the [firewall policy module documentation](../../../modules/net-firewall-policy/README.md#factory)". Sample hierarchical firewall rules are shipped in [data/hierarchical-ingress-rules.yaml](./data/hierarchical-ingress-rules.yaml) and can be easily customised.
### DNS architecture ### DNS architecture
@ -452,7 +452,6 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
<!-- TFDOC OPTS files:1 show_extra:1 --> <!-- TFDOC OPTS files:1 show_extra:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | modules | resources | | name | description | modules | resources |
@ -461,7 +460,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | | | [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | |
| [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> | |
| [landing.tf](./landing.tf) | Landing VPC and related resources. | <code>net-cloudnat</code> · <code>net-vpc</code> · <code>net-vpc-firewall</code> · <code>project</code> | | | [landing.tf](./landing.tf) | Landing VPC and related resources. | <code>net-cloudnat</code> · <code>net-vpc</code> · <code>net-vpc-firewall</code> · <code>project</code> | |
| [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> | | | [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> · <code>net-firewall-policy</code> | |
| [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> | | [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> |
| [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> |
| [nva.tf](./nva.tf) | None | <code>compute-mig</code> · <code>compute-vm</code> · <code>simple-nva</code> | | | [nva.tf](./nva.tf) | None | <code>compute-mig</code> · <code>compute-vm</code> · <code>simple-nva</code> | |
@ -504,5 +503,4 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [shared_vpc_self_links](outputs.tf#L68) | Shared VPC host projects. | | | | [shared_vpc_self_links](outputs.tf#L68) | Shared VPC host projects. | | |
| [tfvars](outputs.tf#L73) | Terraform variables file for the following stages. | ✓ | | | [tfvars](outputs.tf#L73) | Terraform variables file for the following stages. | ✓ | |
| [vpn_gateway_endpoints](outputs.tf#L79) | External IP Addresses for the GCP VPN gateways. | | | | [vpn_gateway_endpoints](outputs.tf#L79) | External IP Addresses for the GCP VPN gateways. | | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -0,0 +1,37 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
priority: 1000
match:
source_ranges:
- rfc1918
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
priority: 1001
match:
source_ranges:
- healthchecks
layer4_configs:
- protocol: tcp
ports: ["80", "443"]
allow-ssh-from-iap:
description: Enable SSH from IAP
priority: 1002
match:
source_ranges:
- 35.235.240.0/20
layer4_configs:
- protocol: tcp
ports: ["22"]
allow-icmp:
description: Enable ICMP
priority: 1003
match:
source_ranges:
- 0.0.0.0/0
layer4_configs:
- protocol: icmp

View File

@ -1,49 +0,0 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
action: allow
priority: 1000
ranges:
- $rfc1918
ports:
all: []
target_resources: null
enable_logging: false
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
direction: INGRESS
action: allow
priority: 1001
ranges:
- $healthchecks
ports:
tcp: ["80", "443"]
target_resources: null
enable_logging: false
allow-ssh-from-iap:
description: Enable SSH from IAP
direction: INGRESS
action: allow
priority: 1002
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
enable_logging: false
allow-icmp:
description: Enable ICMP
direction: INGRESS
action: allow
priority: 1003
ranges:
- 0.0.0.0/0
ports:
icmp: []
target_resources: null
enable_logging: false

View File

@ -46,12 +46,17 @@ module "folder" {
name = "Networking" name = "Networking"
folder_create = var.folder_ids.networking == null folder_create = var.folder_ids.networking == null
id = var.folder_ids.networking id = var.folder_ids.networking
firewall_policy_factory = { firewall_policy_associations = {
cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" default = module.firewall-policy-default.id
policy_name = var.factories_config.firewall_policy_name }
rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" }
}
firewall_policy_association = { module "firewall-policy-default" {
factory-policy = var.factories_config.firewall_policy_name source = "../../../modules/net-firewall-policy"
name = "net-default"
parent_id = module.folder.id
rules_factory_config = {
cidr_file_path = "${var.factories_config.data_dir}/cidrs.yaml"
ingress_rules_file_path = "${var.factories_config.data_dir}/hierarchical-ingress-rules.yaml"
} }
} }

View File

@ -137,7 +137,7 @@ Static routes are defined in `net-*.tf` files, in the `routes` section of each `
**VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `net-*.tf` file and leverage a resource factory to massively create rules. **VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `net-*.tf` file and leverage a resource factory to massively create rules.
To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/dev](./data/firewall-rules/dev) and can be easily customised. To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/dev](./data/firewall-rules/dev) and can be easily customised.
**Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf`, and managed through a policy factory implemented by the `folder` module, which applies the defined hierarchical to the `Networking` folder, which contains all the core networking infrastructure. Policies are defined in the `rules_file` file - to define a new one simply use the instructions found on "[Firewall policy factory](../../../modules/organization#firewall-policy-factory)". Sample hierarchical firewall policies are shipped in [data/hierarchical-policy-rules.yaml](./data/hierarchical-policy-rules.yaml) and can be easily customised. **Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf` and managed through a policy factory implemented by the `net-firewall-policy` module, which is then applied to the `Networking` folder containing all the core networking infrastructure. Policies are defined in the `rules_file` file, to define a new one simply use the [firewall policy module documentation](../../../modules/net-firewall-policy/README.md#factory)". Sample hierarchical firewall rules are shipped in [data/hierarchical-ingress-rules.yaml](./data/hierarchical-ingress-rules.yaml) and can be easily customised.
### DNS architecture ### DNS architecture
@ -317,14 +317,13 @@ Regions are defined via the `regions` variable which sets up a mapping between t
<!-- TFDOC OPTS files:1 show_extra:1 --> <!-- TFDOC OPTS files:1 show_extra:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | modules | resources | | name | description | modules | resources |
|---|---|---|---| |---|---|---|---|
| [dns-dev.tf](./dns-dev.tf) | Development spoke DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | | | [dns-dev.tf](./dns-dev.tf) | Development spoke DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | |
| [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | |
| [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> | | | [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> · <code>net-firewall-policy</code> | |
| [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> | | [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> |
| [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | | <code>google_storage_bucket_object</code> · <code>local_file</code> | | [outputs.tf](./outputs.tf) | Module outputs. | | <code>google_storage_bucket_object</code> · <code>local_file</code> |
@ -366,5 +365,4 @@ Regions are defined via the `regions` variable which sets up a mapping between t
| [shared_vpc_self_links](outputs.tf#L79) | Shared VPC host projects. | | | | [shared_vpc_self_links](outputs.tf#L79) | Shared VPC host projects. | | |
| [tfvars](outputs.tf#L84) | Terraform variables file for the following stages. | ✓ | | | [tfvars](outputs.tf#L84) | Terraform variables file for the following stages. | ✓ | |
| [vpn_gateway_endpoints](outputs.tf#L90) | External IP Addresses for the GCP VPN gateways. | | | | [vpn_gateway_endpoints](outputs.tf#L90) | External IP Addresses for the GCP VPN gateways. | | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -0,0 +1,37 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
priority: 1000
match:
source_ranges:
- rfc1918
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
priority: 1001
match:
source_ranges:
- healthchecks
layer4_configs:
- protocol: tcp
ports: ["80", "443"]
allow-ssh-from-iap:
description: Enable SSH from IAP
priority: 1002
match:
source_ranges:
- 35.235.240.0/20
layer4_configs:
- protocol: tcp
ports: ["22"]
allow-icmp:
description: Enable ICMP
priority: 1003
match:
source_ranges:
- 0.0.0.0/0
layer4_configs:
- protocol: icmp

View File

@ -1,49 +0,0 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
action: allow
priority: 1000
ranges:
- $rfc1918
ports:
all: []
target_resources: null
enable_logging: false
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
direction: INGRESS
action: allow
priority: 1001
ranges:
- $healthchecks
ports:
tcp: ["80", "443"]
target_resources: null
enable_logging: false
allow-ssh-from-iap:
description: Enable SSH from IAP
direction: INGRESS
action: allow
priority: 1002
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
enable_logging: false
allow-icmp:
description: Enable ICMP
direction: INGRESS
action: allow
priority: 1003
ranges:
- 0.0.0.0/0
ports:
icmp: []
target_resources: null
enable_logging: false

View File

@ -41,13 +41,17 @@ module "folder" {
name = "Networking" name = "Networking"
folder_create = var.folder_ids.networking == null folder_create = var.folder_ids.networking == null
id = var.folder_ids.networking id = var.folder_ids.networking
firewall_policy_factory = { firewall_policy_associations = {
cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" default = module.firewall-policy-default.id
policy_name = var.factories_config.firewall_policy_name
rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml"
}
firewall_policy_association = {
factory-policy = var.factories_config.firewall_policy_name
} }
} }
module "firewall-policy-default" {
source = "../../../modules/net-firewall-policy"
name = "net-default"
parent_id = module.folder.id
rules_factory_config = {
cidr_file_path = "${var.factories_config.data_dir}/cidrs.yaml"
ingress_rules_file_path = "${var.factories_config.data_dir}/hierarchical-ingress-rules.yaml"
}
}

View File

@ -276,7 +276,7 @@ BGP sessions for trusted landing to on-premises are configured through the varia
**VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `vpc-*.tf` file and leverage a resource factory to massively create rules. **VPC firewall rules** ([`net-vpc-firewall`](../../../modules/net-vpc-firewall)) are defined per-vpc on each `vpc-*.tf` file and leverage a resource factory to massively create rules.
To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/landing-untrusted](./data/firewall-rules/landing-untrusted) and in [data/firewall-rules/landing-trusted](./data/firewall-rules/landing-trusted), and can be easily customized. To add a new firewall rule, create a new file or edit an existing one in the `data_folder` directory defined in the module `net-vpc-firewall`, following the examples of the "[Rules factory](../../../modules/net-vpc-firewall#rules-factory)" section of the module documentation. Sample firewall rules are shipped in [data/firewall-rules/landing-untrusted](./data/firewall-rules/landing-untrusted) and in [data/firewall-rules/landing-trusted](./data/firewall-rules/landing-trusted), and can be easily customized.
**Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf`, and managed through a policy factory implemented by the `folder` module, which applies the defined hierarchical to the `Networking` folder, which contains all the core networking infrastructure. Policies are defined in the `rules_file` file - to define a new one simply use the instructions found on "[Firewall policy factory](../../../modules/organization#firewall-policy-factory)". Sample hierarchical firewall policies are shipped in [data/hierarchical-policy-rules.yaml](./data/hierarchical-policy-rules.yaml) and can be easily customized. **Hierarchical firewall policies** ([`folder`](../../../modules/folder)) are defined in `main.tf` and managed through a policy factory implemented by the `net-firewall-policy` module, which is then applied to the `Networking` folder containing all the core networking infrastructure. Policies are defined in the `rules_file` file, to define a new one simply use the [firewall policy module documentation](../../../modules/net-firewall-policy/README.md#factory)". Sample hierarchical firewall rules are shipped in [data/hierarchical-ingress-rules.yaml](./data/hierarchical-ingress-rules.yaml) and can be easily customised.
### DNS architecture ### DNS architecture
@ -476,7 +476,6 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
<!-- TFDOC OPTS files:1 show_extra:1 --> <!-- TFDOC OPTS files:1 show_extra:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | modules | resources | | name | description | modules | resources |
@ -485,7 +484,7 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | | | [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | <code>dns</code> · <code>dns-response-policy</code> | |
| [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | <code>dns</code> | |
| [landing.tf](./landing.tf) | Landing VPC and related resources. | <code>net-cloudnat</code> · <code>net-vpc</code> · <code>net-vpc-firewall</code> · <code>project</code> | | | [landing.tf](./landing.tf) | Landing VPC and related resources. | <code>net-cloudnat</code> · <code>net-vpc</code> · <code>net-vpc-firewall</code> · <code>project</code> | |
| [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> | | | [main.tf](./main.tf) | Networking folder and hierarchical policy. | <code>folder</code> · <code>net-firewall-policy</code> | |
| [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> | | [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | <code>google_monitoring_alert_policy</code> |
| [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | <code>google_monitoring_dashboard</code> |
| [ncc.tf](./ncc.tf) | None | <code>ncc-spoke-ra</code> | | | [ncc.tf](./ncc.tf) | None | <code>ncc-spoke-ra</code> | |
@ -531,5 +530,4 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS
| [shared_vpc_self_links](outputs.tf#L68) | Shared VPC host projects. | | | | [shared_vpc_self_links](outputs.tf#L68) | Shared VPC host projects. | | |
| [tfvars](outputs.tf#L73) | Terraform variables file for the following stages. | ✓ | | | [tfvars](outputs.tf#L73) | Terraform variables file for the following stages. | ✓ | |
| [vpn_gateway_endpoints](outputs.tf#L79) | External IP Addresses for the GCP VPN gateways. | | | | [vpn_gateway_endpoints](outputs.tf#L79) | External IP Addresses for the GCP VPN gateways. | | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -0,0 +1,37 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
priority: 1000
match:
source_ranges:
- rfc1918
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
priority: 1001
match:
source_ranges:
- healthchecks
layer4_configs:
- protocol: tcp
ports: ["80", "443"]
allow-ssh-from-iap:
description: Enable SSH from IAP
priority: 1002
match:
source_ranges:
- 35.235.240.0/20
layer4_configs:
- protocol: tcp
ports: ["22"]
allow-icmp:
description: Enable ICMP
priority: 1003
match:
source_ranges:
- 0.0.0.0/0
layer4_configs:
- protocol: icmp

View File

@ -1,49 +0,0 @@
# skip boilerplate check
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
action: allow
priority: 1000
ranges:
- $rfc1918
ports:
all: []
target_resources: null
enable_logging: false
allow-healthchecks:
description: Enable HTTP and HTTPS healthchecks
direction: INGRESS
action: allow
priority: 1001
ranges:
- $healthchecks
ports:
tcp: ["80", "443"]
target_resources: null
enable_logging: false
allow-ssh-from-iap:
description: Enable SSH from IAP
direction: INGRESS
action: allow
priority: 1002
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
enable_logging: false
allow-icmp:
description: Enable ICMP
direction: INGRESS
action: allow
priority: 1003
ranges:
- 0.0.0.0/0
ports:
icmp: []
target_resources: null
enable_logging: false

View File

@ -46,12 +46,17 @@ module "folder" {
name = "Networking" name = "Networking"
folder_create = var.folder_ids.networking == null folder_create = var.folder_ids.networking == null
id = var.folder_ids.networking id = var.folder_ids.networking
firewall_policy_factory = { firewall_policy_associations = {
cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" default = module.firewall-policy-default.id
policy_name = var.factories_config.firewall_policy_name }
rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" }
}
firewall_policy_association = { module "firewall-policy-default" {
factory-policy = var.factories_config.firewall_policy_name source = "../../../modules/net-firewall-policy"
name = "net-default"
parent_id = module.folder.id
rules_factory_config = {
cidr_file_path = "${var.factories_config.data_dir}/cidrs.yaml"
ingress_rules_file_path = "${var.factories_config.data_dir}/hierarchical-ingress-rules.yaml"
} }
} }

View File

@ -45,6 +45,7 @@ These modules are used in the examples included in this repository. If you are u
- [Cloud Endpoints](./endpoints) - [Cloud Endpoints](./endpoints)
- [DNS](./dns) - [DNS](./dns)
- [DNS Response Policy](./dns-response-policy/) - [DNS Response Policy](./dns-response-policy/)
- [Firewall policy](./net-firewall-policy)
- [External Application Load Balancer](./net-lb-app-ext/) - [External Application Load Balancer](./net-lb-app-ext/)
- [External Passthrough Network Load Balancer](./net-lb-ext) - [External Passthrough Network Load Balancer](./net-lb-ext)
- [Internal Application Load Balancer](./net-lb-app-int) - [Internal Application Load Balancer](./net-lb-app-int)
@ -55,7 +56,6 @@ These modules are used in the examples included in this repository. If you are u
- [Service Directory](./service-directory) - [Service Directory](./service-directory)
- [VPC](./net-vpc) - [VPC](./net-vpc)
- [VPC firewall](./net-vpc-firewall) - [VPC firewall](./net-vpc-firewall)
- [VPC firewall policy](./net-vpc-firewall-policy)
- [VPN dynamic](./net-vpn-dynamic) - [VPN dynamic](./net-vpn-dynamic)
- [VPC peering](./net-vpc-peering) - [VPC peering](./net-vpc-peering)
- [VPN HA](./net-vpn-ha) - [VPN HA](./net-vpn-ha)

View File

@ -9,6 +9,7 @@ Cloud Run management, with support for IAM roles, revision annotations and optio
- [IAM and environment variables](#iam-and-environment-variables) - [IAM and environment variables](#iam-and-environment-variables)
- [Mounting secrets as volumes](#mounting-secrets-as-volumes) - [Mounting secrets as volumes](#mounting-secrets-as-volumes)
- [Revision annotations](#revision-annotations) - [Revision annotations](#revision-annotations)
- [Second generation execution environment](#second-generation-execution-environment)
- [VPC Access Connector creation](#vpc-access-connector-creation) - [VPC Access Connector creation](#vpc-access-connector-creation)
- [Traffic split](#traffic-split) - [Traffic split](#traffic-split)
- [Eventarc triggers](#eventarc-triggers) - [Eventarc triggers](#eventarc-triggers)
@ -107,6 +108,25 @@ module "cloud_run" {
# tftest modules=1 resources=1 inventory=revision-annotations.yaml # tftest modules=1 resources=1 inventory=revision-annotations.yaml
``` ```
### Second generation execution environment
Second generation execution environment (gen2) can be enabled by setting the `gen2_execution_environment` variable to true:
```hcl
module "cloud_run" {
source = "./fabric/modules/cloud-run"
project_id = var.project_id
name = "hello"
containers = {
hello = {
image = "us-docker.pkg.dev/cloudrun/container/hello"
}
}
gen2_execution_environment = true
}
# tftest modules=1 resources=1 inventory=gen2.yaml
```
### VPC Access Connector creation ### VPC Access Connector creation
If creation of a [VPC Access Connector](https://cloud.google.com/vpc/docs/serverless-vpc-access) is required, use the `vpc_connector_create` variable which also support optional attributes for number of instances, machine type, and throughput (not shown here). The annotation to use the connector will be added automatically. If creation of a [VPC Access Connector](https://cloud.google.com/vpc/docs/serverless-vpc-access) is required, use the `vpc_connector_create` variable which also support optional attributes for number of instances, machine type, and throughput (not shown here). The annotation to use the connector will be added automatically.
@ -313,29 +333,29 @@ module "cloud_run" {
# tftest modules=1 resources=1 inventory=service-account-external.yaml # tftest modules=1 resources=1 inventory=service-account-external.yaml
``` ```
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [name](variables.tf#L130) | Name used for cloud run service. | <code>string</code> | ✓ | | | [name](variables.tf#L136) | Name used for cloud run service. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L145) | Project id used for all resources. | <code>string</code> | ✓ | | | [project_id](variables.tf#L151) | Project id used for all resources. | <code>string</code> | ✓ | |
| [container_concurrency](variables.tf#L18) | Maximum allowed in-flight (concurrent) requests per container of the revision. | <code>string</code> | | <code>null</code> | | [container_concurrency](variables.tf#L18) | Maximum allowed in-flight (concurrent) requests per container of the revision. | <code>string</code> | | <code>null</code> |
| [containers](variables.tf#L24) | Containers in arbitrary key => attributes format. | <code title="map&#40;object&#40;&#123;&#10; image &#61; string&#10; args &#61; optional&#40;list&#40;string&#41;&#41;&#10; command &#61; optional&#40;list&#40;string&#41;&#41;&#10; env &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; env_from_key &#61; optional&#40;map&#40;object&#40;&#123;&#10; key &#61; string&#10; name &#61; string&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; liveness_probe &#61; optional&#40;object&#40;&#123;&#10; action &#61; object&#40;&#123;&#10; grcp &#61; optional&#40;object&#40;&#123;&#10; port &#61; optional&#40;number&#41;&#10; service &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; http_get &#61; optional&#40;object&#40;&#123;&#10; http_headers &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; path &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#10; failure_threshold &#61; optional&#40;number&#41;&#10; initial_delay_seconds &#61; optional&#40;number&#41;&#10; period_seconds &#61; optional&#40;number&#41;&#10; timeout_seconds &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; ports &#61; optional&#40;map&#40;object&#40;&#123;&#10; container_port &#61; optional&#40;number&#41;&#10; name &#61; optional&#40;string&#41;&#10; protocol &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; resources &#61; optional&#40;object&#40;&#123;&#10; limits &#61; optional&#40;object&#40;&#123;&#10; cpu &#61; string&#10; memory &#61; string&#10; &#125;&#41;&#41;&#10; requests &#61; optional&#40;object&#40;&#123;&#10; cpu &#61; string&#10; memory &#61; string&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; startup_probe &#61; optional&#40;object&#40;&#123;&#10; action &#61; object&#40;&#123;&#10; grcp &#61; optional&#40;object&#40;&#123;&#10; port &#61; optional&#40;number&#41;&#10; service &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; http_get &#61; optional&#40;object&#40;&#123;&#10; http_headers &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; path &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; tcp_socket &#61; optional&#40;object&#40;&#123;&#10; port &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#10; failure_threshold &#61; optional&#40;number&#41;&#10; initial_delay_seconds &#61; optional&#40;number&#41;&#10; period_seconds &#61; optional&#40;number&#41;&#10; timeout_seconds &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; volume_mounts &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [containers](variables.tf#L24) | Containers in arbitrary key => attributes format. | <code title="map&#40;object&#40;&#123;&#10; image &#61; string&#10; args &#61; optional&#40;list&#40;string&#41;&#41;&#10; command &#61; optional&#40;list&#40;string&#41;&#41;&#10; env &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; env_from_key &#61; optional&#40;map&#40;object&#40;&#123;&#10; key &#61; string&#10; name &#61; string&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; liveness_probe &#61; optional&#40;object&#40;&#123;&#10; action &#61; object&#40;&#123;&#10; grcp &#61; optional&#40;object&#40;&#123;&#10; port &#61; optional&#40;number&#41;&#10; service &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; http_get &#61; optional&#40;object&#40;&#123;&#10; http_headers &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; path &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#10; failure_threshold &#61; optional&#40;number&#41;&#10; initial_delay_seconds &#61; optional&#40;number&#41;&#10; period_seconds &#61; optional&#40;number&#41;&#10; timeout_seconds &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; ports &#61; optional&#40;map&#40;object&#40;&#123;&#10; container_port &#61; optional&#40;number&#41;&#10; name &#61; optional&#40;string&#41;&#10; protocol &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; resources &#61; optional&#40;object&#40;&#123;&#10; limits &#61; optional&#40;object&#40;&#123;&#10; cpu &#61; string&#10; memory &#61; string&#10; &#125;&#41;&#41;&#10; requests &#61; optional&#40;object&#40;&#123;&#10; cpu &#61; string&#10; memory &#61; string&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10; startup_probe &#61; optional&#40;object&#40;&#123;&#10; action &#61; object&#40;&#123;&#10; grcp &#61; optional&#40;object&#40;&#123;&#10; port &#61; optional&#40;number&#41;&#10; service &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; http_get &#61; optional&#40;object&#40;&#123;&#10; http_headers &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; path &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#10; tcp_socket &#61; optional&#40;object&#40;&#123;&#10; port &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#10; failure_threshold &#61; optional&#40;number&#41;&#10; initial_delay_seconds &#61; optional&#40;number&#41;&#10; period_seconds &#61; optional&#40;number&#41;&#10; timeout_seconds &#61; optional&#40;number&#41;&#10; &#125;&#41;&#41;&#10; volume_mounts &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [eventarc_triggers](variables.tf#L91) | Event arc triggers for different sources. | <code title="object&#40;&#123;&#10; audit_log &#61; optional&#40;map&#40;object&#40;&#123;&#10; method &#61; string&#10; service &#61; string&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; pubsub &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_account_email &#61; optional&#40;string&#41;&#10; service_account_create &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | | [eventarc_triggers](variables.tf#L91) | Event arc triggers for different sources. | <code title="object&#40;&#123;&#10; audit_log &#61; optional&#40;map&#40;object&#40;&#123;&#10; method &#61; string&#10; service &#61; string&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10; pubsub &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; service_account_email &#61; optional&#40;string&#41;&#10; service_account_create &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam](variables.tf#L105) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [gen2_execution_environment](variables.tf#L105) | Use second generation execution environment. | <code>bool</code> | | <code>false</code> |
| [ingress_settings](variables.tf#L111) | Ingress settings. | <code>string</code> | | <code>null</code> | | [iam](variables.tf#L111) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [labels](variables.tf#L124) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [ingress_settings](variables.tf#L117) | Ingress settings. | <code>string</code> | | <code>null</code> |
| [prefix](variables.tf#L135) | Optional prefix used for resource names. | <code>string</code> | | <code>null</code> | | [labels](variables.tf#L130) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [region](variables.tf#L150) | Region used for all resources. | <code>string</code> | | <code>&#34;europe-west1&#34;</code> | | [prefix](variables.tf#L141) | Optional prefix used for resource names. | <code>string</code> | | <code>null</code> |
| [revision_annotations](variables.tf#L156) | Configure revision template annotations. | <code title="object&#40;&#123;&#10; autoscaling &#61; optional&#40;object&#40;&#123;&#10; max_scale &#61; number&#10; min_scale &#61; number&#10; &#125;&#41;&#41;&#10; cloudsql_instances &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; vpcaccess_connector &#61; optional&#40;string&#41;&#10; vpcaccess_egress &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | | [region](variables.tf#L156) | Region used for all resources. | <code>string</code> | | <code>&#34;europe-west1&#34;</code> |
| [revision_name](variables.tf#L171) | Revision name. | <code>string</code> | | <code>null</code> | | [revision_annotations](variables.tf#L162) | Configure revision template annotations. | <code title="object&#40;&#123;&#10; autoscaling &#61; optional&#40;object&#40;&#123;&#10; max_scale &#61; number&#10; min_scale &#61; number&#10; &#125;&#41;&#41;&#10; cloudsql_instances &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10; vpcaccess_connector &#61; optional&#40;string&#41;&#10; vpcaccess_egress &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_account](variables.tf#L177) | Service account email. Unused if service account is auto-created. | <code>string</code> | | <code>null</code> | | [revision_name](variables.tf#L177) | Revision name. | <code>string</code> | | <code>null</code> |
| [service_account_create](variables.tf#L183) | Auto-create service account. | <code>bool</code> | | <code>false</code> | | [service_account](variables.tf#L183) | Service account email. Unused if service account is auto-created. | <code>string</code> | | <code>null</code> |
| [timeout_seconds](variables.tf#L189) | Maximum duration the instance is allowed for responding to a request. | <code>number</code> | | <code>null</code> | | [service_account_create](variables.tf#L189) | Auto-create service account. | <code>bool</code> | | <code>false</code> |
| [traffic](variables.tf#L195) | Traffic steering configuration. If revision name is null the latest revision will be used. | <code title="map&#40;object&#40;&#123;&#10; percent &#61; number&#10; latest &#61; optional&#40;bool&#41;&#10; tag &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [timeout_seconds](variables.tf#L195) | Maximum duration the instance is allowed for responding to a request. | <code>number</code> | | <code>null</code> |
| [volumes](variables.tf#L206) | Named volumes in containers in name => attributes format. | <code title="map&#40;object&#40;&#123;&#10; secret_name &#61; string&#10; default_mode &#61; optional&#40;string&#41;&#10; items &#61; optional&#40;map&#40;object&#40;&#123;&#10; path &#61; string&#10; mode &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [traffic](variables.tf#L201) | Traffic steering configuration. If revision name is null the latest revision will be used. | <code title="map&#40;object&#40;&#123;&#10; percent &#61; number&#10; latest &#61; optional&#40;bool&#41;&#10; tag &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [vpc_connector_create](variables.tf#L220) | Populate this to create a VPC connector. You can then refer to it in the template annotations. | <code title="object&#40;&#123;&#10; ip_cidr_range &#61; optional&#40;string&#41;&#10; vpc_self_link &#61; optional&#40;string&#41;&#10; machine_type &#61; optional&#40;string&#41;&#10; name &#61; optional&#40;string&#41;&#10; instances &#61; optional&#40;object&#40;&#123;&#10; max &#61; optional&#40;number&#41;&#10; min &#61; optional&#40;number&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; throughput &#61; optional&#40;object&#40;&#123;&#10; max &#61; optional&#40;number&#41;&#10; min &#61; optional&#40;number&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; subnet &#61; optional&#40;object&#40;&#123;&#10; name &#61; optional&#40;string&#41;&#10; project_id &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [volumes](variables.tf#L212) | Named volumes in containers in name => attributes format. | <code title="map&#40;object&#40;&#123;&#10; secret_name &#61; string&#10; default_mode &#61; optional&#40;string&#41;&#10; items &#61; optional&#40;map&#40;object&#40;&#123;&#10; path &#61; string&#10; mode &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [vpc_connector_create](variables.tf#L226) | Populate this to create a VPC connector. You can then refer to it in the template annotations. | <code title="object&#40;&#123;&#10; ip_cidr_range &#61; optional&#40;string&#41;&#10; vpc_self_link &#61; optional&#40;string&#41;&#10; machine_type &#61; optional&#40;string&#41;&#10; name &#61; optional&#40;string&#41;&#10; instances &#61; optional&#40;object&#40;&#123;&#10; max &#61; optional&#40;number&#41;&#10; min &#61; optional&#40;number&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; throughput &#61; optional&#40;object&#40;&#123;&#10; max &#61; optional&#40;number&#41;&#10; min &#61; optional&#40;number&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; subnet &#61; optional&#40;object&#40;&#123;&#10; name &#61; optional&#40;string&#41;&#10; project_id &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
## Outputs ## Outputs
@ -348,5 +368,4 @@ module "cloud_run" {
| [service_account_iam_email](outputs.tf#L38) | Service account email. | | | [service_account_iam_email](outputs.tf#L38) | Service account email. | |
| [service_name](outputs.tf#L46) | Cloud Run service name. | | | [service_name](outputs.tf#L46) | Cloud Run service name. | |
| [vpc_connector](outputs.tf#L52) | VPC connector resource if created. | | | [vpc_connector](outputs.tf#L52) | VPC connector resource if created. | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -69,6 +69,9 @@ locals {
var.revision_annotations.vpcaccess_egress var.revision_annotations.vpcaccess_egress
) )
}, },
var.gen2_execution_environment ? {
"run.googleapis.com/execution-environment" = "gen2"
} : {},
) )
revision_name = ( revision_name = (
try(var.revision_name, null) == null try(var.revision_name, null) == null

View File

@ -102,6 +102,12 @@ variable "eventarc_triggers" {
default = {} default = {}
} }
variable "gen2_execution_environment" {
description = "Use second generation execution environment."
type = bool
default = false
}
variable "iam" { variable "iam" {
description = "IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format." description = "IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format."
type = map(list(string)) type = map(list(string))

View File

@ -193,7 +193,7 @@ module "db" {
| [network](variables.tf#L130) | VPC self link where the instances will be deployed. Private Service Networking must be enabled and configured in this VPC. | <code>string</code> | ✓ | | | [network](variables.tf#L130) | VPC self link where the instances will be deployed. Private Service Networking must be enabled and configured in this VPC. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L151) | The ID of the project where this instances will be created. | <code>string</code> | ✓ | | | [project_id](variables.tf#L151) | The ID of the project where this instances will be created. | <code>string</code> | ✓ | |
| [region](variables.tf#L156) | Region of the primary instance. | <code>string</code> | ✓ | | | [region](variables.tf#L156) | Region of the primary instance. | <code>string</code> | ✓ | |
| [tier](variables.tf#L176) | The machine type to use for the instances. | <code>string</code> | ✓ | | | [tier](variables.tf#L182) | The machine type to use for the instances. | <code>string</code> | ✓ | |
| [allocated_ip_ranges](variables.tf#L17) | (Optional)The name of the allocated ip range for the private ip CloudSQL instance. For example: \"google-managed-services-default\". If set, the instance ip will be created in the allocated range. The range name must comply with RFC 1035. Specifically, the name must be 1-63 characters long and match the regular expression a-z?. | <code title="object&#40;&#123;&#10; primary &#61; optional&#40;string&#41;&#10; replica &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | | [allocated_ip_ranges](variables.tf#L17) | (Optional)The name of the allocated ip range for the private ip CloudSQL instance. For example: \"google-managed-services-default\". If set, the instance ip will be created in the allocated range. The range name must comply with RFC 1035. Specifically, the name must be 1-63 characters long and match the regular expression a-z?. | <code title="object&#40;&#123;&#10; primary &#61; optional&#40;string&#41;&#10; replica &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [authorized_networks](variables.tf#L26) | Map of NAME=>CIDR_RANGE to allow to connect to the database(s). | <code>map&#40;string&#41;</code> | | <code>null</code> | | [authorized_networks](variables.tf#L26) | Map of NAME=>CIDR_RANGE to allow to connect to the database(s). | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [availability_type](variables.tf#L32) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | <code>string</code> | | <code>&#34;ZONAL&#34;</code> | | [availability_type](variables.tf#L32) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | <code>string</code> | | <code>&#34;ZONAL&#34;</code> |
@ -210,8 +210,9 @@ module "db" {
| [postgres_client_certificates](variables.tf#L135) | Map of cert keys connect to the application(s) using public IP. | <code>list&#40;string&#41;</code> | | <code>null</code> | | [postgres_client_certificates](variables.tf#L135) | Map of cert keys connect to the application(s) using public IP. | <code>list&#40;string&#41;</code> | | <code>null</code> |
| [prefix](variables.tf#L141) | Optional prefix used to generate instance names. | <code>string</code> | | <code>null</code> | | [prefix](variables.tf#L141) | Optional prefix used to generate instance names. | <code>string</code> | | <code>null</code> |
| [replicas](variables.tf#L161) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | <code title="map&#40;object&#40;&#123;&#10; region &#61; string&#10; encryption_key_name &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [replicas](variables.tf#L161) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | <code title="map&#40;object&#40;&#123;&#10; region &#61; string&#10; encryption_key_name &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [root_password](variables.tf#L170) | Root password of the Cloud SQL instance. Required for MS SQL Server. | <code>string</code> | | <code>null</code> | | [require_ssl](variables.tf#L170) | Enable SSL connections only. | <code>bool</code> | | <code>null</code> |
| [users](variables.tf#L181) | Map of users to create in the primary instance (and replicated to other replicas) in the format USER=>PASSWORD. For MySQL, anything afterr the first `@` (if persent) will be used as the user's host. Set PASSWORD to null if you want to get an autogenerated password. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [root_password](variables.tf#L176) | Root password of the Cloud SQL instance. Required for MS SQL Server. | <code>string</code> | | <code>null</code> |
| [users](variables.tf#L187) | Map of users to create in the primary instance (and replicated to other replicas) in the format USER=>PASSWORD. For MySQL, anything afterr the first `@` (if persent) will be used as the user's host. Set PASSWORD to null if you want to get an autogenerated password. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -64,6 +64,7 @@ resource "google_sql_database_instance" "primary" {
ipv4_enabled = var.ipv4_enabled ipv4_enabled = var.ipv4_enabled
private_network = var.network private_network = var.network
allocated_ip_range = var.allocated_ip_ranges.primary allocated_ip_range = var.allocated_ip_ranges.primary
require_ssl = var.require_ssl
dynamic "authorized_networks" { dynamic "authorized_networks" {
for_each = var.authorized_networks != null ? var.authorized_networks : {} for_each = var.authorized_networks != null ? var.authorized_networks : {}
iterator = network iterator = network

View File

@ -167,6 +167,12 @@ variable "replicas" {
default = {} default = {}
} }
variable "require_ssl" {
description = "Enable SSL connections only."
type = bool
default = null
}
variable "root_password" { variable "root_password" {
description = "Root password of the Cloud SQL instance. Required for MS SQL Server." description = "Root password of the Cloud SQL instance. Required for MS SQL Server."
type = string type = string

View File

@ -6,8 +6,23 @@ This module can be coupled with the [`compute-vm`](../compute-vm) module which c
Stateful disks can be created directly, as shown in the last example below. Stateful disks can be created directly, as shown in the last example below.
<!-- BEGIN TOC -->
- [Examples](#examples)
- [Simple Example](#simple-example)
- [Multiple Versions](#multiple-versions)
- [Health Check and Autohealing Policies](#health-check-and-autohealing-policies)
- [Autoscaling](#autoscaling)
- [Update Policy](#update-policy)
- [Stateful MIGs - MIG Config](#stateful-migs-mig-config)
- [Stateful MIGs - Instance Config](#stateful-migs-instance-config)
- [Variables](#variables)
- [Outputs](#outputs)
<!-- END TOC -->
## Examples ## Examples
### Simple Example
This example shows how to manage a simple MIG that leverages the `compute-vm` module to manage the underlying instance template. The following sub-examples will only show how to enable specific features of this module, and won't replicate the combined setup. This example shows how to manage a simple MIG that leverages the `compute-vm` module to manage the underlying instance template. The following sub-examples will only show how to enable specific features of this module, and won't replicate the combined setup.
```hcl ```hcl
@ -49,7 +64,7 @@ module "nginx-mig" {
# tftest modules=2 resources=2 inventory=simple.yaml # tftest modules=2 resources=2 inventory=simple.yaml
``` ```
### Multiple versions ### Multiple Versions
If multiple versions are desired, use more `compute-vm` instances for the additional templates used in each version (not shown here), and reference them like this: If multiple versions are desired, use more `compute-vm` instances for the additional templates used in each version (not shown here), and reference them like this:
@ -100,7 +115,7 @@ module "nginx-mig" {
# tftest modules=2 resources=2 # tftest modules=2 resources=2
``` ```
### Health check and autohealing policies ### Health Check and Autohealing Policies
Autohealing policies can use an externally defined health check, or have this module auto-create one: Autohealing policies can use an externally defined health check, or have this module auto-create one:
@ -205,7 +220,7 @@ module "nginx-mig" {
# tftest modules=2 resources=3 inventory=autoscaling.yaml # tftest modules=2 resources=3 inventory=autoscaling.yaml
``` ```
### Update policy ### Update Policy
```hcl ```hcl
module "cos-nginx" { module "cos-nginx" {
@ -262,7 +277,7 @@ You can configure a disk defined in the instance template to be stateful for al
An example using only the configuration at the MIG level can be seen below. An example using only the configuration at the MIG level can be seen below.
Note that when referencing the stateful disk, you use `device_name` and not `disk_name`. Note that when referencing the stateful disk, you use `device_name` and not `disk_name`. Specifying an existing disk in the template (and stateful config) only allows a single instance to be managed by the MIG, typically coupled with an autohealing policy (shown in the examples above).
```hcl ```hcl
module "cos-nginx" { module "cos-nginx" {
@ -270,16 +285,15 @@ module "cos-nginx" {
} }
module "nginx-template" { module "nginx-template" {
source = "./fabric/modules/compute-vm" source = "./fabric/modules/compute-vm"
project_id = var.project_id project_id = "my-prj"
name = "nginx-template" name = "nginx-template"
zone = "europe-west1-b" zone = "europe-west8-b"
tags = ["http-server", "ssh"] tags = ["http-server", "ssh"]
instance_type = "e2-small"
network_interfaces = [{ network_interfaces = [{
network = var.vpc.self_link network = var.vpc.self_link
subnetwork = var.subnet.self_link subnetwork = var.subnet.self_link
nat = false
addresses = null
}] }]
boot_disk = { boot_disk = {
initialize_params = { initialize_params = {
@ -287,15 +301,10 @@ module "nginx-template" {
} }
} }
attached_disks = [{ attached_disks = [{
name = "repd-1"
size = null
source_type = "attach" source_type = "attach"
source = "regions/${var.region}/disks/repd-test-1" name = "data-1"
options = { size = 10
mode = "READ_ONLY" source = "test-data-1"
replica_zone = "${var.region}-c"
type = "PERSISTENT"
}
}] }]
create_template = true create_template = true
metadata = { metadata = {
@ -305,34 +314,21 @@ module "nginx-template" {
module "nginx-mig" { module "nginx-mig" {
source = "./fabric/modules/compute-mig" source = "./fabric/modules/compute-mig"
project_id = "my-project" project_id = "my-prj"
location = "europe-west1-b" location = "europe-west8-b"
name = "mig-test" name = "mig-test-2"
target_size = 3 target_size = 1
instance_template = module.nginx-template.template.self_link instance_template = module.nginx-template.template.self_link
autoscaler_config = {
max_replicas = 3
min_replicas = 1
cooldown_period = 30
scaling_signals = {
cpu_utilization = {
target = 0.65
}
}
}
stateful_disks = { stateful_disks = {
repd-1 = false data-1 = false
} }
} }
# tftest modules=2 resources=3 # tftest modules=2 resources=2
``` ```
### Stateful MIGs - Instance Config ### Stateful MIGs - Instance Config
Here is an example defining the stateful config at the instance level. Here is an example defining the stateful config at the instance level. As in the example above, specifying an existing disk in the template (and stateful config) only allows a single instance to be managed by the MIG, typically coupled with an autohealing policy (shown in the examples above).
Note that you will need to know the instance name in order to use this configuration.
```hcl ```hcl
module "cos-nginx" { module "cos-nginx" {
@ -340,16 +336,15 @@ module "cos-nginx" {
} }
module "nginx-template" { module "nginx-template" {
source = "./fabric/modules/compute-vm" source = "./fabric/modules/compute-vm"
project_id = var.project_id project_id = "my-prj"
name = "nginx-template" name = "nginx-template"
zone = "europe-west1-b" zone = "europe-west8-b"
tags = ["http-server", "ssh"] tags = ["http-server", "ssh"]
instance_type = "e2-small"
network_interfaces = [{ network_interfaces = [{
network = var.vpc.self_link network = var.vpc.self_link
subnetwork = var.subnet.self_link subnetwork = var.subnet.self_link
nat = false
addresses = null
}] }]
boot_disk = { boot_disk = {
initialize_params = { initialize_params = {
@ -357,15 +352,10 @@ module "nginx-template" {
} }
} }
attached_disks = [{ attached_disks = [{
name = "repd-1"
size = null
source_type = "attach" source_type = "attach"
source = "regions/${var.region}/disks/repd-test-1" name = "data-1"
options = { size = 10
mode = "READ_ONLY" source = "test-data-1"
replica_zone = "${var.region}-c"
type = "PERSISTENT"
}
}] }]
create_template = true create_template = true
metadata = { metadata = {
@ -375,30 +365,18 @@ module "nginx-template" {
module "nginx-mig" { module "nginx-mig" {
source = "./fabric/modules/compute-mig" source = "./fabric/modules/compute-mig"
project_id = "my-project" project_id = "my-prj"
location = "europe-west1-b" location = "europe-west8-b"
name = "mig-test" name = "mig-test"
target_size = 3
instance_template = module.nginx-template.template.self_link instance_template = module.nginx-template.template.self_link
autoscaler_config = {
max_replicas = 3
min_replicas = 1
cooldown_period = 30
scaling_signals = {
cpu_utilization = {
target = 0.65
}
}
}
stateful_config = { stateful_config = {
# name needs to match a MIG instance name
instance-1 = { instance-1 = {
minimal_action = "NONE", minimal_action = "NONE",
most_disruptive_allowed_action = "REPLACE" most_disruptive_allowed_action = "REPLACE"
preserved_state = { preserved_state = {
disks = { disks = {
persistent-disk-1 = { data-1 = {
source = "test-disk", source = "projects/my-prj/zones/europe-west8-b/disks/test-data-1"
} }
} }
metadata = { metadata = {
@ -408,8 +386,7 @@ module "nginx-mig" {
} }
} }
} }
# tftest modules=2 resources=4 inventory=stateful.yaml # tftest modules=2 resources=3 inventory=stateful.yaml
``` ```
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->

View File

@ -22,7 +22,7 @@ resource "google_compute_per_instance_config" "default" {
zone = var.location zone = var.location
name = each.key name = each.key
instance_group_manager = try( instance_group_manager = try(
google_compute_instance_group_manager.default.0.id, null google_compute_instance_group_manager.default.0.name, null
) )
minimal_action = each.value.minimal_action minimal_action = each.value.minimal_action
most_disruptive_allowed_action = each.value.most_disruptive_action most_disruptive_allowed_action = each.value.most_disruptive_action
@ -59,7 +59,7 @@ resource "google_compute_region_per_instance_config" "default" {
region = var.location region = var.location
name = each.key name = each.key
region_instance_group_manager = try( region_instance_group_manager = try(
google_compute_region_instance_group_manager.default.0.id, null google_compute_region_instance_group_manager.default.0.name, null
) )
minimal_action = each.value.minimal_action minimal_action = each.value.minimal_action
most_disruptive_allowed_action = each.value.most_disruptive_action most_disruptive_allowed_action = each.value.most_disruptive_action

View File

@ -626,7 +626,6 @@ module "instance" {
# tftest modules=1 resources=5 inventory=snapshot-schedule-create.yaml # tftest modules=1 resources=5 inventory=snapshot-schedule-create.yaml
``` ```
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
@ -636,7 +635,7 @@ module "instance" {
| [project_id](variables.tf#L277) | Project id. | <code>string</code> | ✓ | | | [project_id](variables.tf#L277) | Project id. | <code>string</code> | ✓ | |
| [zone](variables.tf#L379) | Compute zone. | <code>string</code> | ✓ | | | [zone](variables.tf#L379) | Compute zone. | <code>string</code> | ✓ | |
| [attached_disk_defaults](variables.tf#L17) | Defaults for attached disks options. | <code title="object&#40;&#123;&#10; auto_delete &#61; optional&#40;bool, false&#41;&#10; mode &#61; string&#10; replica_zone &#61; string&#10; type &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; auto_delete &#61; true&#10; mode &#61; &#34;READ_WRITE&#34;&#10; replica_zone &#61; null&#10; type &#61; &#34;pd-balanced&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | [attached_disk_defaults](variables.tf#L17) | Defaults for attached disks options. | <code title="object&#40;&#123;&#10; auto_delete &#61; optional&#40;bool, false&#41;&#10; mode &#61; string&#10; replica_zone &#61; string&#10; type &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; auto_delete &#61; true&#10; mode &#61; &#34;READ_WRITE&#34;&#10; replica_zone &#61; null&#10; type &#61; &#34;pd-balanced&#34;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [attached_disks](variables.tf#L38) | Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null. | <code title="list&#40;object&#40;&#123;&#10; name &#61; string&#10; device_name &#61; optional&#40;string&#41;&#10; size &#61; string&#10; snapshot_schedule &#61; optional&#40;string&#41;&#10; source &#61; optional&#40;string&#41;&#10; source_type &#61; optional&#40;string&#41;&#10; options &#61; optional&#40;&#10; object&#40;&#123;&#10; auto_delete &#61; optional&#40;bool, false&#41;&#10; mode &#61; optional&#40;string, &#34;READ_WRITE&#34;&#41;&#10; replica_zone &#61; optional&#40;string&#41;&#10; type &#61; optional&#40;string, &#34;pd-balanced&#34;&#41;&#10; &#125;&#41;,&#10; &#123;&#10; auto_delete &#61; true&#10; mode &#61; &#34;READ_WRITE&#34;&#10; replica_zone &#61; null&#10; type &#61; &#34;pd-balanced&#34;&#10; &#125;&#10; &#41;&#10;&#125;&#41;&#41;">list&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#91;&#93;</code> | | [attached_disks](variables.tf#L37) | Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null. | <code title="list&#40;object&#40;&#123;&#10; name &#61; string&#10; device_name &#61; optional&#40;string&#41;&#10; size &#61; string&#10; snapshot_schedule &#61; optional&#40;string&#41;&#10; source &#61; optional&#40;string&#41;&#10; source_type &#61; optional&#40;string&#41;&#10; options &#61; optional&#40;&#10; object&#40;&#123;&#10; auto_delete &#61; optional&#40;bool, false&#41;&#10; mode &#61; optional&#40;string, &#34;READ_WRITE&#34;&#41;&#10; replica_zone &#61; optional&#40;string&#41;&#10; type &#61; optional&#40;string, &#34;pd-balanced&#34;&#41;&#10; &#125;&#41;,&#10; &#123;&#10; auto_delete &#61; true&#10; mode &#61; &#34;READ_WRITE&#34;&#10; replica_zone &#61; null&#10; type &#61; &#34;pd-balanced&#34;&#10; &#125;&#10; &#41;&#10;&#125;&#41;&#41;">list&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#91;&#93;</code> |
| [boot_disk](variables.tf#L83) | Boot disk properties. | <code title="object&#40;&#123;&#10; auto_delete &#61; optional&#40;bool, true&#41;&#10; snapshot_schedule &#61; optional&#40;string&#41;&#10; source &#61; optional&#40;string&#41;&#10; initialize_params &#61; optional&#40;object&#40;&#123;&#10; image &#61; optional&#40;string, &#34;projects&#47;debian-cloud&#47;global&#47;images&#47;family&#47;debian-11&#34;&#41;&#10; size &#61; optional&#40;number, 10&#41;&#10; type &#61; optional&#40;string, &#34;pd-balanced&#34;&#41;&#10; &#125;&#41;&#41;&#10; use_independent_disk &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; initialize_params &#61; &#123;&#125;&#10;&#125;">&#123;&#8230;&#125;</code> | | [boot_disk](variables.tf#L83) | Boot disk properties. | <code title="object&#40;&#123;&#10; auto_delete &#61; optional&#40;bool, true&#41;&#10; snapshot_schedule &#61; optional&#40;string&#41;&#10; source &#61; optional&#40;string&#41;&#10; initialize_params &#61; optional&#40;object&#40;&#123;&#10; image &#61; optional&#40;string, &#34;projects&#47;debian-cloud&#47;global&#47;images&#47;family&#47;debian-11&#34;&#41;&#10; size &#61; optional&#40;number, 10&#41;&#10; type &#61; optional&#40;string, &#34;pd-balanced&#34;&#41;&#10; &#125;&#41;&#41;&#10; use_independent_disk &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; initialize_params &#61; &#123;&#125;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [can_ip_forward](variables.tf#L117) | Enable IP forwarding. | <code>bool</code> | | <code>false</code> | | [can_ip_forward](variables.tf#L117) | Enable IP forwarding. | <code>bool</code> | | <code>false</code> |
| [confidential_compute](variables.tf#L123) | Enable Confidential Compute for these instances. | <code>bool</code> | | <code>false</code> | | [confidential_compute](variables.tf#L123) | Enable Confidential Compute for these instances. | <code>bool</code> | | <code>false</code> |
@ -678,7 +677,6 @@ module "instance" {
| [service_account_iam_email](outputs.tf#L74) | Service account email. | | | [service_account_iam_email](outputs.tf#L74) | Service account email. | |
| [template](outputs.tf#L82) | Template resource. | | | [template](outputs.tf#L82) | Template resource. | |
| [template_name](outputs.tf#L87) | Template name. | | | [template_name](outputs.tf#L87) | Template name. | |
<!-- END TFDOC --> <!-- END TFDOC -->
## TODO ## TODO

View File

@ -28,7 +28,6 @@ variable "attached_disk_defaults" {
replica_zone = null replica_zone = null
type = "pd-balanced" type = "pd-balanced"
} }
validation { validation {
condition = var.attached_disk_defaults.mode == "READ_WRITE" || !var.attached_disk_defaults.auto_delete condition = var.attached_disk_defaults.mode == "READ_WRITE" || !var.attached_disk_defaults.auto_delete
error_message = "auto_delete can only be specified on READ_WRITE disks." error_message = "auto_delete can only be specified on READ_WRITE disks."
@ -38,8 +37,9 @@ variable "attached_disk_defaults" {
variable "attached_disks" { variable "attached_disks" {
description = "Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null." description = "Additional disks, if options is null defaults will be used in its place. Source type is one of 'image' (zonal disks in vms and template), 'snapshot' (vm), 'existing', and null."
type = list(object({ type = list(object({
name = string name = string
device_name = optional(string) device_name = optional(string)
# TODO: size can be null when source_type is attach
size = string size = string
snapshot_schedule = optional(string) snapshot_schedule = optional(string)
source = optional(string) source = optional(string)

View File

@ -4,6 +4,15 @@ This module simplifies the creation of [Data Catalog](https://cloud.google.com/d
Note: Data Catalog is still in beta, hence this module currently uses the beta provider. Note: Data Catalog is still in beta, hence this module currently uses the beta provider.
<!-- BEGIN TOC -->
- [Examples](#examples)
- [Simple Taxonomy with policy tags](#simple-taxonomy-with-policy-tags)
- [Taxonomy with IAM binding](#taxonomy-with-iam-binding)
- [Variables](#variables)
- [Outputs](#outputs)
- [TODO](#todo)
<!-- END TOC -->
## Examples ## Examples
### Simple Taxonomy with policy tags ### Simple Taxonomy with policy tags
@ -43,25 +52,32 @@ module "cmn-dc" {
iam = { iam = {
"roles/datacatalog.categoryAdmin" = ["group:GROUP_NAME@example.com"] "roles/datacatalog.categoryAdmin" = ["group:GROUP_NAME@example.com"]
} }
iam_members = {
am1-admin = {
member = "user:am1@example.com"
role = "roles/datacatalog.categoryAdmin"
}
}
} }
# tftest modules=1 resources=6 # tftest modules=1 resources=7
``` ```
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [name](variables.tf#L59) | Name of this taxonomy. | <code>string</code> | ✓ | | | [name](variables.tf#L69) | Name of this taxonomy. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L74) | GCP project id. | <code></code> | ✓ | | | [project_id](variables.tf#L84) | GCP project id. | <code></code> | ✓ | |
| [activated_policy_types](variables.tf#L17) | A list of policy types that are activated for this taxonomy. | <code>list&#40;string&#41;</code> | | <code>&#91;&#34;FINE_GRAINED_ACCESS_CONTROL&#34;&#93;</code> | | [activated_policy_types](variables.tf#L17) | A list of policy types that are activated for this taxonomy. | <code>list&#40;string&#41;</code> | | <code>&#91;&#34;FINE_GRAINED_ACCESS_CONTROL&#34;&#93;</code> |
| [description](variables.tf#L23) | Description of this taxonomy. | <code>string</code> | | <code>&#34;Taxonomy - Terraform managed&#34;</code> | | [description](variables.tf#L23) | Description of this taxonomy. | <code>string</code> | | <code>&#34;Taxonomy - Terraform managed&#34;</code> |
| [group_iam](variables.tf#L29) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [group_iam](variables.tf#L29) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam](variables.tf#L35) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam](variables.tf#L35) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive](variables.tf#L41) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive](variables.tf#L41) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive_members](variables.tf#L47) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive_members](variables.tf#L47) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [location](variables.tf#L53) | Data Catalog Taxonomy location. | <code>string</code> | | <code>&#34;eu&#34;</code> | | [iam_members](variables.tf#L53) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [prefix](variables.tf#L64) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> | | [location](variables.tf#L63) | Data Catalog Taxonomy location. | <code>string</code> | | <code>&#34;eu&#34;</code> |
| [tags](variables.tf#L78) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [prefix](variables.tf#L74) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
| [tags](variables.tf#L88) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs ## Outputs

View File

@ -58,9 +58,9 @@ locals {
resource "google_data_catalog_taxonomy_iam_binding" "authoritative" { resource "google_data_catalog_taxonomy_iam_binding" "authoritative" {
provider = google-beta provider = google-beta
for_each = local.iam for_each = local.iam
taxonomy = google_data_catalog_taxonomy.default.id
role = each.key role = each.key
members = each.value members = each.value
taxonomy = google_data_catalog_taxonomy.default.id
} }
resource "google_data_catalog_taxonomy_iam_member" "additive" { resource "google_data_catalog_taxonomy_iam_member" "additive" {
@ -70,9 +70,16 @@ resource "google_data_catalog_taxonomy_iam_member" "additive" {
? local.iam_additive ? local.iam_additive
: {} : {}
) )
taxonomy = google_data_catalog_taxonomy.default.id
role = each.value.role role = each.value.role
member = each.value.member member = each.value.member
}
resource "google_data_catalog_taxonomy_iam_member" "members" {
for_each = var.iam_members
taxonomy = google_data_catalog_taxonomy.default.id taxonomy = google_data_catalog_taxonomy.default.id
role = each.value.role
member = each.value.member
} }
resource "google_data_catalog_policy_tag_iam_binding" "authoritative" { resource "google_data_catalog_policy_tag_iam_binding" "authoritative" {
@ -80,7 +87,6 @@ resource "google_data_catalog_policy_tag_iam_binding" "authoritative" {
for_each = { for_each = {
for v in local.tags_iam : "${v.tag}.${v.role}" => v for v in local.tags_iam : "${v.tag}.${v.role}" => v
} }
policy_tag = google_data_catalog_policy_tag.default[each.value.tag].name policy_tag = google_data_catalog_policy_tag.default[each.value.tag].name
role = each.value.role role = each.value.role
members = each.value.members members = each.value.members

View File

@ -50,6 +50,16 @@ variable "iam_additive_members" {
default = {} default = {}
} }
variable "iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
}))
nullable = false
default = {}
}
variable "location" { variable "location" {
description = "Data Catalog Taxonomy location." description = "Data Catalog Taxonomy location."
type = string type = string

View File

@ -2,9 +2,20 @@
This module manages the creation of Dataplex DataScan resources. This module manages the creation of Dataplex DataScan resources.
<!-- BEGIN TOC -->
- [Data Profiling](#data-profiling)
- [Data Quality](#data-quality)
- [Data Source](#data-source)
- [Execution Schedule](#execution-schedule)
- [IAM](#iam)
- [TODO](#todo)
- [Variables](#variables)
- [Outputs](#outputs)
<!-- END TOC -->
## Data Profiling ## Data Profiling
This example shows how to create a Data Profiling scan. To create an Data Profiling scan, provide the `data_profile_spec` input arguments as documented in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataProfileSpec. This example shows how to create a Data Profiling scan. To create an Data Profiling scan, provide the `data_profile_spec` input arguments as documented in <https://cloud.google.com/dataplex/docs/reference/rest/v1/DataProfileSpec>.
```hcl ```hcl
module "dataplex-datascan" { module "dataplex-datascan" {
@ -30,9 +41,9 @@ module "dataplex-datascan" {
## Data Quality ## Data Quality
To create an Data Quality scan, provide the `data_quality_spec` input arguments as documented in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. To create an Data Quality scan, provide the `data_quality_spec` input arguments as documented in <https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec>.
Documentation for the supported rule types and rule specifications can be found in https://cloud.example.com/dataplex/docs/reference/rest/v1/DataQualityRule. Documentation for the supported rule types and rule specifications can be found in <https://cloud.example.com/dataplex/docs/reference/rest/v1/DataQualityRule>.
This example shows how to create a Data Quality scan. This example shows how to create a Data Quality scan.
@ -304,6 +315,7 @@ rules:
The input variable 'data' is required to create a DataScan. This value is immutable. Once it is set, you cannot change the DataScan to another source. The input variable 'data' is required to create a DataScan. This value is immutable. Once it is set, you cannot change the DataScan to another source.
The input variable 'data' should be an object containing a single key-value pair that can be one of: The input variable 'data' should be an object containing a single key-value pair that can be one of:
* `entity`: The Dataplex entity that represents the data source (e.g. BigQuery table) for DataScan, of the form: `projects/{project_number}/locations/{locationId}/lakes/{lakeId}/zones/{zoneId}/entities/{entityId}`. * `entity`: The Dataplex entity that represents the data source (e.g. BigQuery table) for DataScan, of the form: `projects/{project_number}/locations/{locationId}/lakes/{lakeId}/zones/{zoneId}/entities/{entityId}`.
* `resource`: The service-qualified full resource name of the cloud resource for a DataScan job to scan against. The field could be: BigQuery table of type "TABLE" for DataProfileScan/DataQualityScan format, e.g: `//bigquery.googleapis.com/projects/PROJECT_ID/datasets/DATASET_ID/tables/TABLE_ID`. * `resource`: The service-qualified full resource name of the cloud resource for a DataScan job to scan against. The field could be: BigQuery table of type "TABLE" for DataProfileScan/DataQualityScan format, e.g: `//bigquery.googleapis.com/projects/PROJECT_ID/datasets/DATASET_ID/tables/TABLE_ID`.
@ -368,17 +380,17 @@ module "dataplex-datascan" {
## IAM ## IAM
There are three mutually exclusive ways of managing IAM in this module IAM is managed via several variables that implement different levels of control:
- non-authoritative via the `iam_additive` and `iam_additive_members` variables, where bindings created outside this module will coexist with those managed here * `group_iam` and `iam` configure authoritative bindings that manage individual roles exclusively, mapping to the [`google_project_iam_binding`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_binding) resource
- authoritative via the `group_iam` and `iam` variables, where bindings created outside this module (eg in the console) will be removed at each `terraform apply` cycle if the same role is also managed here * `iam_additive`, `iam_additive_members` and `iam_members` configure additive bindings that only manage individual role/member pairs, mapping to the [`google_project_iam_member`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_member) resource
- authoritative policy via the `iam_policy` variable, where any binding created outside this module (eg in the console) will be removed at each `terraform apply` cycle regardless of the role * `iam_policy` which controls the entire IAM policy for the project, where any binding created outside this module (eg in the console) will be removed at each `terraform apply` cycle regardless of the role
The authoritative and additive approaches can be used together, provided different roles are managed by each. The IAM policy is incompatible with the other approaches, and must be used with extreme care. The authoritative and additive approaches can be used together, provided different roles are managed by each. The IAM policy is incompatible with the other approaches, and must be used with extreme care.
Some care must also be taken with the `group_iam` variable (and in some situations with the additive variables) to ensure that variable keys are static values, so that Terraform is able to compute the dependency graph. Some care must also be taken with the `group_iam` and `iam_additive_*` variables to ensure that variable keys are static values, so that Terraform is able to compute the dependency graph. For additive roles `iam_members` ensures that no dynamic values are used in the internal loop.
An example is provided beow for using `group_iam` and `iam` variables. An example is provided below for using some of these variables.
```hcl ```hcl
module "dataplex-datascan" { module "dataplex-datascan" {
@ -404,8 +416,14 @@ module "dataplex-datascan" {
"roles/dataplex.dataScanViewer" "roles/dataplex.dataScanViewer"
] ]
} }
iam_members = {
am1-viewer = {
member = "user:am1@example.com"
role = "roles/dataplex.dataScanViewer"
}
}
} }
# tftest modules=1 resources=4 inventory=datascan_iam.yaml # tftest modules=1 resources=5 inventory=datascan_iam.yaml
``` ```
## TODO ## TODO
@ -415,9 +433,9 @@ module "dataplex-datascan" {
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [data](variables.tf#L17) | The data source for DataScan. The source can be either a Dataplex `entity` or a BigQuery `resource`. | <code title="object&#40;&#123;&#10; entity &#61; optional&#40;string&#41;&#10; resource &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | [data](variables.tf#L17) | The data source for DataScan. The source can be either a Dataplex `entity` or a BigQuery `resource`. | <code title="object&#40;&#123;&#10; entity &#61; optional&#40;string&#41;&#10; resource &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [name](variables.tf#L146) | Name of Dataplex Scan. | <code>string</code> | ✓ | | | [name](variables.tf#L156) | Name of Dataplex Scan. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L157) | The ID of the project where the Dataplex DataScan will be created. | <code>string</code> | ✓ | | | [project_id](variables.tf#L167) | The ID of the project where the Dataplex DataScan will be created. | <code>string</code> | ✓ | |
| [region](variables.tf#L162) | Region for the Dataplex DataScan. | <code>string</code> | ✓ | | | [region](variables.tf#L172) | Region for the Dataplex DataScan. | <code>string</code> | ✓ | |
| [data_profile_spec](variables.tf#L29) | DataProfileScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataProfileSpec. | <code title="object&#40;&#123;&#10; sampling_percent &#61; optional&#40;number&#41;&#10; row_filter &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [data_profile_spec](variables.tf#L29) | DataProfileScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataProfileSpec. | <code title="object&#40;&#123;&#10; sampling_percent &#61; optional&#40;number&#41;&#10; row_filter &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [data_quality_spec](variables.tf#L38) | DataQualityScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. | <code title="object&#40;&#123;&#10; sampling_percent &#61; optional&#40;number&#41;&#10; row_filter &#61; optional&#40;string&#41;&#10; rules &#61; list&#40;object&#40;&#123;&#10; column &#61; optional&#40;string&#41;&#10; ignore_null &#61; optional&#40;bool, null&#41;&#10; dimension &#61; string&#10; threshold &#61; optional&#40;number&#41;&#10; non_null_expectation &#61; optional&#40;object&#40;&#123;&#125;&#41;&#41;&#10; range_expectation &#61; optional&#40;object&#40;&#123;&#10; min_value &#61; optional&#40;number&#41;&#10; max_value &#61; optional&#40;number&#41;&#10; strict_min_enabled &#61; optional&#40;bool&#41;&#10; strict_max_enabled &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#10; regex_expectation &#61; optional&#40;object&#40;&#123;&#10; regex &#61; string&#10; &#125;&#41;&#41;&#10; set_expectation &#61; optional&#40;object&#40;&#123;&#10; values &#61; list&#40;string&#41;&#10; &#125;&#41;&#41;&#10; uniqueness_expectation &#61; optional&#40;object&#40;&#123;&#125;&#41;&#41;&#10; statistic_range_expectation &#61; optional&#40;object&#40;&#123;&#10; statistic &#61; string&#10; min_value &#61; optional&#40;number&#41;&#10; max_value &#61; optional&#40;number&#41;&#10; strict_min_enabled &#61; optional&#40;bool&#41;&#10; strict_max_enabled &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#10; row_condition_expectation &#61; optional&#40;object&#40;&#123;&#10; sql_expression &#61; string&#10; &#125;&#41;&#41;&#10; table_condition_expectation &#61; optional&#40;object&#40;&#123;&#10; sql_expression &#61; string&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [data_quality_spec](variables.tf#L38) | DataQualityScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. | <code title="object&#40;&#123;&#10; sampling_percent &#61; optional&#40;number&#41;&#10; row_filter &#61; optional&#40;string&#41;&#10; rules &#61; list&#40;object&#40;&#123;&#10; column &#61; optional&#40;string&#41;&#10; ignore_null &#61; optional&#40;bool, null&#41;&#10; dimension &#61; string&#10; threshold &#61; optional&#40;number&#41;&#10; non_null_expectation &#61; optional&#40;object&#40;&#123;&#125;&#41;&#41;&#10; range_expectation &#61; optional&#40;object&#40;&#123;&#10; min_value &#61; optional&#40;number&#41;&#10; max_value &#61; optional&#40;number&#41;&#10; strict_min_enabled &#61; optional&#40;bool&#41;&#10; strict_max_enabled &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#10; regex_expectation &#61; optional&#40;object&#40;&#123;&#10; regex &#61; string&#10; &#125;&#41;&#41;&#10; set_expectation &#61; optional&#40;object&#40;&#123;&#10; values &#61; list&#40;string&#41;&#10; &#125;&#41;&#41;&#10; uniqueness_expectation &#61; optional&#40;object&#40;&#123;&#125;&#41;&#41;&#10; statistic_range_expectation &#61; optional&#40;object&#40;&#123;&#10; statistic &#61; string&#10; min_value &#61; optional&#40;number&#41;&#10; max_value &#61; optional&#40;number&#41;&#10; strict_min_enabled &#61; optional&#40;bool&#41;&#10; strict_max_enabled &#61; optional&#40;bool&#41;&#10; &#125;&#41;&#41;&#10; row_condition_expectation &#61; optional&#40;object&#40;&#123;&#10; sql_expression &#61; string&#10; &#125;&#41;&#41;&#10; table_condition_expectation &#61; optional&#40;object&#40;&#123;&#10; sql_expression &#61; string&#10; &#125;&#41;&#41;&#10; &#125;&#41;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [data_quality_spec_file](variables.tf#L80) | Path to a YAML file containing DataQualityScan related setting. Input content can use either camelCase or snake_case. Variables description are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. | <code title="object&#40;&#123;&#10; path &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [data_quality_spec_file](variables.tf#L80) | Path to a YAML file containing DataQualityScan related setting. Input content can use either camelCase or snake_case. Variables description are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. | <code title="object&#40;&#123;&#10; path &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
@ -427,10 +445,11 @@ module "dataplex-datascan" {
| [iam](variables.tf#L107) | Dataplex DataScan IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam](variables.tf#L107) | Dataplex DataScan IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive](variables.tf#L114) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive](variables.tf#L114) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive_members](variables.tf#L121) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive_members](variables.tf#L121) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_policy](variables.tf#L127) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> | | [iam_members](variables.tf#L127) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [incremental_field](variables.tf#L133) | The unnested field (of type Date or Timestamp) that contains values which monotonically increase over time. If not specified, a data scan will run for all data in the table. | <code>string</code> | | <code>null</code> | | [iam_policy](variables.tf#L137) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> |
| [labels](variables.tf#L139) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [incremental_field](variables.tf#L143) | The unnested field (of type Date or Timestamp) that contains values which monotonically increase over time. If not specified, a data scan will run for all data in the table. | <code>string</code> | | <code>null</code> |
| [prefix](variables.tf#L151) | Optional prefix used to generate Dataplex DataScan ID. | <code>string</code> | | <code>null</code> | | [labels](variables.tf#L149) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [prefix](variables.tf#L161) | Optional prefix used to generate Dataplex DataScan ID. | <code>string</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -69,6 +69,15 @@ resource "google_dataplex_datascan_iam_member" "additive" {
member = each.value.member member = each.value.member
} }
resource "google_dataplex_datascan_iam_member" "members" {
for_each = var.iam_members
project = google_dataplex_datascan.datascan.project
location = google_dataplex_datascan.datascan.location
data_scan_id = google_dataplex_datascan.datascan.data_scan_id
role = each.value.role
member = each.value.member
}
resource "google_dataplex_datascan_iam_policy" "authoritative_for_resource" { resource "google_dataplex_datascan_iam_policy" "authoritative_for_resource" {
count = var.iam_policy != null ? 1 : 0 count = var.iam_policy != null ? 1 : 0
project = google_dataplex_datascan.datascan.project project = google_dataplex_datascan.datascan.project

View File

@ -124,6 +124,16 @@ variable "iam_additive_members" {
default = {} default = {}
} }
variable "iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
}))
nullable = false
default = {}
}
variable "iam_policy" { variable "iam_policy" {
description = "IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution." description = "IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution."
type = map(list(string)) type = map(list(string))

File diff suppressed because one or more lines are too long

View File

@ -65,3 +65,11 @@ resource "google_dataproc_cluster_iam_member" "additive" {
role = each.value.role role = each.value.role
member = each.value.member member = each.value.member
} }
resource "google_dataproc_cluster_iam_member" "members" {
for_each = var.iam_members
project = var.project_id
cluster = google_dataproc_cluster.cluster.name
role = each.value.role
member = each.value.member
}

View File

@ -203,6 +203,16 @@ variable "iam_additive" {
nullable = false nullable = false
} }
variable "iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
}))
nullable = false
default = {}
}
variable "labels" { variable "labels" {
description = "The resource labels for instance to use to annotate any related underlying resources, such as Compute Engine VMs." description = "The resource labels for instance to use to annotate any related underlying resources, such as Compute Engine VMs."
type = map(string) type = map(string)

View File

@ -2,15 +2,12 @@
This module allows the creation and management of folders, including support for IAM bindings, organization policies, and hierarchical firewall rules. This module allows the creation and management of folders, including support for IAM bindings, organization policies, and hierarchical firewall rules.
<!-- BEGIN TOC --> <!-- BEGIN TOC -->
- [Basic example with IAM bindings](#basic-example-with-iam-bindings) - [Basic example with IAM bindings](#basic-example-with-iam-bindings)
- [IAM](#iam) - [IAM](#iam)
- [Organization policies](#organization-policies) - [Organization policies](#organization-policies)
- [Organization Policy Factory](#organization-policy-factory) - [Organization Policy Factory](#organization-policy-factory)
- [Hierarchical Firewall Policies](#hierarchical-firewall-policies) - [Hierarchical Firewall Policy Attachments](#hierarchical-firewall-policy-attachments)
- [Directly Defined Firewall Policies](#directly-defined-firewall-policies)
- [Firewall Policy Factory](#firewall-policy-factory)
- [Log Sinks](#log-sinks) - [Log Sinks](#log-sinks)
- [Data Access Logs](#data-access-logs) - [Data Access Logs](#data-access-logs)
- [Tags](#tags) - [Tags](#tags)
@ -44,15 +41,21 @@ module "folder" {
"user:am1@example.org" = ["roles/storage.admin"] "user:am1@example.org" = ["roles/storage.admin"]
"user:am2@example.org" = ["roles/storage.objectViewer"] "user:am2@example.org" = ["roles/storage.objectViewer"]
} }
iam_members = {
am1-storage-admin = {
member = "user:am1@example.org"
role = "roles/storage.admin"
}
}
} }
# tftest modules=1 resources=9 inventory=iam.yaml # tftest modules=1 resources=10 inventory=iam.yaml
``` ```
## IAM ## IAM
There are three mutually exclusive ways at the role level of managing IAM in this module There are four three exclusive ways at the role level of managing IAM in this module
- non-authoritative via the `iam_additive` and `iam_additive_members` variables, where bindings created outside this module will coexist with those managed here - non-authoritative via the `iam_additive`, `iam_additive_members` and `iam_members` variables, where bindings created outside this module will coexist with those managed here
- authoritative via the `group_iam` and `iam` variables, where bindings created outside this module (eg in the console) will be removed at each `terraform apply` cycle if the same role is also managed here - authoritative via the `group_iam` and `iam` variables, where bindings created outside this module (eg in the console) will be removed at each `terraform apply` cycle if the same role is also managed here
- authoritative policy via the `iam_policy` variable, where any binding created outside this module (eg in the console) will be removed at each `terraform apply` cycle regardless of the role - authoritative policy via the `iam_policy` variable, where any binding created outside this module (eg in the console) will be removed at each `terraform apply` cycle regardless of the role
@ -121,128 +124,31 @@ module "folder" {
See the [organization policy factory in the project module](../project#organization-policy-factory). See the [organization policy factory in the project module](../project#organization-policy-factory).
## Hierarchical Firewall Policies ## Hierarchical Firewall Policy Attachments
Hierarchical firewall policies can be managed in two ways: Hierarchical firewall policies can be managed via the [`net-firewall-policy`](../net-firewall-policy/) module, including support for factories. Once a policy is available, attaching it to the organization can be done either in the firewall policy module itself, or here:
- via the `firewall_policies` variable, to directly define policies and rules in Terraform
- via the `firewall_policy_factory` variable, to leverage external YaML files via a simple "factory" embedded in the module ([see here](../../blueprints/factories) for more context on factories)
Once you have policies (either created via the module or externally), you can associate them using the `firewall_policy_association` variable.
### Directly Defined Firewall Policies
```hcl ```hcl
module "folder1" { module "firewall-policy" {
source = "./fabric/modules/folder" source = "./fabric/modules/net-firewall-policy"
parent = var.organization_id name = "test-1"
name = "policy-container" parent_id = module.folder.id
# attachment via the firewall policy module
firewall_policies = { # attachments = {
iap-policy = { # folder-1 = module.folder.id
allow-admins = { # }
description = "Access from the admin subnet to all subnets"
direction = "INGRESS"
action = "allow"
priority = 1000
ranges = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
ports = { all = [] }
target_service_accounts = null
target_resources = null
logging = false
}
allow-iap-ssh = {
description = "Always allow ssh from IAP"
direction = "INGRESS"
action = "allow"
priority = 100
ranges = ["35.235.240.0/20"]
ports = { tcp = ["22"] }
target_service_accounts = null
target_resources = null
logging = false
}
}
}
firewall_policy_association = {
iap-policy = "iap-policy"
}
} }
module "folder2" { module "folder" {
source = "./fabric/modules/folder" source = "./fabric/modules/folder"
parent = var.organization_id parent = "organizations/1234567890"
name = "hf2" name = "Folder name"
firewall_policy_association = { # attachment via the organization module
iap-policy = module.folder1.firewall_policy_id["iap-policy"] firewall_policy_associations = {
test-1 = module.firewall-policy.id
} }
} }
# tftest modules=2 resources=7 inventory=hfw.yaml # tftest modules=2 resources=3
```
### Firewall Policy Factory
The in-built factory allows you to define a single policy, using one file for rules, and an optional file for CIDR range substitution variables. Remember that non-absolute paths are relative to the root module (the folder where you run `terraform`).
```hcl
module "folder1" {
source = "./fabric/modules/folder"
parent = var.organization_id
name = "policy-container"
firewall_policy_factory = {
cidr_file = "configs/firewall-policies/cidrs.yaml"
policy_name = "iap-policy"
rules_file = "configs/firewall-policies/rules.yaml"
}
firewall_policy_association = {
iap-policy = "iap-policy"
}
}
module "folder2" {
source = "./fabric/modules/folder"
parent = var.organization_id
name = "hf2"
firewall_policy_association = {
iap-policy = module.folder1.firewall_policy_id["iap-policy"]
}
}
# tftest modules=2 resources=7 files=cidrs,rules inventory=hfw.yaml
```
```yaml
# tftest-file id=cidrs path=configs/firewall-policies/cidrs.yaml
rfc1918:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
```
```yaml
# tftest-file id=rules path=configs/firewall-policies/rules.yaml
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
action: allow
priority: 1000
ranges:
- $rfc1918
ports:
all: []
target_resources: null
logging: false
allow-iap-ssh:
description: "Always allow ssh from IAP"
direction: INGRESS
action: allow
priority: 100
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
logging: false
``` ```
## Log Sinks ## Log Sinks
@ -395,15 +301,13 @@ module "folder" {
<!-- TFDOC OPTS files:1 --> <!-- TFDOC OPTS files:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | resources | | name | description | resources |
|---|---|---| |---|---|---|
| [firewall-policies.tf](./firewall-policies.tf) | None | <code>google_compute_firewall_policy</code> · <code>google_compute_firewall_policy_association</code> · <code>google_compute_firewall_policy_rule</code> |
| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | <code>google_folder_iam_binding</code> · <code>google_folder_iam_member</code> · <code>google_folder_iam_policy</code> | | [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | <code>google_folder_iam_binding</code> · <code>google_folder_iam_member</code> · <code>google_folder_iam_policy</code> |
| [logging.tf](./logging.tf) | Log sinks and supporting resources. | <code>google_bigquery_dataset_iam_member</code> · <code>google_folder_iam_audit_config</code> · <code>google_logging_folder_exclusion</code> · <code>google_logging_folder_sink</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> | | [logging.tf](./logging.tf) | Log sinks and supporting resources. | <code>google_bigquery_dataset_iam_member</code> · <code>google_folder_iam_audit_config</code> · <code>google_logging_folder_exclusion</code> · <code>google_logging_folder_sink</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_essential_contacts_contact</code> · <code>google_folder</code> | | [main.tf](./main.tf) | Module-level locals and resources. | <code>google_compute_firewall_policy_association</code> · <code>google_essential_contacts_contact</code> · <code>google_folder</code> |
| [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | <code>google_org_policy_policy</code> | | [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | <code>google_org_policy_policy</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | | | [outputs.tf](./outputs.tf) | Module outputs. | |
| [tags.tf](./tags.tf) | None | <code>google_tags_tag_binding</code> | | [tags.tf](./tags.tf) | None | <code>google_tags_tag_binding</code> |
@ -415,34 +319,30 @@ module "folder" {
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [firewall_policies](variables.tf#L24) | Hierarchical firewall policies created in this folder. | <code title="map&#40;map&#40;object&#40;&#123;&#10; action &#61; string&#10; description &#61; string&#10; direction &#61; string&#10; logging &#61; bool&#10; ports &#61; map&#40;list&#40;string&#41;&#41;&#10; priority &#61; number&#10; ranges &#61; list&#40;string&#41;&#10; target_resources &#61; list&#40;string&#41;&#10; target_service_accounts &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;&#41;">map&#40;map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [firewall_policy_associations](variables.tf#L24) | Hierarchical firewall policies to associate to this folder, in association name => policy id format. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [firewall_policy_association](variables.tf#L41) | The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [folder_create](variables.tf#L31) | Create folder. When set to false, uses id to reference an existing folder. | <code>bool</code> | | <code>true</code> |
| [firewall_policy_factory](variables.tf#L48) | Configuration for the firewall policy factory. | <code title="object&#40;&#123;&#10; cidr_file &#61; string&#10; policy_name &#61; string&#10; rules_file &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [group_iam](variables.tf#L37) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [folder_create](variables.tf#L58) | Create folder. When set to false, uses id to reference an existing folder. | <code>bool</code> | | <code>true</code> | | [iam](variables.tf#L44) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [group_iam](variables.tf#L64) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive](variables.tf#L51) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam](variables.tf#L71) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive_members](variables.tf#L58) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive](variables.tf#L78) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_members](variables.tf#L65) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive_members](variables.tf#L85) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_policy](variables.tf#L75) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> |
| [iam_policy](variables.tf#L92) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> | | [id](variables.tf#L81) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> |
| [id](variables.tf#L98) | Folder ID in case you use folder_create=false. | <code>string</code> | | <code>null</code> | | [logging_data_access](variables.tf#L87) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_data_access](variables.tf#L104) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [logging_exclusions](variables.tf#L102) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_exclusions](variables.tf#L119) | Logging exclusions for this folder in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [logging_sinks](variables.tf#L109) | Logging sinks to create for the organization. | <code title="map&#40;object&#40;&#123;&#10; bq_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; string&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; include_children &#61; optional&#40;bool, true&#41;&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_sinks](variables.tf#L126) | Logging sinks to create for the organization. | <code title="map&#40;object&#40;&#123;&#10; bq_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; string&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; include_children &#61; optional&#40;bool, true&#41;&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [name](variables.tf#L139) | Folder name. | <code>string</code> | | <code>null</code> |
| [name](variables.tf#L156) | Folder name. | <code>string</code> | | <code>null</code> | | [org_policies](variables.tf#L145) | Organization policies applied to this folder keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L162) | Organization policies applied to this folder keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policies_data_path](variables.tf#L172) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [org_policies_data_path](variables.tf#L189) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> | | [parent](variables.tf#L178) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> |
| [parent](variables.tf#L195) | Parent in folders/folder_id or organizations/org_id format. | <code>string</code> | | <code>null</code> | | [tag_bindings](variables.tf#L188) | Tag bindings for this folder, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L205) | Tag bindings for this folder, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs ## Outputs
| name | description | sensitive | | name | description | sensitive |
|---|---|:---:| |---|---|:---:|
| [firewall_policies](outputs.tf#L16) | Map of firewall policy resources created in this folder. | | | [folder](outputs.tf#L17) | Folder resource. | |
| [firewall_policy_id](outputs.tf#L21) | Map of firewall policy ids created in this folder. | | | [id](outputs.tf#L22) | Fully qualified folder id. | |
| [folder](outputs.tf#L26) | Folder resource. | | | [name](outputs.tf#L32) | Folder name. | |
| [id](outputs.tf#L31) | Fully qualified folder id. | | | [sink_writer_identities](outputs.tf#L37) | Writer identities created for each sink. | |
| [name](outputs.tf#L41) | Folder name. | |
| [sink_writer_identities](outputs.tf#L46) | Writer identities created for each sink. | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -1,93 +0,0 @@
/**
* Copyright 2022 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.
*/
locals {
_factory_cidrs = try(
yamldecode(file(var.firewall_policy_factory.cidr_file)), {}
)
_factory_name = (
try(var.firewall_policy_factory.policy_name, null) == null
? "factory"
: var.firewall_policy_factory.policy_name
)
_factory_rules = try(
yamldecode(file(var.firewall_policy_factory.rules_file)), {}
)
_factory_rules_parsed = {
for name, rule in local._factory_rules : name => merge(rule, {
ranges = flatten([
for r in(rule.ranges == null ? [] : rule.ranges) :
lookup(local._factory_cidrs, trimprefix(r, "$"), r)
])
})
}
_merged_rules = flatten([
for policy, rules in local.firewall_policies : [
for name, rule in rules : merge(rule, {
policy = policy
name = name
})
]
])
firewall_policies = merge(var.firewall_policies, (
length(local._factory_rules) == 0
? {}
: { (local._factory_name) = local._factory_rules_parsed }
))
firewall_rules = {
for r in local._merged_rules : "${r.policy}-${r.name}" => r
}
}
resource "google_compute_firewall_policy" "policy" {
for_each = local.firewall_policies
short_name = each.key
parent = local.folder.id
}
resource "google_compute_firewall_policy_rule" "rule" {
for_each = local.firewall_rules
firewall_policy = google_compute_firewall_policy.policy[each.value.policy].id
action = each.value.action
direction = each.value.direction
priority = try(each.value.priority, null)
target_resources = try(each.value.target_resources, null)
target_service_accounts = try(each.value.target_service_accounts, null)
enable_logging = try(each.value.logging, null)
# preview = each.value.preview
description = each.value.description
match {
src_ip_ranges = each.value.direction == "INGRESS" ? each.value.ranges : null
dest_ip_ranges = each.value.direction == "EGRESS" ? each.value.ranges : null
dynamic "layer4_configs" {
for_each = each.value.ports
iterator = port
content {
ip_protocol = port.key
ports = port.value
}
}
}
}
resource "google_compute_firewall_policy_association" "association" {
for_each = var.firewall_policy_association
name = replace(local.folder.id, "/", "-")
attachment_target = local.folder.id
firewall_policy = try(google_compute_firewall_policy.policy[each.value].id, each.value)
}

View File

@ -64,6 +64,13 @@ resource "google_folder_iam_member" "additive" {
member = each.value.member member = each.value.member
} }
resource "google_folder_iam_member" "members" {
for_each = var.iam_members
folder = local.folder.name
role = each.value.role
member = each.value.member
}
resource "google_folder_iam_policy" "authoritative" { resource "google_folder_iam_policy" "authoritative" {
count = var.iam_policy != null ? 1 : 0 count = var.iam_policy != null ? 1 : 0
folder = local.folder.name folder = local.folder.name

View File

@ -41,3 +41,10 @@ resource "google_essential_contacts_contact" "contact" {
language_tag = "en" language_tag = "en"
notification_category_subscriptions = each.value notification_category_subscriptions = each.value
} }
resource "google_compute_firewall_policy_association" "default" {
for_each = var.firewall_policy_associations
attachment_target = local.folder.id
name = each.key
firewall_policy = each.value
}

View File

@ -13,15 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
output "firewall_policies" {
description = "Map of firewall policy resources created in this folder."
value = { for k, v in google_compute_firewall_policy.policy : k => v }
}
output "firewall_policy_id" {
description = "Map of firewall policy ids created in this folder."
value = { for k, v in google_compute_firewall_policy.policy : k => v.id }
}
output "folder" { output "folder" {
description = "Folder resource." description = "Folder resource."

View File

@ -21,40 +21,13 @@ variable "contacts" {
nullable = false nullable = false
} }
variable "firewall_policies" { variable "firewall_policy_associations" {
description = "Hierarchical firewall policies created in this folder." description = "Hierarchical firewall policies to associate to this folder, in association name => policy id format."
type = map(map(object({
action = string
description = string
direction = string
logging = bool
ports = map(list(string))
priority = number
ranges = list(string)
target_resources = list(string)
target_service_accounts = list(string)
})))
default = {}
nullable = false
}
variable "firewall_policy_association" {
description = "The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else."
type = map(string) type = map(string)
default = {} default = {}
nullable = false nullable = false
} }
variable "firewall_policy_factory" {
description = "Configuration for the firewall policy factory."
type = object({
cidr_file = string
policy_name = string
rules_file = string
})
default = null
}
variable "folder_create" { variable "folder_create" {
description = "Create folder. When set to false, uses id to reference an existing folder." description = "Create folder. When set to false, uses id to reference an existing folder."
type = bool type = bool
@ -89,6 +62,16 @@ variable "iam_additive_members" {
nullable = false nullable = false
} }
variable "iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
}))
nullable = false
default = {}
}
variable "iam_policy" { variable "iam_policy" {
description = "IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution." description = "IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution."
type = map(list(string)) type = map(list(string))

View File

@ -27,7 +27,6 @@ module "myproject-default-service-accounts" {
``` ```
<!-- TFDOC OPTS files:1 --> <!-- TFDOC OPTS files:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | resources | | name | description | resources |
@ -42,8 +41,8 @@ module "myproject-default-service-accounts" {
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [name](variables.tf#L91) | Name of the service account to create. | <code>string</code> | ✓ | | | [name](variables.tf#L101) | Name of the service account to create. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L106) | Project id where service account will be created. | <code>string</code> | ✓ | | | [project_id](variables.tf#L116) | Project id where service account will be created. | <code>string</code> | ✓ | |
| [description](variables.tf#L17) | Optional description. | <code>string</code> | | <code>null</code> | | [description](variables.tf#L17) | Optional description. | <code>string</code> | | <code>null</code> |
| [display_name](variables.tf#L23) | Display name of the service account to create. | <code>string</code> | | <code>&#34;Terraform-managed.&#34;</code> | | [display_name](variables.tf#L23) | Display name of the service account to create. | <code>string</code> | | <code>&#34;Terraform-managed.&#34;</code> |
| [generate_key](variables.tf#L29) | Generate a key for service account. | <code>bool</code> | | <code>false</code> | | [generate_key](variables.tf#L29) | Generate a key for service account. | <code>bool</code> | | <code>false</code> |
@ -51,13 +50,14 @@ module "myproject-default-service-accounts" {
| [iam_additive](variables.tf#L42) | IAM additive bindings on the service account in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive](variables.tf#L42) | IAM additive bindings on the service account in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_billing_roles](variables.tf#L49) | Billing account roles granted to this service account, by billing account id. Non-authoritative. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_billing_roles](variables.tf#L49) | Billing account roles granted to this service account, by billing account id. Non-authoritative. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_folder_roles](variables.tf#L56) | Folder roles granted to this service account, by folder id. Non-authoritative. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_folder_roles](variables.tf#L56) | Folder roles granted to this service account, by folder id. Non-authoritative. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_organization_roles](variables.tf#L63) | Organization roles granted to this service account, by organization id. Non-authoritative. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_members](variables.tf#L63) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_project_roles](variables.tf#L70) | Project roles granted to this service account, by project id. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_organization_roles](variables.tf#L73) | Organization roles granted to this service account, by organization id. Non-authoritative. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_sa_roles](variables.tf#L77) | Service account roles granted to this service account, by service account name. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_project_roles](variables.tf#L80) | Project roles granted to this service account, by project id. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_storage_roles](variables.tf#L84) | Storage roles granted to this service account, by bucket name. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_sa_roles](variables.tf#L87) | Service account roles granted to this service account, by service account name. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [prefix](variables.tf#L96) | Prefix applied to service account names. | <code>string</code> | | <code>null</code> | | [iam_storage_roles](variables.tf#L94) | Storage roles granted to this service account, by bucket name. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [public_keys_directory](variables.tf#L111) | Path to public keys data files to upload to the service account (should have `.pem` extension). | <code>string</code> | | <code>&#34;&#34;</code> | | [prefix](variables.tf#L106) | Prefix applied to service account names. | <code>string</code> | | <code>null</code> |
| [service_account_create](variables.tf#L117) | Create service account. When set to false, uses a data source to reference an existing service account. | <code>bool</code> | | <code>true</code> | | [public_keys_directory](variables.tf#L121) | Path to public keys data files to upload to the service account (should have `.pem` extension). | <code>string</code> | | <code>&#34;&#34;</code> |
| [service_account_create](variables.tf#L127) | Create service account. When set to false, uses a data source to reference an existing service account. | <code>bool</code> | | <code>true</code> |
## Outputs ## Outputs
@ -70,5 +70,4 @@ module "myproject-default-service-accounts" {
| [name](outputs.tf#L48) | Service account name. | | | [name](outputs.tf#L48) | Service account name. | |
| [service_account](outputs.tf#L57) | Service account resource. | | | [service_account](outputs.tf#L57) | Service account resource. | |
| [service_account_credentials](outputs.tf#L62) | Service account json credential templates for uploaded public keys data. | | | [service_account_credentials](outputs.tf#L62) | Service account json credential templates for uploaded public keys data. | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -134,6 +134,13 @@ resource "google_service_account_iam_member" "additive" {
member = local.resource_iam_email member = local.resource_iam_email
} }
resource "google_service_account_iam_member" "members" {
for_each = var.iam_members
service_account_id = each.value.entity
role = each.value.role
member = each.value.member
}
resource "google_storage_bucket_iam_member" "bucket-roles" { resource "google_storage_bucket_iam_member" "bucket-roles" {
for_each = { for_each = {
for pair in local.iam_storage_pairs : for pair in local.iam_storage_pairs :

View File

@ -60,6 +60,16 @@ variable "iam_folder_roles" {
nullable = false nullable = false
} }
variable "iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
}))
nullable = false
default = {}
}
variable "iam_organization_roles" { variable "iam_organization_roles" {
description = "Organization roles granted to this service account, by organization id. Non-authoritative." description = "Organization roles granted to this service account, by organization id. Non-authoritative."
type = map(list(string)) type = map(list(string))

View File

@ -4,6 +4,16 @@ This module allows creating and managing KMS crypto keys and IAM bindings at bot
When using an existing keyring be mindful about applying IAM bindings, as all bindings used by this module are authoritative, and you might inadvertently override bindings managed by the keyring creator. When using an existing keyring be mindful about applying IAM bindings, as all bindings used by this module are authoritative, and you might inadvertently override bindings managed by the keyring creator.
<!-- BEGIN TOC -->
- [Protecting against destroy](#protecting-against-destroy)
- [Examples](#examples)
- [Using an existing keyring](#using-an-existing-keyring)
- [Keyring creation and crypto key rotation and IAM roles](#keyring-creation-and-crypto-key-rotation-and-iam-roles)
- [Crypto key purpose](#crypto-key-purpose)
- [Variables](#variables)
- [Outputs](#outputs)
<!-- END TOC -->
## Protecting against destroy ## Protecting against destroy
In this module **no lifecycle blocks are set on resources to prevent destroy**, in order to allow for experimentation and testing where rapid `apply`/`destroy` cycles are needed. If you plan on using this module to manage non-development resources, **clone it and uncomment the lifecycle blocks** found in `main.tf`. In this module **no lifecycle blocks are set on resources to prevent destroy**, in order to allow for experimentation and testing where rapid `apply`/`destroy` cycles are needed. If you plan on using this module to manage non-development resources, **clone it and uncomment the lifecycle blocks** found in `main.tf`.
@ -49,6 +59,13 @@ module "kms" {
] ]
} }
} }
key_iam_members = {
key-b-am1 = {
key = "key-b"
member = "user:am1@example.com"
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
}
}
keyring = { location = "europe-west1", name = "test" } keyring = { location = "europe-west1", name = "test" }
keys = { keys = {
key-a = null key-a = null
@ -56,7 +73,7 @@ module "kms" {
key-c = { rotation_period = null, labels = { env = "test" } } key-c = { rotation_period = null, labels = { env = "test" } }
} }
} }
# tftest modules=1 resources=9 inventory=basic.yaml # tftest modules=1 resources=10
``` ```
### Crypto key purpose ### Crypto key purpose
@ -80,22 +97,23 @@ module "kms" {
# tftest modules=1 resources=4 # tftest modules=1 resources=4
``` ```
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [keyring](variables.tf#L70) | Keyring attributes. | <code title="object&#40;&#123;&#10; location &#61; string&#10; name &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | [keyring](variables.tf#L91) | Keyring attributes. | <code title="object&#40;&#123;&#10; location &#61; string&#10; name &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [project_id](variables.tf#L93) | Project id where the keyring will be created. | <code>string</code> | ✓ | | | [project_id](variables.tf#L114) | Project id where the keyring will be created. | <code>string</code> | ✓ | |
| [iam](variables.tf#L17) | Keyring IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam](variables.tf#L17) | Keyring IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive](variables.tf#L23) | Keyring IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive](variables.tf#L23) | Keyring IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [key_iam](variables.tf#L29) | Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_members](variables.tf#L29) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [key_iam_additive](variables.tf#L35) | Key IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [key_iam](variables.tf#L39) | Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [key_purpose](variables.tf#L41) | Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | <code title="map&#40;object&#40;&#123;&#10; purpose &#61; string&#10; version_template &#61; object&#40;&#123;&#10; algorithm &#61; string&#10; protection_level &#61; string&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [key_iam_additive](variables.tf#L45) | Key IAM additive bindings in {KEY => {ROLE => [MEMBERS]}} format. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [key_purpose_defaults](variables.tf#L53) | Defaults used for key purpose when not defined at the key level. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | <code title="object&#40;&#123;&#10; purpose &#61; string&#10; version_template &#61; object&#40;&#123;&#10; algorithm &#61; string&#10; protection_level &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; purpose &#61; null&#10; version_template &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> | | [key_iam_members](variables.tf#L51) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; key &#61; string&#10; member &#61; string&#10; role &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [keyring_create](variables.tf#L78) | Set to false to manage keys and IAM bindings in an existing keyring. | <code>bool</code> | | <code>true</code> | | [key_purpose](variables.tf#L62) | Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | <code title="map&#40;object&#40;&#123;&#10; purpose &#61; string&#10; version_template &#61; object&#40;&#123;&#10; algorithm &#61; string&#10; protection_level &#61; string&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [keys](variables.tf#L84) | Key names and base attributes. Set attributes to null if not needed. | <code title="map&#40;object&#40;&#123;&#10; rotation_period &#61; string&#10; labels &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [key_purpose_defaults](variables.tf#L74) | Defaults used for key purpose when not defined at the key level. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required. | <code title="object&#40;&#123;&#10; purpose &#61; string&#10; version_template &#61; object&#40;&#123;&#10; algorithm &#61; string&#10; protection_level &#61; string&#10; &#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; purpose &#61; null&#10; version_template &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> |
| [tag_bindings](variables.tf#L98) | Tag bindings for this keyring, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [keyring_create](variables.tf#L99) | Set to false to manage keys and IAM bindings in an existing keyring. | <code>bool</code> | | <code>true</code> |
| [keys](variables.tf#L105) | Key names and base attributes. Set attributes to null if not needed. | <code title="map&#40;object&#40;&#123;&#10; rotation_period &#61; string&#10; labels &#61; map&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [tag_bindings](variables.tf#L119) | Tag bindings for this keyring, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs ## Outputs
@ -107,5 +125,4 @@ module "kms" {
| [keys](outputs.tf#L44) | Key resources. | | | [keys](outputs.tf#L44) | Key resources. | |
| [location](outputs.tf#L52) | Keyring location. | | | [location](outputs.tf#L52) | Keyring location. | |
| [name](outputs.tf#L60) | Keyring name. | | | [name](outputs.tf#L60) | Keyring name. | |
<!-- END TFDOC --> <!-- END TFDOC -->

97
modules/kms/iam.tf Normal file
View File

@ -0,0 +1,97 @@
/**
* Copyright 2023 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.
*/
locals {
iam_additive_members = flatten([
for role, members in var.iam_additive : [
for member in members : {
member = member
role = role
}
]
])
key_iam_additive_members = flatten([
for key, roles in var.key_iam_additive : [
for role, members in roles : [
for member in members : {
key = key
member = member
role = role
}
]
]
])
key_iam_members = flatten([
for key, roles in var.key_iam : [
for role, members in roles : {
key = key
role = role
members = members
}
]
])
}
resource "google_kms_key_ring_iam_binding" "default" {
for_each = var.iam
key_ring_id = local.keyring.id
role = each.key
members = each.value
}
resource "google_kms_key_ring_iam_member" "default" {
for_each = {
for binding in local.iam_additive_members :
"${binding.role}${binding.member}" => binding
}
key_ring_id = local.keyring.id
role = each.value.role
member = each.value.member
}
resource "google_kms_key_ring_iam_member" "members" {
for_each = var.iam_members
key_ring_id = local.keyring.id
role = each.value.role
member = each.value.member
}
resource "google_kms_crypto_key_iam_binding" "default" {
for_each = {
for binding in local.key_iam_members :
"${binding.key}.${binding.role}" => binding
}
role = each.value.role
crypto_key_id = google_kms_crypto_key.default[each.value.key].id
members = each.value.members
}
resource "google_kms_crypto_key_iam_member" "default" {
for_each = {
for binding in local.key_iam_additive_members :
"${binding.key}.${binding.role}${binding.member}" => binding
}
role = each.value.role
crypto_key_id = google_kms_crypto_key.default[each.value.key].id
member = each.value.member
}
resource "google_kms_crypto_key_iam_member" "members" {
for_each = var.key_iam_members
crypto_key_id = google_kms_crypto_key.default[each.value.key].id
role = each.value.role
member = each.value.member
}

View File

@ -15,34 +15,6 @@
*/ */
locals { locals {
iam_additive_members = flatten([
for role, members in var.iam_additive : [
for member in members : {
member = member
role = role
}
]
])
key_iam_additive_members = flatten([
for key, roles in var.key_iam_additive : [
for role, members in roles : [
for member in members : {
key = key
member = member
role = role
}
]
]
])
key_iam_members = flatten([
for key, roles in var.key_iam : [
for role, members in roles : {
key = key
role = role
members = members
}
]
])
key_purpose = { key_purpose = {
for key, attrs in var.keys : key => try( for key, attrs in var.keys : key => try(
var.key_purpose[key], var.key_purpose_defaults var.key_purpose[key], var.key_purpose_defaults
@ -69,23 +41,6 @@ resource "google_kms_key_ring" "default" {
location = var.keyring.location location = var.keyring.location
} }
resource "google_kms_key_ring_iam_binding" "default" {
for_each = var.iam
key_ring_id = local.keyring.id
role = each.key
members = each.value
}
resource "google_kms_key_ring_iam_member" "default" {
for_each = {
for binding in local.iam_additive_members :
"${binding.role}${binding.member}" => binding
}
key_ring_id = local.keyring.id
role = each.value.role
member = each.value.member
}
resource "google_kms_crypto_key" "default" { resource "google_kms_crypto_key" "default" {
for_each = var.keys for_each = var.keys
key_ring = local.keyring.id key_ring = local.keyring.id
@ -101,23 +56,3 @@ resource "google_kms_crypto_key" "default" {
} }
} }
} }
resource "google_kms_crypto_key_iam_binding" "default" {
for_each = {
for binding in local.key_iam_members :
"${binding.key}.${binding.role}" => binding
}
role = each.value.role
crypto_key_id = google_kms_crypto_key.default[each.value.key].id
members = each.value.members
}
resource "google_kms_crypto_key_iam_member" "default" {
for_each = {
for binding in local.key_iam_additive_members :
"${binding.key}.${binding.role}${binding.member}" => binding
}
role = each.value.role
crypto_key_id = google_kms_crypto_key.default[each.value.key].id
member = each.value.member
}

View File

@ -26,6 +26,16 @@ variable "iam_additive" {
default = {} default = {}
} }
variable "iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
}))
nullable = false
default = {}
}
variable "key_iam" { variable "key_iam" {
description = "Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format." description = "Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format."
type = map(map(list(string))) type = map(map(list(string)))
@ -33,11 +43,22 @@ variable "key_iam" {
} }
variable "key_iam_additive" { variable "key_iam_additive" {
description = "Key IAM additive bindings in {ROLE => [MEMBERS]} format." description = "Key IAM additive bindings in {KEY => {ROLE => [MEMBERS]}} format."
type = map(map(list(string))) type = map(map(list(string)))
default = {} default = {}
} }
variable "key_iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
key = string
member = string
role = string
}))
nullable = false
default = {}
}
variable "key_purpose" { variable "key_purpose" {
description = "Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required." description = "Per-key purpose, if not set defaults will be used. If purpose is not `ENCRYPT_DECRYPT` (the default), `version_template.algorithm` is required."
type = map(object({ type = map(object({

View File

@ -9,13 +9,23 @@ The module also manages policy rules via code or a factory, and optional policy
The module also makes fewer assumptions about implicit defaults, only using one to set `match.layer4_configs` to `[{ protocol = "all" }]` if no explicit set of protocols and ports has been specified. The module also makes fewer assumptions about implicit defaults, only using one to set `match.layer4_configs` to `[{ protocol = "all" }]` if no explicit set of protocols and ports has been specified.
<!-- BEGIN TOC -->
- [Examples](#examples)
- [Hierarchical Policy](#hierarchical-policy)
- [Global Network policy](#global-network-policy)
- [Regional Network policy](#regional-network-policy)
- [Factory](#factory)
- [Variables](#variables)
- [Outputs](#outputs)
<!-- END TOC -->
## Examples ## Examples
### Hierarchical Policy ### Hierarchical Policy
```hcl ```hcl
module "firewall-policy" { module "firewall-policy" {
source = "./fabric/modules/net-vpc-firewall-policy" source = "./fabric/modules/net-firewall-policy"
name = "test-1" name = "test-1"
parent_id = "folders/1234567890" parent_id = "folders/1234567890"
attachments = { attachments = {
@ -67,9 +77,10 @@ module "vpc" {
} }
module "firewall-policy" { module "firewall-policy" {
source = "./fabric/modules/net-vpc-firewall-policy" source = "./fabric/modules/net-firewall-policy"
name = "test-1" name = "test-1"
parent_id = "my-project" parent_id = "my-project"
region = "global"
attachments = { attachments = {
my-vpc = module.vpc.self_link my-vpc = module.vpc.self_link
} }
@ -119,7 +130,7 @@ module "vpc" {
} }
module "firewall-policy" { module "firewall-policy" {
source = "./fabric/modules/net-vpc-firewall-policy" source = "./fabric/modules/net-firewall-policy"
name = "test-1" name = "test-1"
parent_id = "my-project" parent_id = "my-project"
region = "europe-west8" region = "europe-west8"
@ -164,7 +175,7 @@ This is an example of a simple factory:
```hcl ```hcl
module "firewall-policy" { module "firewall-policy" {
source = "./fabric/modules/net-vpc-firewall-policy" source = "./fabric/modules/net-firewall-policy"
name = "test-1" name = "test-1"
parent_id = "folders/1234567890" parent_id = "folders/1234567890"
attachments = { attachments = {
@ -219,7 +230,6 @@ icmp:
layer4_configs: layer4_configs:
- protocol: icmp - protocol: icmp
``` ```
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Variables ## Variables
@ -231,7 +241,7 @@ icmp:
| [description](variables.tf#L24) | Policy description. | <code>string</code> | | <code>null</code> | | [description](variables.tf#L24) | Policy description. | <code>string</code> | | <code>null</code> |
| [egress_rules](variables.tf#L30) | List of egress rule definitions, action can be 'allow', 'deny', 'goto_next'. The match.layer4configs map is in protocol => optional [ports] format. | <code title="map&#40;object&#40;&#123;&#10; priority &#61; number&#10; action &#61; optional&#40;string, &#34;deny&#34;&#41;&#10; description &#61; optional&#40;string&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; enable_logging &#61; optional&#40;bool&#41;&#10; target_service_accounts &#61; optional&#40;list&#40;string&#41;&#41;&#10; target_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; match &#61; object&#40;&#123;&#10; address_groups &#61; optional&#40;list&#40;string&#41;&#41;&#10; fqdns &#61; optional&#40;list&#40;string&#41;&#41;&#10; region_codes &#61; optional&#40;list&#40;string&#41;&#41;&#10; threat_intelligences &#61; optional&#40;list&#40;string&#41;&#41;&#10; destination_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; layer4_configs &#61; optional&#40;list&#40;object&#40;&#123;&#10; protocol &#61; optional&#40;string, &#34;all&#34;&#41;&#10; ports &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#91;&#123;&#125;&#93;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [egress_rules](variables.tf#L30) | List of egress rule definitions, action can be 'allow', 'deny', 'goto_next'. The match.layer4configs map is in protocol => optional [ports] format. | <code title="map&#40;object&#40;&#123;&#10; priority &#61; number&#10; action &#61; optional&#40;string, &#34;deny&#34;&#41;&#10; description &#61; optional&#40;string&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; enable_logging &#61; optional&#40;bool&#41;&#10; target_service_accounts &#61; optional&#40;list&#40;string&#41;&#41;&#10; target_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; match &#61; object&#40;&#123;&#10; address_groups &#61; optional&#40;list&#40;string&#41;&#41;&#10; fqdns &#61; optional&#40;list&#40;string&#41;&#41;&#10; region_codes &#61; optional&#40;list&#40;string&#41;&#41;&#10; threat_intelligences &#61; optional&#40;list&#40;string&#41;&#41;&#10; destination_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; layer4_configs &#61; optional&#40;list&#40;object&#40;&#123;&#10; protocol &#61; optional&#40;string, &#34;all&#34;&#41;&#10; ports &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#91;&#123;&#125;&#93;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [ingress_rules](variables.tf#L71) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next'. | <code title="map&#40;object&#40;&#123;&#10; priority &#61; number&#10; action &#61; optional&#40;string, &#34;allow&#34;&#41;&#10; description &#61; optional&#40;string&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; enable_logging &#61; optional&#40;bool&#41;&#10; target_service_accounts &#61; optional&#40;list&#40;string&#41;&#41;&#10; target_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; match &#61; object&#40;&#123;&#10; address_groups &#61; optional&#40;list&#40;string&#41;&#41;&#10; fqdns &#61; optional&#40;list&#40;string&#41;&#41;&#10; region_codes &#61; optional&#40;list&#40;string&#41;&#41;&#10; threat_intelligences &#61; optional&#40;list&#40;string&#41;&#41;&#10; destination_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; layer4_configs &#61; optional&#40;list&#40;object&#40;&#123;&#10; protocol &#61; optional&#40;string, &#34;all&#34;&#41;&#10; ports &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#91;&#123;&#125;&#93;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [ingress_rules](variables.tf#L71) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next'. | <code title="map&#40;object&#40;&#123;&#10; priority &#61; number&#10; action &#61; optional&#40;string, &#34;allow&#34;&#41;&#10; description &#61; optional&#40;string&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; enable_logging &#61; optional&#40;bool&#41;&#10; target_service_accounts &#61; optional&#40;list&#40;string&#41;&#41;&#10; target_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; match &#61; object&#40;&#123;&#10; address_groups &#61; optional&#40;list&#40;string&#41;&#41;&#10; fqdns &#61; optional&#40;list&#40;string&#41;&#41;&#10; region_codes &#61; optional&#40;list&#40;string&#41;&#41;&#10; threat_intelligences &#61; optional&#40;list&#40;string&#41;&#41;&#10; destination_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; layer4_configs &#61; optional&#40;list&#40;object&#40;&#123;&#10; protocol &#61; optional&#40;string, &#34;all&#34;&#41;&#10; ports &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#91;&#123;&#125;&#93;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [region](variables.tf#L125) | Policy region. Leave null for hierarchical policy, or global network policy. | <code>string</code> | | <code>null</code> | | [region](variables.tf#L125) | Policy region. Leave null for hierarchical policy, set to 'global' for a global network policy. | <code>string</code> | | <code>null</code> |
| [rules_factory_config](variables.tf#L131) | Configuration for the optional rules factory. | <code title="object&#40;&#123;&#10; cidr_file_path &#61; optional&#40;string&#41;&#10; egress_rules_file_path &#61; optional&#40;string&#41;&#10; ingress_rules_file_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> | | [rules_factory_config](variables.tf#L131) | Configuration for the optional rules factory. | <code title="object&#40;&#123;&#10; cidr_file_path &#61; optional&#40;string&#41;&#10; egress_rules_file_path &#61; optional&#40;string&#41;&#10; ingress_rules_file_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs ## Outputs

View File

@ -15,23 +15,17 @@
*/ */
locals { locals {
_factory_egress_rules = ( _factory_egress_rules = try(
var.rules_factory_config.egress_rules_file_path == null yamldecode(file(var.rules_factory_config.egress_rules_file_path)), {}
? {}
: yamldecode(file(var.rules_factory_config.egress_rules_file_path))
) )
_factory_ingress_rules = ( _factory_ingress_rules = try(
var.rules_factory_config.ingress_rules_file_path == null yamldecode(file(var.rules_factory_config.ingress_rules_file_path)), {}
? {}
: yamldecode(file(var.rules_factory_config.ingress_rules_file_path))
) )
factory_cidrs = ( factory_cidrs = try(
var.rules_factory_config.cidr_file_path == null yamldecode(file(var.rules_factory_config.cidr_file_path)), {}
? {}
: yamldecode(file(var.rules_factory_config.cidr_file_path))
) )
factory_egress_rules = { factory_egress_rules = {
for k, v in local._factory_egress_rules : "ingress/${k}" => { for k, v in local._factory_egress_rules : "egress/${k}" => {
action = "deny" action = "deny"
direction = "EGRESS" direction = "EGRESS"
priority = v.priority priority = v.priority
@ -74,7 +68,7 @@ locals {
} }
} }
factory_ingress_rules = { factory_ingress_rules = {
for k, v in local._factory_ingress_rules : "egress/${k}" => { for k, v in local._factory_ingress_rules : "ingress/${k}" => {
action = "allow" action = "allow"
direction = "INGRESS" direction = "INGRESS"
priority = v.priority priority = v.priority

View File

@ -27,6 +27,7 @@ locals {
local.factory_egress_rules, local.factory_ingress_rules, local.factory_egress_rules, local.factory_ingress_rules,
local._rules_egress, local._rules_ingress local._rules_egress, local._rules_ingress
) )
use_hierarchical = strcontains(var.parent_id, "/") ? true : false # do not depend on the parent id as that might be dynamic and prevent count
use_regional = !local.use_hierarchical && var.region != null use_hierarchical = var.region == null
use_regional = !local.use_hierarchical && var.region != "global"
} }

View File

@ -16,9 +16,13 @@
output "id" { output "id" {
description = "Fully qualified firewall policy id." description = "Fully qualified firewall policy id."
value = coalesce([ value = (
try(google_compute_firewall_policy.hierarchical.0.id, null), local.use_hierarchical
try(google_compute_network_firewall_policy.net-global.0.id, null), ? google_compute_firewall_policy.hierarchical.0.id
try(google_compute_region_network_firewall_policy.net-regional.0.id, null) : (
]) local.use_regional
? google_compute_region_network_firewall_policy.net-regional.0.id
: google_compute_network_firewall_policy.net-global.0.id
)
)
} }

View File

@ -123,7 +123,7 @@ variable "parent_id" {
} }
variable "region" { variable "region" {
description = "Policy region. Leave null for hierarchical policy, or global network policy." description = "Policy region. Leave null for hierarchical policy, set to 'global' for a global network policy."
type = string type = string
default = null default = null
} }

View File

@ -131,8 +131,15 @@ module "vpc" {
] ]
} }
} }
subnet_iam_members = {
subnet-2-am1 = {
member = "user:am1@example.com"
role = "roles/compute.networkUser"
subnet = "europe-west1/subnet-2"
}
}
} }
# tftest modules=1 resources=8 inventory=subnet-iam.yaml # tftest modules=1 resources=9 inventory=subnet-iam.yaml
``` ```
### Peering ### Peering
@ -534,10 +541,11 @@ module "vpc" {
| [shared_vpc_service_projects](variables.tf#L161) | Shared VPC service projects to register with this host. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [shared_vpc_service_projects](variables.tf#L161) | Shared VPC service projects to register with this host. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [subnet_iam](variables.tf#L167) | Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [subnet_iam](variables.tf#L167) | Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [subnet_iam_additive](variables.tf#L173) | Subnet IAM additive bindings in {REGION/NAME => {ROLE => [MEMBERS]}} format. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [subnet_iam_additive](variables.tf#L173) | Subnet IAM additive bindings in {REGION/NAME => {ROLE => [MEMBERS]}} format. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [subnets](variables.tf#L180) | Subnet configuration. | <code title="list&#40;object&#40;&#123;&#10; name &#61; string&#10; ip_cidr_range &#61; string&#10; region &#61; string&#10; description &#61; optional&#40;string&#41;&#10; enable_private_access &#61; optional&#40;bool, true&#41;&#10; flow_logs_config &#61; optional&#40;object&#40;&#123;&#10; aggregation_interval &#61; optional&#40;string&#41;&#10; filter_expression &#61; optional&#40;string&#41;&#10; flow_sampling &#61; optional&#40;number&#41;&#10; metadata &#61; optional&#40;string&#41;&#10; metadata_fields &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; ipv6 &#61; optional&#40;object&#40;&#123;&#10; access_type &#61; optional&#40;string, &#34;INTERNAL&#34;&#41;&#10; &#125;&#41;&#41;&#10; secondary_ip_ranges &#61; optional&#40;map&#40;string&#41;&#41;&#10;&#125;&#41;&#41;">list&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#91;&#93;</code> | | [subnet_iam_members](variables.tf#L180) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10; subnet &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [subnets_proxy_only](variables.tf#L206) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | <code title="list&#40;object&#40;&#123;&#10; name &#61; string&#10; ip_cidr_range &#61; string&#10; region &#61; string&#10; description &#61; optional&#40;string&#41;&#10; active &#61; bool&#10;&#125;&#41;&#41;">list&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#91;&#93;</code> | | [subnets](variables.tf#L191) | Subnet configuration. | <code title="list&#40;object&#40;&#123;&#10; name &#61; string&#10; ip_cidr_range &#61; string&#10; region &#61; string&#10; description &#61; optional&#40;string&#41;&#10; enable_private_access &#61; optional&#40;bool, true&#41;&#10; flow_logs_config &#61; optional&#40;object&#40;&#123;&#10; aggregation_interval &#61; optional&#40;string&#41;&#10; filter_expression &#61; optional&#40;string&#41;&#10; flow_sampling &#61; optional&#40;number&#41;&#10; metadata &#61; optional&#40;string&#41;&#10; metadata_fields &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; ipv6 &#61; optional&#40;object&#40;&#123;&#10; access_type &#61; optional&#40;string, &#34;INTERNAL&#34;&#41;&#10; &#125;&#41;&#41;&#10; secondary_ip_ranges &#61; optional&#40;map&#40;string&#41;&#41;&#10;&#125;&#41;&#41;">list&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#91;&#93;</code> |
| [subnets_psc](variables.tf#L218) | List of subnets for Private Service Connect service producers. | <code title="list&#40;object&#40;&#123;&#10; name &#61; string&#10; ip_cidr_range &#61; string&#10; region &#61; string&#10; description &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">list&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#91;&#93;</code> | | [subnets_proxy_only](variables.tf#L217) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | <code title="list&#40;object&#40;&#123;&#10; name &#61; string&#10; ip_cidr_range &#61; string&#10; region &#61; string&#10; description &#61; optional&#40;string&#41;&#10; active &#61; bool&#10;&#125;&#41;&#41;">list&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#91;&#93;</code> |
| [vpc_create](variables.tf#L229) | Create VPC. When set to false, uses a data source to reference existing VPC. | <code>bool</code> | | <code>true</code> | | [subnets_psc](variables.tf#L229) | List of subnets for Private Service Connect service producers. | <code title="list&#40;object&#40;&#123;&#10; name &#61; string&#10; ip_cidr_range &#61; string&#10; region &#61; string&#10; description &#61; optional&#40;string&#41;&#10;&#125;&#41;&#41;">list&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#91;&#93;</code> |
| [vpc_create](variables.tf#L240) | Create VPC. When set to false, uses a data source to reference existing VPC. | <code>bool</code> | | <code>true</code> |
## Outputs ## Outputs

View File

@ -189,3 +189,12 @@ resource "google_compute_subnetwork_iam_member" "binding" {
role = each.value.role role = each.value.role
member = each.value.member member = each.value.member
} }
resource "google_compute_subnetwork_iam_member" "members" {
for_each = var.subnet_iam_members
project = var.project_id
subnetwork = google_compute_subnetwork.subnetwork[each.value.subnet].name
region = google_compute_subnetwork.subnetwork[each.value.subnet].region
role = each.value.role
member = each.value.member
}

View File

@ -177,6 +177,17 @@ variable "subnet_iam_additive" {
nullable = false nullable = false
} }
variable "subnet_iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
subnet = string
}))
nullable = false
default = {}
}
variable "subnets" { variable "subnets" {
description = "Subnet configuration." description = "Subnet configuration."
type = list(object({ type = list(object({

View File

@ -11,6 +11,7 @@ This module allows managing several organization properties:
To manage organization policies, the `orgpolicy.googleapis.com` service should be enabled in the quota project. To manage organization policies, the `orgpolicy.googleapis.com` service should be enabled in the quota project.
## TOC ## TOC
<!-- BEGIN TOC --> <!-- BEGIN TOC -->
- [TOC](#toc) - [TOC](#toc)
- [Example](#example) - [Example](#example)
@ -19,9 +20,7 @@ To manage organization policies, the `orgpolicy.googleapis.com` service should b
- [Organization Policy Factory](#organization-policy-factory) - [Organization Policy Factory](#organization-policy-factory)
- [Organization Policy Custom Constraints](#organization-policy-custom-constraints) - [Organization Policy Custom Constraints](#organization-policy-custom-constraints)
- [Organization Policy Custom Constraints Factory](#organization-policy-custom-constraints-factory) - [Organization Policy Custom Constraints Factory](#organization-policy-custom-constraints-factory)
- [Hierarchical Firewall Policies](#hierarchical-firewall-policies) - [Hierarchical Firewall Policy Attachments](#hierarchical-firewall-policy-attachments)
- [Directly Defined Firewall Policies](#directly-defined-firewall-policies)
- [Firewall Policy Factory](#firewall-policy-factory)
- [Log Sinks](#log-sinks) - [Log Sinks](#log-sinks)
- [Data Access Logs](#data-access-logs) - [Data Access Logs](#data-access-logs)
- [Custom Roles](#custom-roles) - [Custom Roles](#custom-roles)
@ -46,6 +45,12 @@ module "org" {
iam_additive_members = { iam_additive_members = {
"user:compute@example.org" = ["roles/compute.admin", "roles/container.viewer"] "user:compute@example.org" = ["roles/compute.admin", "roles/container.viewer"]
} }
iam_members = {
am1-storage-admin = {
member = "user:am1@example.org"
role = "roles/storage.admin"
}
}
tags = { tags = {
allowexternal = { allowexternal = {
description = "Allow external identities." description = "Allow external identities."
@ -116,14 +121,14 @@ module "org" {
} }
} }
} }
# tftest modules=1 resources=16 inventory=basic.yaml # tftest modules=1 resources=17 inventory=basic.yaml
``` ```
## IAM ## IAM
There are three mutually exclusive ways of managing IAM in this module There are three mutually exclusive ways at the role level of managing IAM in this module
- non-authoritative via the `iam_additive` and `iam_additive_members` variables, where bindings created outside this module will coexist with those managed here - non-authoritative via the `iam_additive`, `iam_additive_members` and `iam_members` variables, where bindings created outside this module will coexist with those managed here
- authoritative via the `group_iam` and `iam` variables, where bindings created outside this module (eg in the console) will be removed at each `terraform apply` cycle if the same role is also managed here - authoritative via the `group_iam` and `iam` variables, where bindings created outside this module (eg in the console) will be removed at each `terraform apply` cycle if the same role is also managed here
- authoritative policy via the `iam_policy` variable, where any binding created outside this module (eg in the console) will be removed at each `terraform apply` cycle regardless of the role - authoritative policy via the `iam_policy` variable, where any binding created outside this module (eg in the console) will be removed at each `terraform apply` cycle regardless of the role
@ -226,109 +231,30 @@ custom.dataprocNoMoreThan10Workers:
description: Cluster cannot have more than 10 workers, including primary and secondary workers. description: Cluster cannot have more than 10 workers, including primary and secondary workers.
``` ```
## Hierarchical Firewall Policies ## Hierarchical Firewall Policy Attachments
Hierarchical firewall policies can be managed in two ways: Hierarchical firewall policies can be managed via the [`net-firewall-policy`](../net-firewall-policy/) module, including support for factories. Once a policy is available, attaching it to the organization can be done either in the firewall policy module itself, or here:
- via the `firewall_policies` variable, to directly define policies and rules in Terraform
- via the `firewall_policy_factory` variable, to leverage external YaML files via a simple "factory" embedded in the module ([see here](../../blueprints/factories) for more context on factories)
Once you have policies (either created via the module or externally), you can associate them using the `firewall_policy_association` variable.
### Directly Defined Firewall Policies
```hcl ```hcl
module "firewall-policy" {
source = "./fabric/modules/net-firewall-policy"
name = "test-1"
parent_id = var.organization_id
# attachment via the firewall policy module
# attachments = {
# org = var.organization_id
# }
}
module "org" { module "org" {
source = "./fabric/modules/organization" source = "./fabric/modules/organization"
organization_id = var.organization_id organization_id = var.organization_id
firewall_policies = { # attachment via the organization module
iap-policy = { firewall_policy_associations = {
allow-admins = { test-1 = module.firewall-policy.id
description = "Access from the admin subnet to all subnets"
direction = "INGRESS"
action = "allow"
priority = 1000
ranges = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
ports = { all = [] }
target_service_accounts = null
target_resources = null
logging = false
}
allow-iap-ssh = {
description = "Always allow ssh from IAP."
direction = "INGRESS"
action = "allow"
priority = 100
ranges = ["35.235.240.0/20"]
ports = {
tcp = ["22"]
}
target_service_accounts = null
target_resources = null
logging = false
}
}
}
firewall_policy_association = {
iap_policy = "iap-policy"
} }
} }
# tftest modules=1 resources=4 inventory=hfw.yaml # tftest modules=2 resources=2
```
### Firewall Policy Factory
The in-built factory allows you to define a single policy, using one file for rules, and an optional file for CIDR range substitution variables. Remember that non-absolute paths are relative to the root module (the folder where you run `terraform`).
```hcl
module "org" {
source = "./fabric/modules/organization"
organization_id = var.organization_id
firewall_policy_factory = {
cidr_file = "configs/firewall-policies/cidrs.yaml"
policy_name = "iap-policy"
rules_file = "configs/firewall-policies/rules.yaml"
}
firewall_policy_association = {
iap_policy = module.org.firewall_policy_id["iap-policy"]
}
}
# tftest modules=1 resources=4 files=cidrs,rules inventory=hfw.yaml
```
```yaml
# tftest-file id=cidrs path=configs/firewall-policies/cidrs.yaml
rfc1918:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
```
```yaml
# tftest-file id=rules path=configs/firewall-policies/rules.yaml
allow-admins:
description: Access from the admin subnet to all subnets
direction: INGRESS
action: allow
priority: 1000
ranges:
- $rfc1918
ports:
all: []
target_resources: null
logging: false
allow-iap-ssh:
description: "Always allow ssh from IAP."
direction: INGRESS
action: allow
priority: 100
ranges:
- 35.235.240.0/20
ports:
tcp: ["22"]
target_resources: null
logging: false
``` ```
## Log Sinks ## Log Sinks
@ -530,18 +456,14 @@ module "org" {
``` ```
<!-- TFDOC OPTS files:1 --> <!-- TFDOC OPTS files:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | resources | | name | description | resources |
|---|---|---| |---|---|---|
| [firewall-policies.tf](./firewall-policies.tf) | Hierarchical firewall policies. | <code>google_compute_firewall_policy</code> · <code>google_compute_firewall_policy_association</code> · <code>google_compute_firewall_policy_rule</code> |
| [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | <code>google_organization_iam_binding</code> · <code>google_organization_iam_custom_role</code> · <code>google_organization_iam_member</code> · <code>google_organization_iam_policy</code> | | [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | <code>google_organization_iam_binding</code> · <code>google_organization_iam_custom_role</code> · <code>google_organization_iam_member</code> · <code>google_organization_iam_policy</code> |
| [logging.tf](./logging.tf) | Log sinks and data access logs. | <code>google_bigquery_dataset_iam_member</code> · <code>google_logging_organization_exclusion</code> · <code>google_logging_organization_sink</code> · <code>google_organization_iam_audit_config</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> | | [logging.tf](./logging.tf) | Log sinks and data access logs. | <code>google_bigquery_dataset_iam_member</code> · <code>google_logging_organization_exclusion</code> · <code>google_logging_organization_sink</code> · <code>google_organization_iam_audit_config</code> · <code>google_project_iam_member</code> · <code>google_pubsub_topic_iam_member</code> · <code>google_storage_bucket_iam_member</code> |
| [main.tf](./main.tf) | Module-level locals and resources. | <code>google_essential_contacts_contact</code> | | [main.tf](./main.tf) | Module-level locals and resources. | <code>google_compute_firewall_policy_association</code> · <code>google_essential_contacts_contact</code> |
| [org-policy-custom-constraints.tf](./org-policy-custom-constraints.tf) | None | <code>google_org_policy_custom_constraint</code> | | [org-policy-custom-constraints.tf](./org-policy-custom-constraints.tf) | None | <code>google_org_policy_custom_constraint</code> |
| [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | <code>google_org_policy_policy</code> | | [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | <code>google_org_policy_policy</code> |
| [outputs.tf](./outputs.tf) | Module outputs. | | | [outputs.tf](./outputs.tf) | Module outputs. | |
@ -553,27 +475,26 @@ module "org" {
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [organization_id](variables.tf#L226) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | | | [organization_id](variables.tf#L209) | Organization id in organizations/nnnnnn format. | <code>string</code> | ✓ | |
| [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [contacts](variables.tf#L17) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [firewall_policies](variables.tf#L31) | Hierarchical firewall policy rules created in the organization. | <code title="map&#40;map&#40;object&#40;&#123;&#10; action &#61; string&#10; description &#61; string&#10; direction &#61; string&#10; logging &#61; bool&#10; ports &#61; map&#40;list&#40;string&#41;&#41;&#10; priority &#61; number&#10; ranges &#61; list&#40;string&#41;&#10; target_resources &#61; list&#40;string&#41;&#10; target_service_accounts &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;&#41;">map&#40;map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [firewall_policy_associations](variables.tf#L31) | Hierarchical firewall policies to associate to this folder, in association name => policy id format. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [firewall_policy_association](variables.tf#L48) | The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [group_iam](variables.tf#L38) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [firewall_policy_factory](variables.tf#L55) | Configuration for the firewall policy factory. | <code title="object&#40;&#123;&#10; cidr_file &#61; string&#10; policy_name &#61; string&#10; rules_file &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [iam](variables.tf#L45) | IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [group_iam](variables.tf#L65) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive](variables.tf#L52) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam](variables.tf#L72) | IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive_members](variables.tf#L59) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive](variables.tf#L79) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_members](variables.tf#L66) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive_members](variables.tf#L86) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_policy](variables.tf#L76) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> |
| [iam_policy](variables.tf#L93) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> | | [logging_data_access](variables.tf#L82) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_data_access](variables.tf#L99) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [logging_exclusions](variables.tf#L97) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_exclusions](variables.tf#L114) | Logging exclusions for this organization in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [logging_sinks](variables.tf#L104) | Logging sinks to create for the organization. | <code title="map&#40;object&#40;&#123;&#10; bq_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; string&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; include_children &#61; optional&#40;bool, true&#41;&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_sinks](variables.tf#L121) | Logging sinks to create for the organization. | <code title="map&#40;object&#40;&#123;&#10; bq_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; string&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; include_children &#61; optional&#40;bool, true&#41;&#10; type &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [network_tags](variables.tf#L134) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; network &#61; string &#35; project_id&#47;vpc_name&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [network_tags](variables.tf#L151) | Network tags by key name. If `id` is provided, key creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; network &#61; string &#35; project_id&#47;vpc_name&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policies](variables.tf#L156) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L173) | Organization policies applied to this organization keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policies_data_path](variables.tf#L183) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [org_policies_data_path](variables.tf#L200) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> | | [org_policy_custom_constraints](variables.tf#L189) | Organization policy custom constraints keyed by constraint name. | <code title="map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string&#41;&#10; description &#61; optional&#40;string&#41;&#10; action_type &#61; string&#10; condition &#61; string&#10; method_types &#61; list&#40;string&#41;&#10; resource_types &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policy_custom_constraints](variables.tf#L206) | Organization policy custom constraints keyed by constraint name. | <code title="map&#40;object&#40;&#123;&#10; display_name &#61; optional&#40;string&#41;&#10; description &#61; optional&#40;string&#41;&#10; action_type &#61; string&#10; condition &#61; string&#10; method_types &#61; list&#40;string&#41;&#10; resource_types &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [org_policy_custom_constraints_data_path](variables.tf#L203) | Path containing org policy custom constraints in YAML format. | <code>string</code> | | <code>null</code> |
| [org_policy_custom_constraints_data_path](variables.tf#L220) | Path containing org policy custom constraints in YAML format. | <code>string</code> | | <code>null</code> | | [tag_bindings](variables.tf#L218) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
| [tag_bindings](variables.tf#L235) | Tag bindings for this organization, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [tags](variables.tf#L224) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [tags](variables.tf#L241) | Tags by key name. If `id` is provided, key or value creation is skipped. The `iam` attribute behaves like the similarly named one at module level. | <code title="map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; values &#61; optional&#40;map&#40;object&#40;&#123;&#10; description &#61; optional&#40;string, &#34;Managed by the Terraform organization module.&#34;&#41;&#10; iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; id &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs ## Outputs
@ -582,13 +503,11 @@ module "org" {
| [custom_constraint_ids](outputs.tf#L17) | Map of CUSTOM_CONSTRAINTS => ID in the organization. | | | [custom_constraint_ids](outputs.tf#L17) | Map of CUSTOM_CONSTRAINTS => ID in the organization. | |
| [custom_role_id](outputs.tf#L22) | Map of custom role IDs created in the organization. | | | [custom_role_id](outputs.tf#L22) | Map of custom role IDs created in the organization. | |
| [custom_roles](outputs.tf#L35) | Map of custom roles resources created in the organization. | | | [custom_roles](outputs.tf#L35) | Map of custom roles resources created in the organization. | |
| [firewall_policies](outputs.tf#L40) | Map of firewall policy resources created in the organization. | | | [id](outputs.tf#L40) | Fully qualified organization id. | |
| [firewall_policy_id](outputs.tf#L45) | Map of firewall policy ids created in the organization. | | | [network_tag_keys](outputs.tf#L57) | Tag key resources. | |
| [id](outputs.tf#L50) | Fully qualified organization id. | | | [network_tag_values](outputs.tf#L66) | Tag value resources. | |
| [network_tag_keys](outputs.tf#L67) | Tag key resources. | | | [organization_id](outputs.tf#L76) | Organization id dependent on module resources. | |
| [network_tag_values](outputs.tf#L76) | Tag value resources. | | | [sink_writer_identities](outputs.tf#L93) | Writer identities created for each sink. | |
| [organization_id](outputs.tf#L86) | Organization id dependent on module resources. | | | [tag_keys](outputs.tf#L101) | Tag key resources. | |
| [sink_writer_identities](outputs.tf#L103) | Writer identities created for each sink. | | | [tag_values](outputs.tf#L110) | Tag value resources. | |
| [tag_keys](outputs.tf#L111) | Tag key resources. | |
| [tag_values](outputs.tf#L120) | Tag value resources. | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -1,100 +0,0 @@
/**
* Copyright 2022 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.
*/
# tfdoc:file:description Hierarchical firewall policies.
locals {
_factory_cidrs = try(
yamldecode(file(var.firewall_policy_factory.cidr_file)), {}
)
_factory_name = (
try(var.firewall_policy_factory.policy_name, null) == null
? "factory"
: var.firewall_policy_factory.policy_name
)
_factory_rules = try(
yamldecode(file(var.firewall_policy_factory.rules_file)), {}
)
_factory_rules_parsed = {
for name, rule in local._factory_rules : name => merge(rule, {
ranges = flatten([
for r in(rule.ranges == null ? [] : rule.ranges) :
lookup(local._factory_cidrs, trimprefix(r, "$"), r)
])
})
}
_merged_rules = flatten([
for policy, rules in local.firewall_policies : [
for name, rule in rules : merge(rule, {
policy = policy
name = name
})
]
])
firewall_policies = merge(var.firewall_policies, (
length(local._factory_rules) == 0
? {}
: { (local._factory_name) = local._factory_rules_parsed }
))
firewall_rules = {
for r in local._merged_rules : "${r.policy}-${r.name}" => r
}
}
resource "google_compute_firewall_policy" "policy" {
for_each = local.firewall_policies
short_name = each.key
parent = var.organization_id
depends_on = [
google_organization_iam_binding.authoritative,
google_organization_iam_custom_role.roles,
google_organization_iam_member.additive,
google_organization_iam_policy.authoritative,
]
}
resource "google_compute_firewall_policy_rule" "rule" {
for_each = local.firewall_rules
firewall_policy = google_compute_firewall_policy.policy[each.value.policy].id
action = each.value.action
direction = each.value.direction
priority = try(each.value.priority, null)
target_resources = try(each.value.target_resources, null)
target_service_accounts = try(each.value.target_service_accounts, null)
enable_logging = try(each.value.logging, null)
# preview = each.value.preview
description = each.value.description
match {
src_ip_ranges = each.value.direction == "INGRESS" ? each.value.ranges : null
dest_ip_ranges = each.value.direction == "EGRESS" ? each.value.ranges : null
dynamic "layer4_configs" {
for_each = each.value.ports
iterator = port
content {
ip_protocol = port.key
ports = port.value
}
}
}
}
resource "google_compute_firewall_policy_association" "association" {
for_each = var.firewall_policy_association
name = replace(var.organization_id, "/", "-")
attachment_target = var.organization_id
firewall_policy = try(google_compute_firewall_policy.policy[each.value].id, each.value)
}

View File

@ -73,6 +73,13 @@ resource "google_organization_iam_member" "additive" {
member = each.value.member member = each.value.member
} }
resource "google_organization_iam_member" "members" {
for_each = var.iam_members
org_id = local.organization_id_numeric
role = each.value.role
member = each.value.member
}
resource "google_organization_iam_policy" "authoritative" { resource "google_organization_iam_policy" "authoritative" {
count = var.iam_policy != null ? 1 : 0 count = var.iam_policy != null ? 1 : 0
org_id = local.organization_id_numeric org_id = local.organization_id_numeric

View File

@ -26,3 +26,10 @@ resource "google_essential_contacts_contact" "contact" {
language_tag = "en" language_tag = "en"
notification_category_subscriptions = each.value notification_category_subscriptions = each.value
} }
resource "google_compute_firewall_policy_association" "default" {
for_each = var.firewall_policy_associations
attachment_target = var.organization_id
name = each.key
firewall_policy = each.value
}

View File

@ -37,16 +37,6 @@ output "custom_roles" {
value = google_organization_iam_custom_role.roles value = google_organization_iam_custom_role.roles
} }
output "firewall_policies" {
description = "Map of firewall policy resources created in the organization."
value = { for k, v in google_compute_firewall_policy.policy : k => v }
}
output "firewall_policy_id" {
description = "Map of firewall policy ids created in the organization."
value = { for k, v in google_compute_firewall_policy.policy : k => v.id }
}
output "id" { output "id" {
description = "Fully qualified organization id." description = "Fully qualified organization id."
value = var.organization_id value = var.organization_id

View File

@ -28,40 +28,13 @@ variable "custom_roles" {
nullable = false nullable = false
} }
variable "firewall_policies" { variable "firewall_policy_associations" {
description = "Hierarchical firewall policy rules created in the organization." description = "Hierarchical firewall policies to associate to this folder, in association name => policy id format."
type = map(map(object({
action = string
description = string
direction = string
logging = bool
ports = map(list(string))
priority = number
ranges = list(string)
target_resources = list(string)
target_service_accounts = list(string)
# preview = bool
})))
default = {}
}
variable "firewall_policy_association" {
description = "The hierarchical firewall policy to associate to this folder. Must be either a key in the `firewall_policies` map or the id of a policy defined somewhere else."
type = map(string) type = map(string)
default = {} default = {}
nullable = false nullable = false
} }
variable "firewall_policy_factory" {
description = "Configuration for the firewall policy factory."
type = object({
cidr_file = string
policy_name = string
rules_file = string
})
default = null
}
variable "group_iam" { variable "group_iam" {
description = "Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable." description = "Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable."
type = map(list(string)) type = map(list(string))
@ -90,6 +63,16 @@ variable "iam_additive_members" {
nullable = false nullable = false
} }
variable "iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
}))
nullable = false
default = {}
}
variable "iam_policy" { variable "iam_policy" {
description = "IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution." description = "IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution."
type = map(list(string)) type = map(list(string))

View File

@ -10,7 +10,9 @@ This module implements the creation and management of one GCP project including
- [IAM](#iam) - [IAM](#iam)
- [Authoritative IAM](#authoritative-iam) - [Authoritative IAM](#authoritative-iam)
- [Additive IAM](#additive-iam) - [Additive IAM](#additive-iam)
- [Additive IAM by Member](#additive-iam-by-member) - [Additive IAM by Role](#additive-iam-by-role)
- [Additive IAM by Principal](#additive-iam-by-principal)
- [Additive IAM by Binding](#additive-iam-by-binding)
- [Service Identities and Authoritative IAM](#service-identities-and-authoritative-iam) - [Service Identities and Authoritative IAM](#service-identities-and-authoritative-iam)
- [Using Shortcodes for Service Identities in Additive Iam](#using-shortcodes-for-service-identities-in-additive-iam) - [Using Shortcodes for Service Identities in Additive Iam](#using-shortcodes-for-service-identities-in-additive-iam)
- [Service Identities Requiring Manual Iam Grants](#service-identities-requiring-manual-iam-grants) - [Service Identities Requiring Manual Iam Grants](#service-identities-requiring-manual-iam-grants)
@ -49,7 +51,7 @@ module "project" {
IAM is managed via several variables that implement different levels of control: IAM is managed via several variables that implement different levels of control:
- `group_iam` and `iam` configure authoritative bindings that manage individual roles exclusively, mapping to the [`google_project_iam_binding`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_binding) resource - `group_iam` and `iam` configure authoritative bindings that manage individual roles exclusively, mapping to the [`google_project_iam_binding`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_binding) resource
- `iam_additive` and `iam_additive_members` configure additive bindings that only manage individual role/member pairs, mapping to the [`google_project_iam_member`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_member) resource - `iam_additive`, `iam_additive_members` and `iam_members` configure additive bindings that only manage individual role/member pairs, mapping to the [`google_project_iam_member`](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_member) resource
- `iam_policy` which controls the entire IAM policy for the project, where any binding created outside this module (eg in the console) will be removed at each `terraform apply` cycle regardless of the role - `iam_policy` which controls the entire IAM policy for the project, where any binding created outside this module (eg in the console) will be removed at each `terraform apply` cycle regardless of the role
The authoritative and additive approaches can be used together, provided different roles are managed by each. The IAM policy is incompatible with the other approaches, and must be used with extreme care. The authoritative and additive approaches can be used together, provided different roles are managed by each. The IAM policy is incompatible with the other approaches, and must be used with extreme care.
@ -109,6 +111,10 @@ module "project" {
Additive IAM is typically used where bindings for specific roles are controlled by different modules or in different Terraform stages. One example is when the project is created by one team but a different team manages service account creation for the project, and some of the project-level roles overlap in the two configurations. Additive IAM is typically used where bindings for specific roles are controlled by different modules or in different Terraform stages. One example is when the project is created by one team but a different team manages service account creation for the project, and some of the project-level roles overlap in the two configurations.
#### Additive IAM by Role
Additive IAM is supported via the `iam_additive` variable which is keyed by role:
```hcl ```hcl
module "project" { module "project" {
source = "./fabric/modules/project" source = "./fabric/modules/project"
@ -129,7 +135,9 @@ module "project" {
# tftest modules=1 resources=5 inventory=iam-additive.yaml # tftest modules=1 resources=5 inventory=iam-additive.yaml
``` ```
### Additive IAM by Member #### Additive IAM by Principal
Additive IAM is also supported via the `iam_additive_members` variable which is keyed by principal:
```hcl ```hcl
module "project" { module "project" {
@ -144,6 +152,33 @@ module "project" {
# tftest modules=1 resources=4 inventory=iam-additive-members.yaml # tftest modules=1 resources=4 inventory=iam-additive-members.yaml
``` ```
#### Additive IAM by Binding
When the above approaches to additive IAM are unworkable due to dynamically generated principals, the `iam_members` variable allows specifying individual role/principal pairs using arbitrary keys:
```hcl
module "project" {
source = "./fabric/modules/project"
name = "project-example"
iam_members = {
one-owner = {
member = "user:one@example.org"
role = "roles/owner"
}
two-viewer = {
member = "user:two@example.org"
role = "roles/viewer"
}
two-compute-admin = {
member = "user:two@example.org"
role = "roles/compute.admin"
}
}
}
# tftest modules=1 resources=4 inventory=iam-members.yaml
```
### Service Identities and Authoritative IAM ### Service Identities and Authoritative IAM
As mentioned above, there are cases where authoritative management of specific IAM roles results in removal of default bindings from service identities. One example is outlined below, with a simple workaround leveraging the `service_accounts` output to identify the service identity. A full list of service identities and their roles can be found [here](https://cloud.google.com/iam/docs/service-agents). As mentioned above, there are cases where authoritative management of specific IAM roles results in removal of default bindings from service identities. One example is outlined below, with a simple workaround leveraging the `service_accounts` output to identify the service identity. A full list of service identities and their roles can be found [here](https://cloud.google.com/iam/docs/service-agents).
@ -260,6 +295,7 @@ module "service-project" {
``` ```
The module allows also granting necessary permissions in host project to service identities by specifying which services will be used in service project in `grant_iam_for_services`. The module allows also granting necessary permissions in host project to service identities by specifying which services will be used in service project in `grant_iam_for_services`.
```hcl ```hcl
module "host-project" { module "host-project" {
source = "./fabric/modules/project" source = "./fabric/modules/project"
@ -622,7 +658,7 @@ output "compute_robot" {
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [name](variables.tf#L161) | Project name and id suffix. | <code>string</code> | ✓ | | | [name](variables.tf#L171) | Project name and id suffix. | <code>string</code> | ✓ | |
| [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | <code>bool</code> | | <code>false</code> | | [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | <code>bool</code> | | <code>false</code> |
| [billing_account](variables.tf#L23) | Billing account id. | <code>string</code> | | <code>null</code> | | [billing_account](variables.tf#L23) | Billing account id. | <code>string</code> | | <code>null</code> |
| [contacts](variables.tf#L29) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [contacts](variables.tf#L29) | List of essential contacts for this resource. Must be in the form EMAIL -> [NOTIFICATION_TYPES]. Valid notification types are ALL, SUSPENSION, SECURITY, TECHNICAL, BILLING, LEGAL, PRODUCT_UPDATES. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
@ -633,30 +669,31 @@ output "compute_robot" {
| [iam](variables.tf#L62) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam](variables.tf#L62) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive](variables.tf#L69) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive](variables.tf#L69) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive_members](variables.tf#L76) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive_members](variables.tf#L76) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_policy](variables.tf#L82) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> | | [iam_members](variables.tf#L82) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [labels](variables.tf#L88) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [iam_policy](variables.tf#L92) | IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>null</code> |
| [lien_reason](variables.tf#L95) | If non-empty, creates a project lien with this description. | <code>string</code> | | <code>&#34;&#34;</code> | | [labels](variables.tf#L98) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_data_access](variables.tf#L101) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [lien_reason](variables.tf#L105) | If non-empty, creates a project lien with this description. | <code>string</code> | | <code>&#34;&#34;</code> |
| [logging_exclusions](variables.tf#L116) | Logging exclusions for this project in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [logging_data_access](variables.tf#L111) | Control activation of data access logs. Format is service => { log type => [exempted members]}. The special 'allServices' key denotes configuration for all services. | <code>map&#40;map&#40;list&#40;string&#41;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [logging_sinks](variables.tf#L123) | Logging sinks to create for this project. | <code title="map&#40;object&#40;&#123;&#10; bq_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; string&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; iam &#61; optional&#40;bool, true&#41;&#10; type &#61; string&#10; unique_writer &#61; optional&#40;bool&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [logging_exclusions](variables.tf#L126) | Logging exclusions for this project in the form {NAME -> FILTER}. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [metric_scopes](variables.tf#L154) | List of projects that will act as metric scopes for this project. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [logging_sinks](variables.tf#L133) | Logging sinks to create for this project. | <code title="map&#40;object&#40;&#123;&#10; bq_partitioned_table &#61; optional&#40;bool&#41;&#10; description &#61; optional&#40;string&#41;&#10; destination &#61; string&#10; disabled &#61; optional&#40;bool, false&#41;&#10; exclusions &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; filter &#61; string&#10; iam &#61; optional&#40;bool, true&#41;&#10; type &#61; string&#10; unique_writer &#61; optional&#40;bool&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [org_policies](variables.tf#L166) | Organization policies applied to this project keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [metric_scopes](variables.tf#L164) | List of projects that will act as metric scopes for this project. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [org_policies_data_path](variables.tf#L193) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> | | [org_policies](variables.tf#L176) | Organization policies applied to this project keyed by policy name. | <code title="map&#40;object&#40;&#123;&#10; inherit_from_parent &#61; optional&#40;bool&#41; &#35; for list policies only.&#10; reset &#61; optional&#40;bool&#41;&#10; rules &#61; optional&#40;list&#40;object&#40;&#123;&#10; allow &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; deny &#61; optional&#40;object&#40;&#123;&#10; all &#61; optional&#40;bool&#41;&#10; values &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;&#10; enforce &#61; optional&#40;bool&#41; &#35; for boolean policies only.&#10; condition &#61; optional&#40;object&#40;&#123;&#10; description &#61; optional&#40;string&#41;&#10; expression &#61; optional&#40;string&#41;&#10; location &#61; optional&#40;string&#41;&#10; title &#61; optional&#40;string&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [oslogin](variables.tf#L199) | Enable OS Login. | <code>bool</code> | | <code>false</code> | | [org_policies_data_path](variables.tf#L203) | Path containing org policies in YAML format. | <code>string</code> | | <code>null</code> |
| [oslogin_admins](variables.tf#L205) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [oslogin](variables.tf#L209) | Enable OS Login. | <code>bool</code> | | <code>false</code> |
| [oslogin_users](variables.tf#L213) | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [oslogin_admins](variables.tf#L215) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [parent](variables.tf#L220) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> | | [oslogin_users](variables.tf#L223) | List of IAM-style identities that will be granted roles necessary for OS Login users. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [prefix](variables.tf#L230) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> | | [parent](variables.tf#L230) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | <code>string</code> | | <code>null</code> |
| [project_create](variables.tf#L240) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> | | [prefix](variables.tf#L240) | Optional prefix used to generate project id and name. | <code>string</code> | | <code>null</code> |
| [service_config](variables.tf#L246) | Configure service API activation. | <code title="object&#40;&#123;&#10; disable_on_destroy &#61; bool&#10; disable_dependent_services &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; disable_on_destroy &#61; false&#10; disable_dependent_services &#61; false&#10;&#125;">&#123;&#8230;&#125;</code> | | [project_create](variables.tf#L250) | Create project. When set to false, uses a data source to reference existing project. | <code>bool</code> | | <code>true</code> |
| [service_encryption_key_ids](variables.tf#L258) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [service_config](variables.tf#L256) | Configure service API activation. | <code title="object&#40;&#123;&#10; disable_on_destroy &#61; bool&#10; disable_dependent_services &#61; bool&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; disable_on_destroy &#61; false&#10; disable_dependent_services &#61; false&#10;&#125;">&#123;&#8230;&#125;</code> |
| [service_perimeter_bridges](variables.tf#L265) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list&#40;string&#41;</code> | | <code>null</code> | | [service_encryption_key_ids](variables.tf#L268) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_perimeter_standard](variables.tf#L272) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> | | [service_perimeter_bridges](variables.tf#L275) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | <code>list&#40;string&#41;</code> | | <code>null</code> |
| [services](variables.tf#L278) | Service APIs to enable. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> | | [service_perimeter_standard](variables.tf#L282) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | <code>string</code> | | <code>null</code> |
| [shared_vpc_host_config](variables.tf#L284) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object&#40;&#123;&#10; enabled &#61; bool&#10; service_projects &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [services](variables.tf#L288) | Service APIs to enable. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |
| [shared_vpc_service_config](variables.tf#L293) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_iam_grants &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; host_project &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> | | [shared_vpc_host_config](variables.tf#L294) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | <code title="object&#40;&#123;&#10; enabled &#61; bool&#10; service_projects &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [skip_delete](variables.tf#L315) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> | | [shared_vpc_service_config](variables.tf#L303) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | <code title="object&#40;&#123;&#10; host_project &#61; string&#10; service_identity_iam &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; service_iam_grants &#61; optional&#40;list&#40;string&#41;, &#91;&#93;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; host_project &#61; null&#10;&#125;">&#123;&#8230;&#125;</code> |
| [tag_bindings](variables.tf#L321) | Tag bindings for this project, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> | | [skip_delete](variables.tf#L325) | Allows the underlying resources to be destroyed without destroying the project itself. | <code>bool</code> | | <code>false</code> |
| [tag_bindings](variables.tf#L331) | Tag bindings for this project, in key => tag value id format. | <code>map&#40;string&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -97,6 +97,17 @@ resource "google_project_iam_member" "additive" {
] ]
} }
resource "google_project_iam_member" "members" {
for_each = var.iam_members
project = local.project.project_id
role = each.value.role
member = each.value.member
depends_on = [
google_project_service.project_services,
google_project_iam_custom_role.roles
]
}
resource "google_project_iam_member" "oslogin_iam_serviceaccountuser" { resource "google_project_iam_member" "oslogin_iam_serviceaccountuser" {
for_each = var.oslogin ? toset(distinct(concat(var.oslogin_admins, var.oslogin_users))) : toset([]) for_each = var.oslogin ? toset(distinct(concat(var.oslogin_admins, var.oslogin_users))) : toset([])
project = local.project.project_id project = local.project.project_id

View File

@ -79,6 +79,16 @@ variable "iam_additive_members" {
default = {} default = {}
} }
variable "iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
}))
nullable = false
default = {}
}
variable "iam_policy" { variable "iam_policy" {
description = "IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution." description = "IAM authoritative policy in {ROLE => [MEMBERS]} format. Roles and members not explicitly listed will be cleared, use with extreme caution."
type = map(list(string)) type = map(list(string))

View File

@ -2,6 +2,15 @@
This module allows managing a single Cloud Source Repository, including IAM bindings and basic Cloud Build triggers. This module allows managing a single Cloud Source Repository, including IAM bindings and basic Cloud Build triggers.
<!-- BEGIN TOC -->
- [Examples](#examples)
- [Repository with IAM](#repository-with-iam)
- [Repository with Cloud Build trigger](#repository-with-cloud-build-trigger)
- [Files](#files)
- [Variables](#variables)
- [Outputs](#outputs)
<!-- END TOC -->
## Examples ## Examples
### Repository with IAM ### Repository with IAM
@ -14,8 +23,14 @@ module "repo" {
iam = { iam = {
"roles/source.reader" = ["user:foo@example.com"] "roles/source.reader" = ["user:foo@example.com"]
} }
iam_members = {
am1-reader = {
member = "user:am1@example.com"
role = "roles/source.reader"
}
}
} }
# tftest modules=1 resources=2 inventory=simple.yaml # tftest modules=1 resources=3 inventory=simple.yaml
``` ```
### Repository with Cloud Build trigger ### Repository with Cloud Build trigger
@ -46,7 +61,6 @@ module "repo" {
<!-- TFDOC OPTS files:1 --> <!-- TFDOC OPTS files:1 -->
<!-- BEGIN TFDOC --> <!-- BEGIN TFDOC -->
## Files ## Files
| name | description | resources | | name | description | resources |
@ -61,13 +75,14 @@ module "repo" {
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [name](variables.tf#L44) | Repository name. | <code>string</code> | ✓ | | | [name](variables.tf#L54) | Repository name. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L49) | Project used for resources. | <code>string</code> | ✓ | | | [project_id](variables.tf#L59) | Project used for resources. | <code>string</code> | ✓ | |
| [group_iam](variables.tf#L17) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [group_iam](variables.tf#L17) | Authoritative IAM binding for organization groups, in {GROUP_EMAIL => [ROLES]} format. Group emails need to be static. Can be used in combination with the `iam` variable. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam](variables.tf#L24) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam](variables.tf#L24) | IAM bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive](variables.tf#L31) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive](variables.tf#L31) | IAM additive bindings in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [iam_additive_members](variables.tf#L38) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_additive_members](variables.tf#L38) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [triggers](variables.tf#L54) | Cloud Build triggers. | <code title="map&#40;object&#40;&#123;&#10; filename &#61; string&#10; included_files &#61; list&#40;string&#41;&#10; service_account &#61; string&#10; substitutions &#61; map&#40;string&#41;&#10; template &#61; object&#40;&#123;&#10; branch_name &#61; string&#10; project_id &#61; string&#10; tag_name &#61; string&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam_members](variables.tf#L44) | Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop. | <code title="map&#40;object&#40;&#123;&#10; member &#61; string&#10; role &#61; string&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [triggers](variables.tf#L64) | Cloud Build triggers. | <code title="map&#40;object&#40;&#123;&#10; filename &#61; string&#10; included_files &#61; list&#40;string&#41;&#10; service_account &#61; string&#10; substitutions &#61; map&#40;string&#41;&#10; template &#61; object&#40;&#123;&#10; branch_name &#61; string&#10; project_id &#61; string&#10; tag_name &#61; string&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs ## Outputs
@ -76,5 +91,4 @@ module "repo" {
| [id](outputs.tf#L17) | Fully qualified repository id. | | | [id](outputs.tf#L17) | Fully qualified repository id. | |
| [name](outputs.tf#L22) | Repository name. | | | [name](outputs.tf#L22) | Repository name. | |
| [url](outputs.tf#L27) | Repository URL. | | | [url](outputs.tf#L27) | Repository URL. | |
<!-- END TFDOC --> <!-- END TFDOC -->

View File

@ -65,3 +65,11 @@ resource "google_sourcerepo_repository_iam_member" "additive" {
role = each.value.role role = each.value.role
member = each.value.member member = each.value.member
} }
resource "google_sourcerepo_repository_iam_member" "members" {
for_each = var.iam_members
project = var.project_id
repository = google_sourcerepo_repository.default.name
role = each.value.role
member = each.value.member
}

View File

@ -41,6 +41,16 @@ variable "iam_additive_members" {
default = {} default = {}
} }
variable "iam_members" {
description = "Individual additive IAM bindings, use this when iam_additive does not work due to dynamic resources. Keys are arbitrary and only used for the internal loop."
type = map(object({
member = string
role = string
}))
nullable = false
default = {}
}
variable "name" { variable "name" {
description = "Repository name." description = "Repository name."
type = string type = string

View File

@ -13,21 +13,611 @@
# limitations under the License. # limitations under the License.
values: values:
module.test.module.folder.google_compute_firewall_policy.policy["prefix-fw-policy"]: module.test.module.firewall-policy.google_compute_firewall_policy.hierarchical[0]:
short_name: prefix-fw-policy description: null
short_name: default
timeouts: null
module.test.module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["egress/allow-admins"]:
action: allow
description: Access from the admin subnet to all subnets
direction: INGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: all
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
src_region_codes: null
src_threat_intelligences: null
priority: 1000
target_resources: null
target_service_accounts: null
timeouts: null
module.test.module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["egress/allow-healthchecks"]:
action: allow
description: Enable HTTP and HTTPS healthchecks
direction: INGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: all
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 35.191.0.0/16
- 130.211.0.0/22
- 209.85.152.0/22
- 209.85.204.0/22
src_region_codes: null
src_threat_intelligences: null
priority: 1001
target_resources: null
target_service_accounts: null
timeouts: null
module.test.module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["egress/allow-icmp"]:
action: allow
description: Enable ICMP
direction: INGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: all
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 0.0.0.0/0
src_region_codes: null
src_threat_intelligences: null
priority: 1003
target_resources: null
target_service_accounts: null
timeouts: null
module.test.module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["egress/allow-ssh-from-iap"]:
action: allow
description: Enable SSH from IAP
direction: INGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: all
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 35.235.240.0/20
src_region_codes: null
src_threat_intelligences: null
priority: 1002
target_resources: null
target_service_accounts: null
timeouts: null
module.test.module.folder-workload.google_folder.folder[0]:
display_name: prefix-workload
timeouts: null
module.test.module.folder.google_bigquery_dataset_iam_member.bq-sinks-binding["audit-logs"]:
condition: []
role: roles/bigquery.dataEditor
module.test.module.folder.google_bigquery_dataset_iam_member.bq-sinks-binding["vpc-sc"]:
condition: []
role: roles/bigquery.dataEditor
module.test.module.folder.google_folder.folder[0]: module.test.module.folder.google_folder.folder[0]:
display_name: ShieldedMVP display_name: ShieldedMVP
parent: organizations/1234567890123 parent: organizations/1234567890123
timeouts: null
module.test.module.folder.google_folder_iam_binding.authoritative["roles/editor"]:
condition: []
members:
- group:gcp-data-engineers@example.com
role: roles/editor
module.test.module.folder.google_folder_iam_binding.authoritative["roles/iam.serviceAccountTokenCreator"]:
condition: []
members:
- group:gcp-data-engineers@example.com
role: roles/iam.serviceAccountTokenCreator
module.test.module.folder.google_logging_folder_sink.sink["audit-logs"]:
description: audit-logs (Terraform-managed).
disabled: false
exclusions: []
filter: logName:"/logs/cloudaudit.googleapis.com%2Factivity" OR logName:"/logs/cloudaudit.googleapis.com%2Fsystem_event"
include_children: true
name: audit-logs
module.test.module.folder.google_logging_folder_sink.sink["vpc-sc"]:
description: vpc-sc (Terraform-managed).
disabled: false
exclusions: []
filter: protoPayload.metadata.@type="type.googleapis.com/google.cloud.audit.VpcServiceControlAuditMetadata"
include_children: true
name: vpc-sc
module.test.module.folder.google_org_policy_policy.default["compute.disableGuestAttributesAccess"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: 'TRUE'
values: []
timeouts: null
module.test.module.folder.google_org_policy_policy.default["compute.requireOsLogin"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: 'TRUE'
values: []
timeouts: null
module.test.module.folder.google_org_policy_policy.default["compute.restrictLoadBalancerCreationForTypes"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: null
values:
- allowed_values:
- in:INTERNAL
denied_values: null
timeouts: null
module.test.module.folder.google_org_policy_policy.default["compute.skipDefaultNetworkCreation"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: 'TRUE'
values: []
timeouts: null
module.test.module.folder.google_org_policy_policy.default["compute.vmExternalIpAccess"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: 'TRUE'
enforce: null
values: []
timeouts: null
module.test.module.folder.google_org_policy_policy.default["iam.automaticIamGrantsForDefaultServiceAccounts"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: 'TRUE'
values: []
timeouts: null
module.test.module.folder.google_org_policy_policy.default["iam.disableServiceAccountKeyCreation"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: 'TRUE'
values: []
timeouts: null
module.test.module.folder.google_org_policy_policy.default["iam.disableServiceAccountKeyUpload"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: 'TRUE'
values: []
timeouts: null
module.test.module.folder.google_org_policy_policy.default["run.allowedIngress"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: null
values:
- allowed_values:
- is:internal
denied_values: null
timeouts: null
module.test.module.folder.google_org_policy_policy.default["sql.restrictAuthorizedNetworks"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: 'TRUE'
values: []
timeouts: null
module.test.module.folder.google_org_policy_policy.default["sql.restrictPublicIp"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: 'TRUE'
values: []
timeouts: null
module.test.module.folder.google_org_policy_policy.default["storage.uniformBucketLevelAccess"]:
spec:
- inherit_from_parent: null
reset: null
rules:
- allow_all: null
condition: []
deny_all: null
enforce: 'TRUE'
values: []
timeouts: null
module.test.module.log-export-dataset[0].google_bigquery_dataset.default:
dataset_id: prefix_audit_export
default_encryption_configuration: []
default_partition_expiration_ms: null
default_table_expiration_ms: null
delete_contents_on_destroy: false
description: Terraform managed.
friendly_name: Audit logs export.
location: EU
max_time_travel_hours: '168'
project: prefix-audit-logs
timeouts: null
module.test.module.log-export-project[0].data.google_bigquery_default_service_account.bq_sa[0]:
project: prefix-audit-logs
module.test.module.log-export-project[0].data.google_storage_project_service_account.gcs_sa[0]:
project: prefix-audit-logs
user_project: null
module.test.module.log-export-project[0].google_project.project[0]: module.test.module.log-export-project[0].google_project.project[0]:
auto_create_network: false
billing_account: 123456-123456-123456 billing_account: 123456-123456-123456
labels: null
name: prefix-audit-logs
project_id: prefix-audit-logs project_id: prefix-audit-logs
skip_delete: false
timeouts: null
module.test.module.log-export-project[0].google_project_iam_binding.authoritative["roles/editor"]:
condition: []
members:
- group:gcp-data-security@example.com
project: prefix-audit-logs
role: roles/editor
module.test.module.log-export-project[0].google_project_service.project_services["bigquery.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: prefix-audit-logs
service: bigquery.googleapis.com
timeouts: null
module.test.module.log-export-project[0].google_project_service.project_services["pubsub.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: prefix-audit-logs
service: pubsub.googleapis.com
timeouts: null
module.test.module.log-export-project[0].google_project_service.project_services["stackdriver.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: prefix-audit-logs
service: stackdriver.googleapis.com
timeouts: null
module.test.module.log-export-project[0].google_project_service.project_services["storage.googleapis.com"]:
disable_dependent_services: false
disable_on_destroy: false
project: prefix-audit-logs
service: storage.googleapis.com
timeouts: null
module.test.module.log-export-project[0].google_project_service_identity.jit_si["pubsub.googleapis.com"]:
project: prefix-audit-logs
service: pubsub.googleapis.com
timeouts: null
module.test.module.vpc-sc[0].google_access_context_manager_access_policy.default[0]: module.test.module.vpc-sc[0].google_access_context_manager_access_policy.default[0]:
parent: organizations/1122334455 parent: organizations/1122334455
timeouts: null
title: shielded-folder title: shielded-folder
module.test.module.vpc-sc[0].google_access_context_manager_service_perimeter.regular["shielded"]: module.test.module.vpc-sc[0].google_access_context_manager_service_perimeter.regular["shielded"]:
description: null description: null
perimeter_type: PERIMETER_TYPE_REGULAR perimeter_type: PERIMETER_TYPE_REGULAR
spec:
- access_levels: []
egress_policies: []
ingress_policies:
- ingress_from:
- identity_type: null
sources:
- access_level: '*'
resource: null
ingress_to:
- operations:
- method_selectors: []
service_name: '*'
restricted_services:
- accessapproval.googleapis.com
- adsdatahub.googleapis.com
- aiplatform.googleapis.com
- alloydb.googleapis.com
- alpha-documentai.googleapis.com
- analyticshub.googleapis.com
- apigee.googleapis.com
- apigeeconnect.googleapis.com
- artifactregistry.googleapis.com
- assuredworkloads.googleapis.com
- automl.googleapis.com
- baremetalsolution.googleapis.com
- batch.googleapis.com
- beyondcorp.googleapis.com
- bigquery.googleapis.com
- bigquerydatapolicy.googleapis.com
- bigquerydatatransfer.googleapis.com
- bigquerymigration.googleapis.com
- bigqueryreservation.googleapis.com
- bigtable.googleapis.com
- binaryauthorization.googleapis.com
- cloudasset.googleapis.com
- cloudbuild.googleapis.com
- clouddebugger.googleapis.com
- clouderrorreporting.googleapis.com
- cloudfunctions.googleapis.com
- cloudkms.googleapis.com
- cloudprofiler.googleapis.com
- cloudresourcemanager.googleapis.com
- cloudsearch.googleapis.com
- cloudtrace.googleapis.com
- composer.googleapis.com
- compute.googleapis.com
- connectgateway.googleapis.com
- contactcenterinsights.googleapis.com
- container.googleapis.com
- containeranalysis.googleapis.com
- containerfilesystem.googleapis.com
- containerregistry.googleapis.com
- containerthreatdetection.googleapis.com
- contentwarehouse.googleapis.com
- datacatalog.googleapis.com
- dataflow.googleapis.com
- datafusion.googleapis.com
- datalineage.googleapis.com
- datamigration.googleapis.com
- datapipelines.googleapis.com
- dataplex.googleapis.com
- dataproc.googleapis.com
- datastream.googleapis.com
- dialogflow.googleapis.com
- dlp.googleapis.com
- dns.googleapis.com
- documentai.googleapis.com
- domains.googleapis.com
- essentialcontacts.googleapis.com
- eventarc.googleapis.com
- file.googleapis.com
- firebaseappcheck.googleapis.com
- firebaserules.googleapis.com
- firestore.googleapis.com
- gameservices.googleapis.com
- gkebackup.googleapis.com
- gkeconnect.googleapis.com
- gkehub.googleapis.com
- gkemulticloud.googleapis.com
- healthcare.googleapis.com
- iam.googleapis.com
- iamcredentials.googleapis.com
- iaptunnel.googleapis.com
- ids.googleapis.com
- integrations.googleapis.com
- language.googleapis.com
- lifesciences.googleapis.com
- logging.googleapis.com
- managedidentities.googleapis.com
- memcache.googleapis.com
- meshca.googleapis.com
- metastore.googleapis.com
- ml.googleapis.com
- monitoring.googleapis.com
- networkconnectivity.googleapis.com
- networkmanagement.googleapis.com
- networksecurity.googleapis.com
- networkservices.googleapis.com
- notebooks.googleapis.com
- opsconfigmonitoring.googleapis.com
- osconfig.googleapis.com
- oslogin.googleapis.com
- policytroubleshooter.googleapis.com
- privateca.googleapis.com
- pubsub.googleapis.com
- pubsublite.googleapis.com
- recaptchaenterprise.googleapis.com
- recommender.googleapis.com
- redis.googleapis.com
- retail.googleapis.com
- run.googleapis.com
- secretmanager.googleapis.com
- servicecontrol.googleapis.com
- servicedirectory.googleapis.com
- spanner.googleapis.com
- speakerid.googleapis.com
- speech.googleapis.com
- sqladmin.googleapis.com
- storage.googleapis.com
- storagetransfer.googleapis.com
- texttospeech.googleapis.com
- tpu.googleapis.com
- trafficdirector.googleapis.com
- transcoder.googleapis.com
- translate.googleapis.com
- videointelligence.googleapis.com
- vision.googleapis.com
- visionai.googleapis.com
- vpcaccess.googleapis.com
- workstations.googleapis.com
vpc_accessible_services:
- allowed_services:
- accessapproval.googleapis.com
- adsdatahub.googleapis.com
- aiplatform.googleapis.com
- alloydb.googleapis.com
- alpha-documentai.googleapis.com
- analyticshub.googleapis.com
- apigee.googleapis.com
- apigeeconnect.googleapis.com
- artifactregistry.googleapis.com
- assuredworkloads.googleapis.com
- automl.googleapis.com
- baremetalsolution.googleapis.com
- batch.googleapis.com
- beyondcorp.googleapis.com
- bigquery.googleapis.com
- bigquerydatapolicy.googleapis.com
- bigquerydatatransfer.googleapis.com
- bigquerymigration.googleapis.com
- bigqueryreservation.googleapis.com
- bigtable.googleapis.com
- binaryauthorization.googleapis.com
- cloudasset.googleapis.com
- cloudbuild.googleapis.com
- clouddebugger.googleapis.com
- clouderrorreporting.googleapis.com
- cloudfunctions.googleapis.com
- cloudkms.googleapis.com
- cloudprofiler.googleapis.com
- cloudresourcemanager.googleapis.com
- cloudsearch.googleapis.com
- cloudtrace.googleapis.com
- composer.googleapis.com
- compute.googleapis.com
- connectgateway.googleapis.com
- contactcenterinsights.googleapis.com
- container.googleapis.com
- containeranalysis.googleapis.com
- containerfilesystem.googleapis.com
- containerregistry.googleapis.com
- containerthreatdetection.googleapis.com
- contentwarehouse.googleapis.com
- datacatalog.googleapis.com
- dataflow.googleapis.com
- datafusion.googleapis.com
- datalineage.googleapis.com
- datamigration.googleapis.com
- datapipelines.googleapis.com
- dataplex.googleapis.com
- dataproc.googleapis.com
- datastream.googleapis.com
- dialogflow.googleapis.com
- dlp.googleapis.com
- dns.googleapis.com
- documentai.googleapis.com
- domains.googleapis.com
- essentialcontacts.googleapis.com
- eventarc.googleapis.com
- file.googleapis.com
- firebaseappcheck.googleapis.com
- firebaserules.googleapis.com
- firestore.googleapis.com
- gameservices.googleapis.com
- gkebackup.googleapis.com
- gkeconnect.googleapis.com
- gkehub.googleapis.com
- gkemulticloud.googleapis.com
- healthcare.googleapis.com
- iam.googleapis.com
- iamcredentials.googleapis.com
- iaptunnel.googleapis.com
- ids.googleapis.com
- integrations.googleapis.com
- language.googleapis.com
- lifesciences.googleapis.com
- logging.googleapis.com
- managedidentities.googleapis.com
- memcache.googleapis.com
- meshca.googleapis.com
- metastore.googleapis.com
- ml.googleapis.com
- monitoring.googleapis.com
- networkconnectivity.googleapis.com
- networkmanagement.googleapis.com
- networksecurity.googleapis.com
- networkservices.googleapis.com
- notebooks.googleapis.com
- opsconfigmonitoring.googleapis.com
- osconfig.googleapis.com
- oslogin.googleapis.com
- policytroubleshooter.googleapis.com
- privateca.googleapis.com
- pubsub.googleapis.com
- pubsublite.googleapis.com
- recaptchaenterprise.googleapis.com
- recommender.googleapis.com
- redis.googleapis.com
- retail.googleapis.com
- run.googleapis.com
- secretmanager.googleapis.com
- servicecontrol.googleapis.com
- servicedirectory.googleapis.com
- spanner.googleapis.com
- speakerid.googleapis.com
- speech.googleapis.com
- sqladmin.googleapis.com
- storage.googleapis.com
- storagetransfer.googleapis.com
- texttospeech.googleapis.com
- tpu.googleapis.com
- trafficdirector.googleapis.com
- transcoder.googleapis.com
- translate.googleapis.com
- videointelligence.googleapis.com
- vision.googleapis.com
- visionai.googleapis.com
- vpcaccess.googleapis.com
- workstations.googleapis.com
enable_restriction: true
status: []
timeouts: null
title: shielded title: shielded
use_explicit_dry_run_spec: true
counts: counts:
google_access_context_manager_access_policy: 1 google_access_context_manager_access_policy: 1
@ -47,5 +637,7 @@ counts:
google_project_service_identity: 1 google_project_service_identity: 1
google_projects: 1 google_projects: 1
google_storage_project_service_account: 1 google_storage_project_service_account: 1
modules: 6 modules: 7
resources: 38 resources: 38
outputs: {}

View File

@ -13,5 +13,5 @@
# limitations under the License. # limitations under the License.
counts: counts:
modules: 27 modules: 28
resources: 151 resources: 151

View File

@ -13,5 +13,5 @@
# limitations under the License. # limitations under the License.
counts: counts:
modules: 29 modules: 30
resources: 188 resources: 188

View File

@ -13,5 +13,5 @@
# limitations under the License. # limitations under the License.
counts: counts:
modules: 41 modules: 42
resources: 197 resources: 197

View File

@ -13,5 +13,5 @@
# limitations under the License. # limitations under the License.
counts: counts:
modules: 20 modules: 21
resources: 168 resources: 168

View File

@ -13,5 +13,5 @@
# limitations under the License. # limitations under the License.
counts: counts:
modules: 35 modules: 36
resources: 210 resources: 210

View File

@ -168,6 +168,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
for path in inventory_paths: for path in inventory_paths:
# allow tfvars and inventory to be relative to the caller # allow tfvars and inventory to be relative to the caller
path = basedir / path path = basedir / path
relative_path = path.relative_to(_REPO_ROOT)
try: try:
inventory = yaml.safe_load(path.read_text()) inventory = yaml.safe_load(path.read_text())
except (IOError, OSError, yaml.YAMLError) as e: except (IOError, OSError, yaml.YAMLError) as e:
@ -193,34 +194,34 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None,
expected_values = inventory['values'] expected_values = inventory['values']
for address, expected_value in expected_values.items(): for address, expected_value in expected_values.items():
assert address in summary.values, \ assert address in summary.values, \
f'{address} is not a valid address in the plan' f'{relative_path}: {address} is not a valid address in the plan'
for k, v in expected_value.items(): for k, v in expected_value.items():
assert k in summary.values[address], \ assert k in summary.values[address], \
f'{k} not found at {address}' f'{relative_path}: {k} not found at {address}'
plan_value = summary.values[address][k] plan_value = summary.values[address][k]
assert plan_value == v, \ assert plan_value == v, \
f'{k} at {address} failed. Got `{plan_value}`, expected `{v}`' f'{relative_path}: {k} at {address} failed. Got `{plan_value}`, expected `{v}`'
if 'counts' in inventory: if 'counts' in inventory:
expected_counts = inventory['counts'] expected_counts = inventory['counts']
for type_, expected_count in expected_counts.items(): for type_, expected_count in expected_counts.items():
assert type_ in summary.counts, \ assert type_ in summary.counts, \
f'module does not create any resources of type `{type_}`' f'{relative_path}: module does not create any resources of type `{type_}`'
plan_count = summary.counts[type_] plan_count = summary.counts[type_]
assert plan_count == expected_count, \ assert plan_count == expected_count, \
f'count of {type_} resources failed. Got {plan_count}, expected {expected_count}' f'{relative_path}: count of {type_} resources failed. Got {plan_count}, expected {expected_count}'
if 'outputs' in inventory: if 'outputs' in inventory:
expected_outputs = inventory['outputs'] expected_outputs = inventory['outputs']
for output_name, expected_output in expected_outputs.items(): for output_name, expected_output in expected_outputs.items():
assert output_name in summary.outputs, \ assert output_name in summary.outputs, \
f'module does not output `{output_name}`' f'{relative_path}: module does not output `{output_name}`'
output = summary.outputs[output_name] output = summary.outputs[output_name]
# assert 'value' in output, \ # assert 'value' in output, \
# f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)'
plan_output = output.get('value', '__missing__') plan_output = output.get('value', '__missing__')
assert plan_output == expected_output, \ assert plan_output == expected_output, \
f'output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`' f'{relative_path}: output {output_name} failed. Got `{plan_output}`, expected `{expected_output}`'
return summary return summary

View File

@ -0,0 +1,40 @@
# Copyright 2023 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.
values:
module.cloud_run.google_cloud_run_service.service:
autogenerate_revision_name: false
location: europe-west1
metadata:
- {}
name: hello
project: project-id
template:
- metadata:
- annotations:
run.googleapis.com/execution-environment: gen2
spec:
- containers:
- args: null
command: null
env: []
env_from: []
image: us-docker.pkg.dev/cloudrun/container/hello
liveness_probe: []
volume_mounts: []
working_dir: null
volumes: []
counts:
google_cloud_run_service: 1

View File

@ -13,25 +13,49 @@
# limitations under the License. # limitations under the License.
values: values:
module.nginx-mig.google_compute_instance_group_manager.default[0]:
all_instances_config: []
auto_healing_policies: []
base_instance_name: mig-test
description: Terraform managed.
list_managed_instances_results: PAGELESS
name: mig-test
named_port: []
project: my-prj
stateful_disk: []
stateful_external_ip: []
stateful_internal_ip: []
target_pools: null
timeouts: null
version:
- name: default
target_size: []
wait_for_instances: false
wait_for_instances_status: STABLE
zone: europe-west8-b
module.nginx-mig.google_compute_per_instance_config.default["instance-1"]: module.nginx-mig.google_compute_per_instance_config.default["instance-1"]:
instance_group_manager: mig-test
minimal_action: NONE minimal_action: NONE
most_disruptive_allowed_action: REPLACE most_disruptive_allowed_action: REPLACE
name: instance-1 name: instance-1
preserved_state: preserved_state:
- disk: - disk:
- delete_rule: NEVER - delete_rule: NEVER
device_name: persistent-disk-1 device_name: data-1
mode: READ_WRITE mode: READ_WRITE
source: test-disk source: projects/my-prj/zones/europe-west8-b/disks/test-data-1
metadata: metadata:
foo: bar foo: bar
project: my-project project: my-prj
remove_instance_state_on_destroy: false remove_instance_state_on_destroy: false
timeouts: null timeouts: null
zone: europe-west1-b zone: europe-west8-b
counts: counts:
google_compute_autoscaler: 1
google_compute_instance_group_manager: 1 google_compute_instance_group_manager: 1
google_compute_instance_template: 1 google_compute_instance_template: 1
google_compute_per_instance_config: 1 google_compute_per_instance_config: 1
modules: 2
resources: 3
outputs: {}

Some files were not shown because too many files have changed in this diff Show More