diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b22a9f0..ef1e4e60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,28 @@ All notable changes to this project will be documented in this file. ## [Unreleased] - + + +### FAST + +- [[#1583](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1583)] Fix module path for teams cicd ([ludoo](https://github.com/ludoo)) + +### 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)) +- [[#1578](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1578)] Fix: Instance level stateful disk config ([beardedsamwise](https://github.com/beardedsamwise)) +- [[#1582](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1582)] feat(modules/cloud-run): add gen2 exec env support ([LiuVII](https://github.com/LiuVII)) + +### TOOLS + +- [[#1585](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1585)] Print inventory path when a test fails ([juliocc](https://github.com/juliocc)) + +## [25.0.0] - 2023-08-09 + ### 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)) - [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) - [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) - [[#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)) @@ -23,6 +41,7 @@ All notable changes to this project will be documented in this file. ### 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)) - [[#1573](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1573)] Add information about required groups ([wiktorn](https://github.com/wiktorn)) - [[#1545](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1545)] add dataplex autodq base module ([thinhha](https://github.com/thinhha)) - [[#1557](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1557)] renaming net-vpc-swp to net-swp ([skalolazka](https://github.com/skalolazka)) @@ -33,6 +52,8 @@ All notable changes to this project will be documented in this file. ### FAST +- [[#1579](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1579)] Enable team CI/CD impersonation ([williamsmt](https://github.com/williamsmt)) +- [[#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)) - [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) - [[#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)) - [[#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)) @@ -42,6 +63,9 @@ All notable changes to this project will be documented in this file. ### 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)) +- [[#1580](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1580)] Apigee addons ([apichick](https://github.com/apichick)) +- [[#1576](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1576)] **incompatible change:** Refactor firewall policy module ([ludoo](https://github.com/ludoo)) - [[#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)) - [[#1572](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1572)] **incompatible change:** More module descriptions ([ludoo](https://github.com/ludoo)) - [[#1569](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/1569)] Add support for cost management to GKE module ([ludoo](https://github.com/ludoo)) @@ -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 -[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 [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 diff --git a/README.md b/README.md index bae85ce4..f4487ca6 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The current list of modules supports most of the core foundational and networkin 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) -- **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) - **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) diff --git a/blueprints/data-solutions/shielded-folder/README.md b/blueprints/data-solutions/shielded-folder/README.md index 86d3c62c..83e9589d 100644 --- a/blueprints/data-solutions/shielded-folder/README.md +++ b/blueprints/data-solutions/shielded-folder/README.md @@ -209,5 +209,5 @@ module "test" { billing_account_id = "123456-123456-123456" } } -# tftest modules=6 resources=38 inventory=simple.yaml +# tftest modules=7 resources=38 ``` diff --git a/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-ingress-rules.yaml b/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-ingress-rules.yaml new file mode 100644 index 00000000..a267527d --- /dev/null +++ b/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-ingress-rules.yaml @@ -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 diff --git a/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-policy-rules.yaml b/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-policy-rules.yaml deleted file mode 100644 index 6a3b3133..00000000 --- a/blueprints/data-solutions/shielded-folder/data/firewall-policies/hierarchical-policy-rules.yaml +++ /dev/null @@ -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 - \ No newline at end of file diff --git a/blueprints/data-solutions/shielded-folder/main.tf b/blueprints/data-solutions/shielded-folder/main.tf index 3868ec96..5d1145cd 100644 --- a/blueprints/data-solutions/shielded-folder/main.tf +++ b/blueprints/data-solutions/shielded-folder/main.tf @@ -78,11 +78,6 @@ module "folder" { id = var.folder_config.folder_create != null ? null : var.folder_config.folder_id group_iam = local.group_iam 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 ? { for name, attrs in var.log_sinks : name => { bq_partitioned_table = attrs.type == "bigquery" @@ -93,14 +88,24 @@ module "folder" { } : 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" { source = "../../../modules/folder" parent = module.folder.id name = "${var.prefix}-workload" } +#TODO VPCSC: Access levels -#TODO VPCSC: Access levels data "google_projects" "folder-projects" { filter = "parent.id:${split("/", module.folder.id)[1]}" diff --git a/fast/stages/1-resman/README.md b/fast/stages/1-resman/README.md index 1a389364..4dec2945 100644 --- a/fast/stages/1-resman/README.md +++ b/fast/stages/1-resman/README.md @@ -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. | iam-service-account · source-repository | | | [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the teams branch. | iam-service-account · source-repository | | | [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | iam-service-account · source-repository | | +| [cicd-teams.tf](./cicd-teams.tf) | CI/CD resources for individual teams. | iam-service-account · source-repository | | | [main.tf](./main.tf) | Module-level locals and resources. | | | | [organization.tf](./organization.tf) | Organization policies. | organization | | | [outputs-files.tf](./outputs-files.tf) | Output files persistence to local filesystem. | | local_file | @@ -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. | string | | null | | | [tag_names](variables.tf#L227) | Customized names for resource management tags. | object({…}) | | {…} | | | [tags](variables.tf#L248) | Custome secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | -| [team_folders](variables.tf#L269) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | -| [tenants](variables.tf#L279) | Lightweight tenant definitions. | map(object({…})) | | {} | | -| [tenants_config](variables.tf#L295) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | object({…}) | | {} | | +| [team_folders](variables.tf#L269) | Team folders to be created. Format is described in a code comment. | map(object({…})) | | null | | +| [tenants](variables.tf#L285) | Lightweight tenant definitions. | map(object({…})) | | {} | | +| [tenants_config](variables.tf#L301) | Lightweight tenants shared configuration. Roles will be assigned to tenant admin group and service accounts. | object({…}) | | {} | | ## Outputs | name | description | sensitive | consumers | |---|---|:---:|---| -| [cicd_repositories](outputs.tf#L213) | WIF configuration for CI/CD repositories. | | | -| [dataplatform](outputs.tf#L227) | Data for the Data Platform stage. | | | -| [gke_multitenant](outputs.tf#L243) | Data for the GKE multitenant stage. | | 03-gke-multitenant | -| [networking](outputs.tf#L264) | Data for the networking stage. | | | -| [project_factories](outputs.tf#L273) | Data for the project factories stage. | | | -| [providers](outputs.tf#L288) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams | -| [sandbox](outputs.tf#L295) | Data for the sandbox stage. | | xx-sandbox | -| [security](outputs.tf#L309) | Data for the networking stage. | | 02-security | -| [teams](outputs.tf#L319) | Data for the teams stage. | | | -| [tfvars](outputs.tf#L331) | Terraform variable files for the following stages. | ✓ | | +| [cicd_repositories](outputs.tf#L232) | WIF configuration for CI/CD repositories. | | | +| [dataplatform](outputs.tf#L246) | Data for the Data Platform stage. | | | +| [gke_multitenant](outputs.tf#L262) | Data for the GKE multitenant stage. | | 03-gke-multitenant | +| [networking](outputs.tf#L283) | Data for the networking stage. | | | +| [project_factories](outputs.tf#L292) | Data for the project factories stage. | | | +| [providers](outputs.tf#L307) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · xx-sandbox · xx-teams | +| [sandbox](outputs.tf#L314) | Data for the sandbox stage. | | xx-sandbox | +| [security](outputs.tf#L328) | Data for the networking stage. | | 02-security | +| [team_cicd_repositories](outputs.tf#L338) | WIF configuration for Team CI/CD repositories. | | | +| [teams](outputs.tf#L352) | Data for the teams stage. | | | +| [tfvars](outputs.tf#L364) | Terraform variable files for the following stages. | ✓ | | diff --git a/fast/stages/1-resman/branch-teams.tf b/fast/stages/1-resman/branch-teams.tf index 4996f183..9c9f5399 100644 --- a/fast/stages/1-resman/branch-teams.tf +++ b/fast/stages/1-resman/branch-teams.tf @@ -90,10 +90,13 @@ module "branch-teams-team-sa" { display_name = "Terraform team ${each.key} service account." prefix = var.prefix iam = { - "roles/iam.serviceAccountTokenCreator" = ( - each.value.impersonation_groups == null - ? [] - : [for g in each.value.impersonation_groups : "group:${g}"] + "roles/iam.serviceAccountTokenCreator" = concat( + compact([try(module.branch-teams-team-sa-cicd[each.key].iam_email, null)]), + ( + each.value.impersonation_groups == null + ? [] + : [for g in each.value.impersonation_groups : "group:${g}"] + ) ) } } diff --git a/fast/stages/1-resman/cicd-teams.tf b/fast/stages/1-resman/cicd-teams.tf new file mode 100644 index 00000000..cbfc0780 --- /dev/null +++ b/fast/stages/1-resman/cicd-teams.tf @@ -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"] + } +} diff --git a/fast/stages/1-resman/main.tf b/fast/stages/1-resman/main.tf index 95bc1c4f..a30b56fd 100644 --- a/fast/stages/1-resman/main.tf +++ b/fast/stages/1-resman/main.tf @@ -47,6 +47,21 @@ locals { 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 = { stage_2 = [ "0-bootstrap.auto.tfvars.json", diff --git a/fast/stages/1-resman/outputs-files.tf b/fast/stages/1-resman/outputs-files.tf index f7f080dd..2f13adfc 100644 --- a/fast/stages/1-resman/outputs-files.tf +++ b/fast/stages/1-resman/outputs-files.tf @@ -35,7 +35,7 @@ resource "local_file" "tfvars" { } 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" filename = "${local.outputs_location}/workflows/${replace(each.key, "_", "-")}-workflow.yaml" content = try(each.value, null) diff --git a/fast/stages/1-resman/outputs-gcs.tf b/fast/stages/1-resman/outputs-gcs.tf index 5b9f5d85..8e102a41 100644 --- a/fast/stages/1-resman/outputs-gcs.tf +++ b/fast/stages/1-resman/outputs-gcs.tf @@ -30,7 +30,7 @@ resource "google_storage_bucket_object" "tfvars" { } 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 name = "workflows/${replace(each.key, "_", "-")}-workflow.yaml" content = each.value diff --git a/fast/stages/1-resman/outputs.tf b/fast/stages/1-resman/outputs.tf index c15706e2..552d42d7 100644 --- a/fast/stages/1-resman/outputs.tf +++ b/fast/stages/1-resman/outputs.tf @@ -201,6 +201,25 @@ locals { 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 = { folder_ids = local.folder_ids 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" { description = "Data for the teams stage." value = { diff --git a/fast/stages/1-resman/variables.tf b/fast/stages/1-resman/variables.tf index 35030d2a..a613e5ad 100644 --- a/fast/stages/1-resman/variables.tf +++ b/fast/stages/1-resman/variables.tf @@ -272,6 +272,12 @@ variable "team_folders" { descriptive_name = string group_iam = map(list(string)) impersonation_groups = list(string) + cicd = optional(object({ + branch = string + identity_provider = string + name = string + type = string + })) })) default = null } diff --git a/fast/stages/2-networking-a-peering/README.md b/fast/stages/2-networking-a-peering/README.md index 1dcb29f5..69ab7788 100644 --- a/fast/stages/2-networking-a-peering/README.md +++ b/fast/stages/2-networking-a-peering/README.md @@ -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. 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 @@ -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. | dns · dns-response-policy | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | dns | | | [landing.tf](./landing.tf) | Landing VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | | -| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | | +| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder · net-firewall-policy | | | [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | google_monitoring_alert_policy | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | diff --git a/fast/stages/2-networking-a-peering/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-a-peering/data/hierarchical-ingress-rules.yaml new file mode 100644 index 00000000..a267527d --- /dev/null +++ b/fast/stages/2-networking-a-peering/data/hierarchical-ingress-rules.yaml @@ -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 diff --git a/fast/stages/2-networking-a-peering/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-a-peering/data/hierarchical-policy-rules.yaml deleted file mode 100644 index 0172a309..00000000 --- a/fast/stages/2-networking-a-peering/data/hierarchical-policy-rules.yaml +++ /dev/null @@ -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 diff --git a/fast/stages/2-networking-a-peering/main.tf b/fast/stages/2-networking-a-peering/main.tf index 5888752d..8a47a0d5 100644 --- a/fast/stages/2-networking-a-peering/main.tf +++ b/fast/stages/2-networking-a-peering/main.tf @@ -45,13 +45,18 @@ module "folder" { name = "Networking" folder_create = var.folder_ids.networking == null id = var.folder_ids.networking - firewall_policy_factory = { - cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" - policy_name = var.factories_config.firewall_policy_name - rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" - } - firewall_policy_association = { - factory-policy = "factory" + firewall_policy_associations = { + default = module.firewall-policy-default.id + } +} + +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" } } diff --git a/fast/stages/2-networking-b-vpn/README.md b/fast/stages/2-networking-b-vpn/README.md index 75d8d91a..1f43b180 100644 --- a/fast/stages/2-networking-b-vpn/README.md +++ b/fast/stages/2-networking-b-vpn/README.md @@ -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. 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 @@ -393,7 +393,6 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS - ## Files | 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. | dns · dns-response-policy | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | dns | | | [landing.tf](./landing.tf) | Landing VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | | -| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | | +| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder · net-firewall-policy | | | [monitoring-vpn.tf](./monitoring-vpn.tf) | VPN monitoring alerts. | | google_monitoring_alert_policy | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | @@ -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. | | | | [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. | | | - diff --git a/fast/stages/2-networking-b-vpn/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-b-vpn/data/hierarchical-ingress-rules.yaml new file mode 100644 index 00000000..a267527d --- /dev/null +++ b/fast/stages/2-networking-b-vpn/data/hierarchical-ingress-rules.yaml @@ -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 diff --git a/fast/stages/2-networking-b-vpn/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-b-vpn/data/hierarchical-policy-rules.yaml deleted file mode 100644 index 0172a309..00000000 --- a/fast/stages/2-networking-b-vpn/data/hierarchical-policy-rules.yaml +++ /dev/null @@ -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 diff --git a/fast/stages/2-networking-b-vpn/main.tf b/fast/stages/2-networking-b-vpn/main.tf index 5888752d..8a47a0d5 100644 --- a/fast/stages/2-networking-b-vpn/main.tf +++ b/fast/stages/2-networking-b-vpn/main.tf @@ -45,13 +45,18 @@ module "folder" { name = "Networking" folder_create = var.folder_ids.networking == null id = var.folder_ids.networking - firewall_policy_factory = { - cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" - policy_name = var.factories_config.firewall_policy_name - rules_file = "${var.factories_config.data_dir}/hierarchical-policy-rules.yaml" - } - firewall_policy_association = { - factory-policy = "factory" + firewall_policy_associations = { + default = module.firewall-policy-default.id + } +} + +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" } } diff --git a/fast/stages/2-networking-c-nva/README.md b/fast/stages/2-networking-c-nva/README.md index a0f918a0..a587d68c 100644 --- a/fast/stages/2-networking-c-nva/README.md +++ b/fast/stages/2-networking-c-nva/README.md @@ -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. 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 @@ -452,7 +452,6 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS - ## Files | 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. | dns · dns-response-policy | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | dns | | | [landing.tf](./landing.tf) | Landing VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | | -| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | | +| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder · net-firewall-policy | | | [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | google_monitoring_alert_policy | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | | [nva.tf](./nva.tf) | None | compute-mig · compute-vm · simple-nva | | @@ -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. | | | | [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. | | | - diff --git a/fast/stages/2-networking-c-nva/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-c-nva/data/hierarchical-ingress-rules.yaml new file mode 100644 index 00000000..a267527d --- /dev/null +++ b/fast/stages/2-networking-c-nva/data/hierarchical-ingress-rules.yaml @@ -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 diff --git a/fast/stages/2-networking-c-nva/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-c-nva/data/hierarchical-policy-rules.yaml deleted file mode 100644 index 0172a309..00000000 --- a/fast/stages/2-networking-c-nva/data/hierarchical-policy-rules.yaml +++ /dev/null @@ -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 diff --git a/fast/stages/2-networking-c-nva/main.tf b/fast/stages/2-networking-c-nva/main.tf index 0a56396e..aff2eaf5 100644 --- a/fast/stages/2-networking-c-nva/main.tf +++ b/fast/stages/2-networking-c-nva/main.tf @@ -46,12 +46,17 @@ module "folder" { name = "Networking" folder_create = var.folder_ids.networking == null id = var.folder_ids.networking - firewall_policy_factory = { - cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" - 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 + firewall_policy_associations = { + default = module.firewall-policy-default.id + } +} + +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" } } diff --git a/fast/stages/2-networking-d-separate-envs/README.md b/fast/stages/2-networking-d-separate-envs/README.md index 1276799b..eae0eaf4 100644 --- a/fast/stages/2-networking-d-separate-envs/README.md +++ b/fast/stages/2-networking-d-separate-envs/README.md @@ -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. 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 @@ -317,14 +317,13 @@ Regions are defined via the `regions` variable which sets up a mapping between t - ## Files | name | description | modules | resources | |---|---|---|---| | [dns-dev.tf](./dns-dev.tf) | Development spoke DNS zones and peerings setup. | dns · dns-response-policy | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | dns · dns-response-policy | | -| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | | +| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder · net-firewall-policy | | | [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | google_monitoring_alert_policy | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | | [outputs.tf](./outputs.tf) | Module outputs. | | google_storage_bucket_object · local_file | @@ -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. | | | | [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. | | | - diff --git a/fast/stages/2-networking-d-separate-envs/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-d-separate-envs/data/hierarchical-ingress-rules.yaml new file mode 100644 index 00000000..a267527d --- /dev/null +++ b/fast/stages/2-networking-d-separate-envs/data/hierarchical-ingress-rules.yaml @@ -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 diff --git a/fast/stages/2-networking-d-separate-envs/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-d-separate-envs/data/hierarchical-policy-rules.yaml deleted file mode 100644 index 0172a309..00000000 --- a/fast/stages/2-networking-d-separate-envs/data/hierarchical-policy-rules.yaml +++ /dev/null @@ -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 diff --git a/fast/stages/2-networking-d-separate-envs/main.tf b/fast/stages/2-networking-d-separate-envs/main.tf index 7e9ddc26..5145e186 100644 --- a/fast/stages/2-networking-d-separate-envs/main.tf +++ b/fast/stages/2-networking-d-separate-envs/main.tf @@ -41,13 +41,17 @@ module "folder" { name = "Networking" folder_create = var.folder_ids.networking == null id = var.folder_ids.networking - firewall_policy_factory = { - cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" - 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 + firewall_policy_associations = { + default = module.firewall-policy-default.id } } +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" + } +} diff --git a/fast/stages/2-networking-e-nva-bgp/README.md b/fast/stages/2-networking-e-nva-bgp/README.md index c5117b5d..a9f444e1 100644 --- a/fast/stages/2-networking-e-nva-bgp/README.md +++ b/fast/stages/2-networking-e-nva-bgp/README.md @@ -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. 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 @@ -476,7 +476,6 @@ DNS configurations are centralised in the `dns-*.tf` files. Spokes delegate DNS - ## Files | 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. | dns · dns-response-policy | | | [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | dns | | | [landing.tf](./landing.tf) | Landing VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | | -| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | | +| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder · net-firewall-policy | | | [monitoring-vpn-onprem.tf](./monitoring-vpn-onprem.tf) | VPN monitoring alerts. | | google_monitoring_alert_policy | | [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard | | [ncc.tf](./ncc.tf) | None | ncc-spoke-ra | | @@ -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. | | | | [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. | | | - diff --git a/fast/stages/2-networking-e-nva-bgp/data/hierarchical-ingress-rules.yaml b/fast/stages/2-networking-e-nva-bgp/data/hierarchical-ingress-rules.yaml new file mode 100644 index 00000000..a267527d --- /dev/null +++ b/fast/stages/2-networking-e-nva-bgp/data/hierarchical-ingress-rules.yaml @@ -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 diff --git a/fast/stages/2-networking-e-nva-bgp/data/hierarchical-policy-rules.yaml b/fast/stages/2-networking-e-nva-bgp/data/hierarchical-policy-rules.yaml deleted file mode 100644 index 0172a309..00000000 --- a/fast/stages/2-networking-e-nva-bgp/data/hierarchical-policy-rules.yaml +++ /dev/null @@ -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 diff --git a/fast/stages/2-networking-e-nva-bgp/main.tf b/fast/stages/2-networking-e-nva-bgp/main.tf index 0a56396e..aff2eaf5 100644 --- a/fast/stages/2-networking-e-nva-bgp/main.tf +++ b/fast/stages/2-networking-e-nva-bgp/main.tf @@ -46,12 +46,17 @@ module "folder" { name = "Networking" folder_create = var.folder_ids.networking == null id = var.folder_ids.networking - firewall_policy_factory = { - cidr_file = "${var.factories_config.data_dir}/cidrs.yaml" - 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 + firewall_policy_associations = { + default = module.firewall-policy-default.id + } +} + +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" } } diff --git a/modules/README.md b/modules/README.md index 653c25e8..c100fcc5 100644 --- a/modules/README.md +++ b/modules/README.md @@ -45,6 +45,7 @@ These modules are used in the examples included in this repository. If you are u - [Cloud Endpoints](./endpoints) - [DNS](./dns) - [DNS Response Policy](./dns-response-policy/) +- [Firewall policy](./net-firewall-policy) - [External Application Load Balancer](./net-lb-app-ext/) - [External Passthrough Network Load Balancer](./net-lb-ext) - [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) - [VPC](./net-vpc) - [VPC firewall](./net-vpc-firewall) -- [VPC firewall policy](./net-vpc-firewall-policy) - [VPN dynamic](./net-vpn-dynamic) - [VPC peering](./net-vpc-peering) - [VPN HA](./net-vpn-ha) diff --git a/modules/cloud-run/README.md b/modules/cloud-run/README.md index a16f45b2..0db9d114 100644 --- a/modules/cloud-run/README.md +++ b/modules/cloud-run/README.md @@ -9,6 +9,7 @@ Cloud Run management, with support for IAM roles, revision annotations and optio - [IAM and environment variables](#iam-and-environment-variables) - [Mounting secrets as volumes](#mounting-secrets-as-volumes) - [Revision annotations](#revision-annotations) + - [Second generation execution environment](#second-generation-execution-environment) - [VPC Access Connector creation](#vpc-access-connector-creation) - [Traffic split](#traffic-split) - [Eventarc triggers](#eventarc-triggers) @@ -107,6 +108,25 @@ module "cloud_run" { # 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 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 ``` - ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L130) | Name used for cloud run service. | string | ✓ | | -| [project_id](variables.tf#L145) | Project id used for all resources. | string | ✓ | | +| [name](variables.tf#L136) | Name used for cloud run service. | string | ✓ | | +| [project_id](variables.tf#L151) | Project id used for all resources. | string | ✓ | | | [container_concurrency](variables.tf#L18) | Maximum allowed in-flight (concurrent) requests per container of the revision. | string | | null | | [containers](variables.tf#L24) | Containers in arbitrary key => attributes format. | map(object({…})) | | {} | | [eventarc_triggers](variables.tf#L91) | Event arc triggers for different sources. | object({…}) | | {} | -| [iam](variables.tf#L105) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [ingress_settings](variables.tf#L111) | Ingress settings. | string | | null | -| [labels](variables.tf#L124) | Resource labels. | map(string) | | {} | -| [prefix](variables.tf#L135) | Optional prefix used for resource names. | string | | null | -| [region](variables.tf#L150) | Region used for all resources. | string | | "europe-west1" | -| [revision_annotations](variables.tf#L156) | Configure revision template annotations. | object({…}) | | {} | -| [revision_name](variables.tf#L171) | Revision name. | string | | null | -| [service_account](variables.tf#L177) | Service account email. Unused if service account is auto-created. | string | | null | -| [service_account_create](variables.tf#L183) | Auto-create service account. | bool | | false | -| [timeout_seconds](variables.tf#L189) | Maximum duration the instance is allowed for responding to a request. | number | | null | -| [traffic](variables.tf#L195) | Traffic steering configuration. If revision name is null the latest revision will be used. | map(object({…})) | | {} | -| [volumes](variables.tf#L206) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | -| [vpc_connector_create](variables.tf#L220) | Populate this to create a VPC connector. You can then refer to it in the template annotations. | object({…}) | | null | +| [gen2_execution_environment](variables.tf#L105) | Use second generation execution environment. | bool | | false | +| [iam](variables.tf#L111) | IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [ingress_settings](variables.tf#L117) | Ingress settings. | string | | null | +| [labels](variables.tf#L130) | Resource labels. | map(string) | | {} | +| [prefix](variables.tf#L141) | Optional prefix used for resource names. | string | | null | +| [region](variables.tf#L156) | Region used for all resources. | string | | "europe-west1" | +| [revision_annotations](variables.tf#L162) | Configure revision template annotations. | object({…}) | | {} | +| [revision_name](variables.tf#L177) | Revision name. | string | | null | +| [service_account](variables.tf#L183) | Service account email. Unused if service account is auto-created. | string | | null | +| [service_account_create](variables.tf#L189) | Auto-create service account. | bool | | false | +| [timeout_seconds](variables.tf#L195) | Maximum duration the instance is allowed for responding to a request. | number | | null | +| [traffic](variables.tf#L201) | Traffic steering configuration. If revision name is null the latest revision will be used. | map(object({…})) | | {} | +| [volumes](variables.tf#L212) | Named volumes in containers in name => attributes format. | map(object({…})) | | {} | +| [vpc_connector_create](variables.tf#L226) | Populate this to create a VPC connector. You can then refer to it in the template annotations. | object({…}) | | null | ## Outputs @@ -348,5 +368,4 @@ module "cloud_run" { | [service_account_iam_email](outputs.tf#L38) | Service account email. | | | [service_name](outputs.tf#L46) | Cloud Run service name. | | | [vpc_connector](outputs.tf#L52) | VPC connector resource if created. | | - diff --git a/modules/cloud-run/main.tf b/modules/cloud-run/main.tf index 6b1177b3..e5371273 100644 --- a/modules/cloud-run/main.tf +++ b/modules/cloud-run/main.tf @@ -69,6 +69,9 @@ locals { var.revision_annotations.vpcaccess_egress ) }, + var.gen2_execution_environment ? { + "run.googleapis.com/execution-environment" = "gen2" + } : {}, ) revision_name = ( try(var.revision_name, null) == null diff --git a/modules/cloud-run/variables.tf b/modules/cloud-run/variables.tf index f176ae40..09d10296 100644 --- a/modules/cloud-run/variables.tf +++ b/modules/cloud-run/variables.tf @@ -102,6 +102,12 @@ variable "eventarc_triggers" { default = {} } +variable "gen2_execution_environment" { + description = "Use second generation execution environment." + type = bool + default = false +} + variable "iam" { description = "IAM bindings for Cloud Run service in {ROLE => [MEMBERS]} format." type = map(list(string)) diff --git a/modules/cloudsql-instance/README.md b/modules/cloudsql-instance/README.md index ecd26017..172a805e 100644 --- a/modules/cloudsql-instance/README.md +++ b/modules/cloudsql-instance/README.md @@ -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. | string | ✓ | | | [project_id](variables.tf#L151) | The ID of the project where this instances will be created. | string | ✓ | | | [region](variables.tf#L156) | Region of the primary instance. | string | ✓ | | -| [tier](variables.tf#L176) | The machine type to use for the instances. | string | ✓ | | +| [tier](variables.tf#L182) | The machine type to use for the instances. | string | ✓ | | | [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?. | object({…}) | | {} | | [authorized_networks](variables.tf#L26) | Map of NAME=>CIDR_RANGE to allow to connect to the database(s). | map(string) | | null | | [availability_type](variables.tf#L32) | Availability type for the primary replica. Either `ZONAL` or `REGIONAL`. | string | | "ZONAL" | @@ -210,8 +210,9 @@ module "db" { | [postgres_client_certificates](variables.tf#L135) | Map of cert keys connect to the application(s) using public IP. | list(string) | | null | | [prefix](variables.tf#L141) | Optional prefix used to generate instance names. | string | | null | | [replicas](variables.tf#L161) | Map of NAME=> {REGION, KMS_KEY} for additional read replicas. Set to null to disable replica creation. | map(object({…})) | | {} | -| [root_password](variables.tf#L170) | Root password of the Cloud SQL instance. Required for MS SQL Server. | string | | null | -| [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. | map(string) | | null | +| [require_ssl](variables.tf#L170) | Enable SSL connections only. | bool | | null | +| [root_password](variables.tf#L176) | Root password of the Cloud SQL instance. Required for MS SQL Server. | string | | null | +| [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. | map(string) | | null | ## Outputs diff --git a/modules/cloudsql-instance/main.tf b/modules/cloudsql-instance/main.tf index 67111a7b..5c40b212 100644 --- a/modules/cloudsql-instance/main.tf +++ b/modules/cloudsql-instance/main.tf @@ -64,6 +64,7 @@ resource "google_sql_database_instance" "primary" { ipv4_enabled = var.ipv4_enabled private_network = var.network allocated_ip_range = var.allocated_ip_ranges.primary + require_ssl = var.require_ssl dynamic "authorized_networks" { for_each = var.authorized_networks != null ? var.authorized_networks : {} iterator = network diff --git a/modules/cloudsql-instance/variables.tf b/modules/cloudsql-instance/variables.tf index 8b3e0fff..03e0d6df 100644 --- a/modules/cloudsql-instance/variables.tf +++ b/modules/cloudsql-instance/variables.tf @@ -167,6 +167,12 @@ variable "replicas" { default = {} } +variable "require_ssl" { + description = "Enable SSL connections only." + type = bool + default = null +} + variable "root_password" { description = "Root password of the Cloud SQL instance. Required for MS SQL Server." type = string diff --git a/modules/compute-mig/README.md b/modules/compute-mig/README.md index ef87d085..b281c2e3 100644 --- a/modules/compute-mig/README.md +++ b/modules/compute-mig/README.md @@ -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. + +- [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) + + ## 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. ```hcl @@ -49,7 +64,7 @@ module "nginx-mig" { # 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: @@ -100,7 +115,7 @@ module "nginx-mig" { # 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: @@ -205,7 +220,7 @@ module "nginx-mig" { # tftest modules=2 resources=3 inventory=autoscaling.yaml ``` -### Update policy +### Update Policy ```hcl 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. -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 module "cos-nginx" { @@ -270,16 +285,15 @@ module "cos-nginx" { } module "nginx-template" { - source = "./fabric/modules/compute-vm" - project_id = var.project_id - name = "nginx-template" - zone = "europe-west1-b" - tags = ["http-server", "ssh"] + source = "./fabric/modules/compute-vm" + project_id = "my-prj" + name = "nginx-template" + zone = "europe-west8-b" + tags = ["http-server", "ssh"] + instance_type = "e2-small" network_interfaces = [{ network = var.vpc.self_link subnetwork = var.subnet.self_link - nat = false - addresses = null }] boot_disk = { initialize_params = { @@ -287,15 +301,10 @@ module "nginx-template" { } } attached_disks = [{ - name = "repd-1" - size = null source_type = "attach" - source = "regions/${var.region}/disks/repd-test-1" - options = { - mode = "READ_ONLY" - replica_zone = "${var.region}-c" - type = "PERSISTENT" - } + name = "data-1" + size = 10 + source = "test-data-1" }] create_template = true metadata = { @@ -305,34 +314,21 @@ module "nginx-template" { module "nginx-mig" { source = "./fabric/modules/compute-mig" - project_id = "my-project" - location = "europe-west1-b" - name = "mig-test" - target_size = 3 + project_id = "my-prj" + location = "europe-west8-b" + name = "mig-test-2" + target_size = 1 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 = { - repd-1 = false + data-1 = false } } -# tftest modules=2 resources=3 - +# tftest modules=2 resources=2 ``` ### Stateful MIGs - Instance Config -Here is an example defining the stateful config at the instance level. - -Note that you will need to know the instance name in order to use this configuration. +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). ```hcl module "cos-nginx" { @@ -340,16 +336,15 @@ module "cos-nginx" { } module "nginx-template" { - source = "./fabric/modules/compute-vm" - project_id = var.project_id - name = "nginx-template" - zone = "europe-west1-b" - tags = ["http-server", "ssh"] + source = "./fabric/modules/compute-vm" + project_id = "my-prj" + name = "nginx-template" + zone = "europe-west8-b" + tags = ["http-server", "ssh"] + instance_type = "e2-small" network_interfaces = [{ network = var.vpc.self_link subnetwork = var.subnet.self_link - nat = false - addresses = null }] boot_disk = { initialize_params = { @@ -357,15 +352,10 @@ module "nginx-template" { } } attached_disks = [{ - name = "repd-1" - size = null source_type = "attach" - source = "regions/${var.region}/disks/repd-test-1" - options = { - mode = "READ_ONLY" - replica_zone = "${var.region}-c" - type = "PERSISTENT" - } + name = "data-1" + size = 10 + source = "test-data-1" }] create_template = true metadata = { @@ -375,30 +365,18 @@ module "nginx-template" { module "nginx-mig" { source = "./fabric/modules/compute-mig" - project_id = "my-project" - location = "europe-west1-b" + project_id = "my-prj" + location = "europe-west8-b" name = "mig-test" - target_size = 3 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 = { - # name needs to match a MIG instance name instance-1 = { minimal_action = "NONE", most_disruptive_allowed_action = "REPLACE" preserved_state = { disks = { - persistent-disk-1 = { - source = "test-disk", + data-1 = { + source = "projects/my-prj/zones/europe-west8-b/disks/test-data-1" } } metadata = { @@ -408,8 +386,7 @@ module "nginx-mig" { } } } -# tftest modules=2 resources=4 inventory=stateful.yaml - +# tftest modules=2 resources=3 inventory=stateful.yaml ``` diff --git a/modules/compute-mig/stateful-config.tf b/modules/compute-mig/stateful-config.tf index 1e9e056e..dc0329d1 100644 --- a/modules/compute-mig/stateful-config.tf +++ b/modules/compute-mig/stateful-config.tf @@ -22,7 +22,7 @@ resource "google_compute_per_instance_config" "default" { zone = var.location name = each.key 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 most_disruptive_allowed_action = each.value.most_disruptive_action @@ -59,7 +59,7 @@ resource "google_compute_region_per_instance_config" "default" { region = var.location name = each.key 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 most_disruptive_allowed_action = each.value.most_disruptive_action diff --git a/modules/compute-vm/README.md b/modules/compute-vm/README.md index e47d9530..92a5e3c3 100644 --- a/modules/compute-vm/README.md +++ b/modules/compute-vm/README.md @@ -626,7 +626,6 @@ module "instance" { # tftest modules=1 resources=5 inventory=snapshot-schedule-create.yaml ``` - ## Variables | name | description | type | required | default | @@ -636,7 +635,7 @@ module "instance" { | [project_id](variables.tf#L277) | Project id. | string | ✓ | | | [zone](variables.tf#L379) | Compute zone. | string | ✓ | | | [attached_disk_defaults](variables.tf#L17) | Defaults for attached disks options. | object({…}) | | {…} | -| [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. | list(object({…})) | | [] | +| [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. | list(object({…})) | | [] | | [boot_disk](variables.tf#L83) | Boot disk properties. | object({…}) | | {…} | | [can_ip_forward](variables.tf#L117) | Enable IP forwarding. | bool | | false | | [confidential_compute](variables.tf#L123) | Enable Confidential Compute for these instances. | bool | | false | @@ -678,7 +677,6 @@ module "instance" { | [service_account_iam_email](outputs.tf#L74) | Service account email. | | | [template](outputs.tf#L82) | Template resource. | | | [template_name](outputs.tf#L87) | Template name. | | - ## TODO diff --git a/modules/compute-vm/variables.tf b/modules/compute-vm/variables.tf index efdf7d97..bf33e517 100644 --- a/modules/compute-vm/variables.tf +++ b/modules/compute-vm/variables.tf @@ -28,7 +28,6 @@ variable "attached_disk_defaults" { replica_zone = null type = "pd-balanced" } - validation { 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." @@ -38,8 +37,9 @@ variable "attached_disk_defaults" { 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." type = list(object({ - name = string - device_name = optional(string) + name = string + device_name = optional(string) + # TODO: size can be null when source_type is attach size = string snapshot_schedule = optional(string) source = optional(string) diff --git a/modules/data-catalog-policy-tag/README.md b/modules/data-catalog-policy-tag/README.md index 99f2c08c..6f53fcbd 100644 --- a/modules/data-catalog-policy-tag/README.md +++ b/modules/data-catalog-policy-tag/README.md @@ -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. + +- [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) + + ## Examples ### Simple Taxonomy with policy tags @@ -43,25 +52,32 @@ module "cmn-dc" { iam = { "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 ``` ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L59) | Name of this taxonomy. | string | ✓ | | -| [project_id](variables.tf#L74) | GCP project id. | | ✓ | | +| [name](variables.tf#L69) | Name of this taxonomy. | string | ✓ | | +| [project_id](variables.tf#L84) | GCP project id. | | ✓ | | | [activated_policy_types](variables.tf#L17) | A list of policy types that are activated for this taxonomy. | list(string) | | ["FINE_GRAINED_ACCESS_CONTROL"] | | [description](variables.tf#L23) | Description of this taxonomy. | string | | "Taxonomy - Terraform managed" | | [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. | map(list(string)) | | {} | | [iam](variables.tf#L35) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L41) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L47) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [location](variables.tf#L53) | Data Catalog Taxonomy location. | string | | "eu" | -| [prefix](variables.tf#L64) | Optional prefix used to generate project id and name. | string | | null | -| [tags](variables.tf#L78) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(object({…})) | | {} | +| [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. | map(object({…})) | | {} | +| [location](variables.tf#L63) | Data Catalog Taxonomy location. | string | | "eu" | +| [prefix](variables.tf#L74) | Optional prefix used to generate project id and name. | string | | null | +| [tags](variables.tf#L88) | List of Data Catalog Policy tags to be created with optional IAM binging configuration in {tag => {ROLE => [MEMBERS]}} format. | map(object({…})) | | {} | ## Outputs diff --git a/modules/data-catalog-policy-tag/iam.tf b/modules/data-catalog-policy-tag/iam.tf index d682e6e8..6f79aebf 100644 --- a/modules/data-catalog-policy-tag/iam.tf +++ b/modules/data-catalog-policy-tag/iam.tf @@ -58,9 +58,9 @@ locals { resource "google_data_catalog_taxonomy_iam_binding" "authoritative" { provider = google-beta for_each = local.iam + taxonomy = google_data_catalog_taxonomy.default.id role = each.key members = each.value - taxonomy = google_data_catalog_taxonomy.default.id } resource "google_data_catalog_taxonomy_iam_member" "additive" { @@ -70,9 +70,16 @@ resource "google_data_catalog_taxonomy_iam_member" "additive" { ? local.iam_additive : {} ) + taxonomy = google_data_catalog_taxonomy.default.id role = each.value.role member = each.value.member +} + +resource "google_data_catalog_taxonomy_iam_member" "members" { + for_each = var.iam_members taxonomy = google_data_catalog_taxonomy.default.id + role = each.value.role + member = each.value.member } 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 v in local.tags_iam : "${v.tag}.${v.role}" => v } - policy_tag = google_data_catalog_policy_tag.default[each.value.tag].name role = each.value.role members = each.value.members diff --git a/modules/data-catalog-policy-tag/variables.tf b/modules/data-catalog-policy-tag/variables.tf index 70ba24bf..c7c8eb07 100644 --- a/modules/data-catalog-policy-tag/variables.tf +++ b/modules/data-catalog-policy-tag/variables.tf @@ -50,6 +50,16 @@ variable "iam_additive_members" { 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" { description = "Data Catalog Taxonomy location." type = string diff --git a/modules/dataplex-datascan/README.md b/modules/dataplex-datascan/README.md index 96027a53..65436af6 100644 --- a/modules/dataplex-datascan/README.md +++ b/modules/dataplex-datascan/README.md @@ -2,9 +2,20 @@ This module manages the creation of Dataplex DataScan resources. + +- [Data Profiling](#data-profiling) +- [Data Quality](#data-quality) +- [Data Source](#data-source) +- [Execution Schedule](#execution-schedule) +- [IAM](#iam) +- [TODO](#todo) +- [Variables](#variables) +- [Outputs](#outputs) + + ## 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 . ```hcl module "dataplex-datascan" { @@ -30,9 +41,9 @@ module "dataplex-datascan" { ## 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 . -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 . 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' 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}`. * `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 -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 -- 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 +* `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`, `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 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 module "dataplex-datascan" { @@ -404,8 +416,14 @@ module "dataplex-datascan" { "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 @@ -415,9 +433,9 @@ module "dataplex-datascan" { | 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`. | object({…}) | ✓ | | -| [name](variables.tf#L146) | Name of Dataplex Scan. | string | ✓ | | -| [project_id](variables.tf#L157) | The ID of the project where the Dataplex DataScan will be created. | string | ✓ | | -| [region](variables.tf#L162) | Region for the Dataplex DataScan. | string | ✓ | | +| [name](variables.tf#L156) | Name of Dataplex Scan. | string | ✓ | | +| [project_id](variables.tf#L167) | The ID of the project where the Dataplex DataScan will be created. | string | ✓ | | +| [region](variables.tf#L172) | Region for the Dataplex DataScan. | string | ✓ | | | [data_profile_spec](variables.tf#L29) | DataProfileScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataProfileSpec. | object({…}) | | null | | [data_quality_spec](variables.tf#L38) | DataQualityScan related setting. Variable descriptions are provided in https://cloud.google.com/dataplex/docs/reference/rest/v1/DataQualitySpec. | object({…}) | | null | | [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. | object({…}) | | null | @@ -427,10 +445,11 @@ module "dataplex-datascan" { | [iam](variables.tf#L107) | Dataplex DataScan IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L114) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L121) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [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. | map(list(string)) | | null | -| [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. | string | | null | -| [labels](variables.tf#L139) | Resource labels. | map(string) | | {} | -| [prefix](variables.tf#L151) | Optional prefix used to generate Dataplex DataScan ID. | string | | null | +| [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. | map(object({…})) | | {} | +| [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. | map(list(string)) | | null | +| [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. | string | | null | +| [labels](variables.tf#L149) | Resource labels. | map(string) | | {} | +| [prefix](variables.tf#L161) | Optional prefix used to generate Dataplex DataScan ID. | string | | null | ## Outputs diff --git a/modules/dataplex-datascan/iam.tf b/modules/dataplex-datascan/iam.tf index 62eb2fd9..e1a7c057 100644 --- a/modules/dataplex-datascan/iam.tf +++ b/modules/dataplex-datascan/iam.tf @@ -69,6 +69,15 @@ resource "google_dataplex_datascan_iam_member" "additive" { 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" { count = var.iam_policy != null ? 1 : 0 project = google_dataplex_datascan.datascan.project diff --git a/modules/dataplex-datascan/variables.tf b/modules/dataplex-datascan/variables.tf index 9d167840..47ca7332 100644 --- a/modules/dataplex-datascan/variables.tf +++ b/modules/dataplex-datascan/variables.tf @@ -124,6 +124,16 @@ variable "iam_additive_members" { 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" { 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)) diff --git a/modules/dataproc/README.md b/modules/dataproc/README.md index f848db57..3e454e50 100644 --- a/modules/dataproc/README.md +++ b/modules/dataproc/README.md @@ -2,6 +2,19 @@ This module Manages a Google Cloud [Dataproc](https://cloud.google.com/dataproc) cluster resource, including IAM. + +- [TODO](#todo) +- [Examples](#examples) + - [Simple](#simple) + - [Cluster configuration](#cluster-configuration) + - [Cluster with CMEK encryption](#cluster-with-cmek-encryption) +- [IAM Examples](#iam-examples) + - [Authoritative IAM](#authoritative-iam) + - [Additive IAM](#additive-iam) +- [Variables](#variables) +- [Outputs](#outputs) + + ## TODO - [ ] Add support for Cloud Dataproc [autoscaling policy](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/dataproc_autoscaling_policy_iam). @@ -79,8 +92,10 @@ module "processing-dp-cluster" { 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_dataproc_cluster_iam_binding` resource -- `iam_additive` configure additive bindings that only manage individual role/member pairs, mapping to the `google_dataproc_cluster_iam_member` resource +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 +- `iam_additive` and `iam_members` configures 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 IAM @@ -136,25 +151,31 @@ module "processing-dp-cluster" { "serviceAccount:service-account@PROJECT_ID.iam.gserviceaccount.com" ] } + iam_members = { + am1-viewer = { + member = "user:am1@example.com" + role = "roles/dataproc.viewer" + } + } } -# tftest modules=1 resources=2 +# tftest modules=1 resources=3 ``` - ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L212) | Cluster name. | string | ✓ | | -| [project_id](variables.tf#L227) | Project ID. | string | ✓ | | -| [region](variables.tf#L232) | Dataproc region. | string | ✓ | | +| [name](variables.tf#L222) | Cluster name. | string | ✓ | | +| [project_id](variables.tf#L237) | Project ID. | string | ✓ | | +| [region](variables.tf#L242) | Dataproc region. | string | ✓ | | | [dataproc_config](variables.tf#L17) | Dataproc cluster config. | object({…}) | | {} | | [group_iam](variables.tf#L185) | 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. | map(list(string)) | | {} | | [iam](variables.tf#L192) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L199) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [labels](variables.tf#L206) | The resource labels for instance to use to annotate any related underlying resources, such as Compute Engine VMs. | map(string) | | {} | -| [prefix](variables.tf#L217) | Optional prefix used to generate project id and name. | string | | null | -| [service_account](variables.tf#L237) | Service account to set on the Dataproc cluster. | string | | null | +| [iam_members](variables.tf#L206) | 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. | map(object({…})) | | {} | +| [labels](variables.tf#L216) | The resource labels for instance to use to annotate any related underlying resources, such as Compute Engine VMs. | map(string) | | {} | +| [prefix](variables.tf#L227) | Optional prefix used to generate project id and name. | string | | null | +| [service_account](variables.tf#L247) | Service account to set on the Dataproc cluster. | string | | null | ## Outputs @@ -165,5 +186,4 @@ module "processing-dp-cluster" { | [id](outputs.tf#L29) | Fully qualified cluster id. | | | [instance_names](outputs.tf#L34) | List of instance names which have been assigned to the cluster. | | | [name](outputs.tf#L43) | The name of the cluster. | | - diff --git a/modules/dataproc/iam.tf b/modules/dataproc/iam.tf index e44c2a63..84d49f35 100644 --- a/modules/dataproc/iam.tf +++ b/modules/dataproc/iam.tf @@ -65,3 +65,11 @@ resource "google_dataproc_cluster_iam_member" "additive" { role = each.value.role 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 +} diff --git a/modules/dataproc/variables.tf b/modules/dataproc/variables.tf index 926169b9..b28b9c27 100644 --- a/modules/dataproc/variables.tf +++ b/modules/dataproc/variables.tf @@ -203,6 +203,16 @@ variable "iam_additive" { 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" { description = "The resource labels for instance to use to annotate any related underlying resources, such as Compute Engine VMs." type = map(string) diff --git a/modules/folder/README.md b/modules/folder/README.md index 04e5b8d2..b46c5e8b 100644 --- a/modules/folder/README.md +++ b/modules/folder/README.md @@ -2,15 +2,12 @@ This module allows the creation and management of folders, including support for IAM bindings, organization policies, and hierarchical firewall rules. - - [Basic example with IAM bindings](#basic-example-with-iam-bindings) - [IAM](#iam) - [Organization policies](#organization-policies) - [Organization Policy Factory](#organization-policy-factory) -- [Hierarchical Firewall Policies](#hierarchical-firewall-policies) - - [Directly Defined Firewall Policies](#directly-defined-firewall-policies) - - [Firewall Policy Factory](#firewall-policy-factory) +- [Hierarchical Firewall Policy Attachments](#hierarchical-firewall-policy-attachments) - [Log Sinks](#log-sinks) - [Data Access Logs](#data-access-logs) - [Tags](#tags) @@ -44,15 +41,21 @@ module "folder" { "user:am1@example.org" = ["roles/storage.admin"] "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 -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 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). -## Hierarchical Firewall Policies +## Hierarchical Firewall Policy Attachments -Hierarchical firewall policies can be managed in two ways: - -- 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 +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: ```hcl -module "folder1" { - source = "./fabric/modules/folder" - parent = var.organization_id - name = "policy-container" - - firewall_policies = { - iap-policy = { - 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 "firewall-policy" { + source = "./fabric/modules/net-firewall-policy" + name = "test-1" + parent_id = module.folder.id + # attachment via the firewall policy module + # attachments = { + # folder-1 = module.folder.id + # } } -module "folder2" { +module "folder" { source = "./fabric/modules/folder" - parent = var.organization_id - name = "hf2" - firewall_policy_association = { - iap-policy = module.folder1.firewall_policy_id["iap-policy"] + parent = "organizations/1234567890" + name = "Folder name" + # attachment via the organization module + firewall_policy_associations = { + test-1 = module.firewall-policy.id } } -# tftest modules=2 resources=7 inventory=hfw.yaml -``` - -### 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 +# tftest modules=2 resources=3 ``` ## Log Sinks @@ -395,15 +301,13 @@ module "folder" { - ## Files | name | description | resources | |---|---|---| -| [firewall-policies.tf](./firewall-policies.tf) | None | google_compute_firewall_policy · google_compute_firewall_policy_association · google_compute_firewall_policy_rule | | [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | google_folder_iam_binding · google_folder_iam_member · google_folder_iam_policy | | [logging.tf](./logging.tf) | Log sinks and supporting resources. | google_bigquery_dataset_iam_member · google_folder_iam_audit_config · google_logging_folder_exclusion · google_logging_folder_sink · google_project_iam_member · google_pubsub_topic_iam_member · google_storage_bucket_iam_member | -| [main.tf](./main.tf) | Module-level locals and resources. | google_essential_contacts_contact · google_folder | +| [main.tf](./main.tf) | Module-level locals and resources. | google_compute_firewall_policy_association · google_essential_contacts_contact · google_folder | | [organization-policies.tf](./organization-policies.tf) | Folder-level organization policies. | google_org_policy_policy | | [outputs.tf](./outputs.tf) | Module outputs. | | | [tags.tf](./tags.tf) | None | google_tags_tag_binding | @@ -415,34 +319,30 @@ module "folder" { | 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. | map(list(string)) | | {} | -| [firewall_policies](variables.tf#L24) | Hierarchical firewall policies created in this folder. | map(map(object({…}))) | | {} | -| [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. | map(string) | | {} | -| [firewall_policy_factory](variables.tf#L48) | Configuration for the firewall policy factory. | object({…}) | | null | -| [folder_create](variables.tf#L58) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true | -| [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. | map(list(string)) | | {} | -| [iam](variables.tf#L71) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_additive](variables.tf#L78) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_additive_members](variables.tf#L85) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [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. | map(list(string)) | | null | -| [id](variables.tf#L98) | Folder ID in case you use folder_create=false. | string | | null | -| [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. | map(map(list(string))) | | {} | -| [logging_exclusions](variables.tf#L119) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} | -| [logging_sinks](variables.tf#L126) | Logging sinks to create for the organization. | map(object({…})) | | {} | -| [name](variables.tf#L156) | Folder name. | string | | null | -| [org_policies](variables.tf#L162) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | -| [org_policies_data_path](variables.tf#L189) | Path containing org policies in YAML format. | string | | null | -| [parent](variables.tf#L195) | Parent in folders/folder_id or organizations/org_id format. | string | | null | -| [tag_bindings](variables.tf#L205) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | +| [firewall_policy_associations](variables.tf#L24) | Hierarchical firewall policies to associate to this folder, in association name => policy id format. | map(string) | | {} | +| [folder_create](variables.tf#L31) | Create folder. When set to false, uses id to reference an existing folder. | bool | | true | +| [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. | map(list(string)) | | {} | +| [iam](variables.tf#L44) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_additive](variables.tf#L51) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_additive_members](variables.tf#L58) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | +| [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. | map(object({…})) | | {} | +| [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. | map(list(string)) | | null | +| [id](variables.tf#L81) | Folder ID in case you use folder_create=false. | string | | null | +| [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. | map(map(list(string))) | | {} | +| [logging_exclusions](variables.tf#L102) | Logging exclusions for this folder in the form {NAME -> FILTER}. | map(string) | | {} | +| [logging_sinks](variables.tf#L109) | Logging sinks to create for the organization. | map(object({…})) | | {} | +| [name](variables.tf#L139) | Folder name. | string | | null | +| [org_policies](variables.tf#L145) | Organization policies applied to this folder keyed by policy name. | map(object({…})) | | {} | +| [org_policies_data_path](variables.tf#L172) | Path containing org policies in YAML format. | string | | null | +| [parent](variables.tf#L178) | Parent in folders/folder_id or organizations/org_id format. | string | | null | +| [tag_bindings](variables.tf#L188) | Tag bindings for this folder, in key => tag value id format. | map(string) | | null | ## Outputs | name | description | sensitive | |---|---|:---:| -| [firewall_policies](outputs.tf#L16) | Map of firewall policy resources created in this folder. | | -| [firewall_policy_id](outputs.tf#L21) | Map of firewall policy ids created in this folder. | | -| [folder](outputs.tf#L26) | Folder resource. | | -| [id](outputs.tf#L31) | Fully qualified folder id. | | -| [name](outputs.tf#L41) | Folder name. | | -| [sink_writer_identities](outputs.tf#L46) | Writer identities created for each sink. | | - +| [folder](outputs.tf#L17) | Folder resource. | | +| [id](outputs.tf#L22) | Fully qualified folder id. | | +| [name](outputs.tf#L32) | Folder name. | | +| [sink_writer_identities](outputs.tf#L37) | Writer identities created for each sink. | | diff --git a/modules/folder/firewall-policies.tf b/modules/folder/firewall-policies.tf deleted file mode 100644 index 96224c56..00000000 --- a/modules/folder/firewall-policies.tf +++ /dev/null @@ -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) -} - diff --git a/modules/folder/iam.tf b/modules/folder/iam.tf index 5afae6f4..6b8fc1b1 100644 --- a/modules/folder/iam.tf +++ b/modules/folder/iam.tf @@ -64,6 +64,13 @@ resource "google_folder_iam_member" "additive" { 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" { count = var.iam_policy != null ? 1 : 0 folder = local.folder.name diff --git a/modules/folder/main.tf b/modules/folder/main.tf index 5d285d2d..e2a77321 100644 --- a/modules/folder/main.tf +++ b/modules/folder/main.tf @@ -41,3 +41,10 @@ resource "google_essential_contacts_contact" "contact" { language_tag = "en" 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 +} diff --git a/modules/folder/outputs.tf b/modules/folder/outputs.tf index bf96c4c8..2a791d74 100644 --- a/modules/folder/outputs.tf +++ b/modules/folder/outputs.tf @@ -13,15 +13,6 @@ * See the License for the specific language governing permissions and * 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" { description = "Folder resource." diff --git a/modules/folder/variables.tf b/modules/folder/variables.tf index 284f0fd7..be7aad32 100644 --- a/modules/folder/variables.tf +++ b/modules/folder/variables.tf @@ -21,40 +21,13 @@ variable "contacts" { nullable = false } -variable "firewall_policies" { - description = "Hierarchical firewall policies created in this folder." - 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." +variable "firewall_policy_associations" { + description = "Hierarchical firewall policies to associate to this folder, in association name => policy id format." type = map(string) default = {} 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" { description = "Create folder. When set to false, uses id to reference an existing folder." type = bool @@ -89,6 +62,16 @@ variable "iam_additive_members" { 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" { 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)) diff --git a/modules/iam-service-account/README.md b/modules/iam-service-account/README.md index c1303386..061b651e 100644 --- a/modules/iam-service-account/README.md +++ b/modules/iam-service-account/README.md @@ -27,7 +27,6 @@ module "myproject-default-service-accounts" { ``` - ## Files | name | description | resources | @@ -42,8 +41,8 @@ module "myproject-default-service-accounts" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L91) | Name of the service account to create. | string | ✓ | | -| [project_id](variables.tf#L106) | Project id where service account will be created. | string | ✓ | | +| [name](variables.tf#L101) | Name of the service account to create. | string | ✓ | | +| [project_id](variables.tf#L116) | Project id where service account will be created. | string | ✓ | | | [description](variables.tf#L17) | Optional description. | string | | null | | [display_name](variables.tf#L23) | Display name of the service account to create. | string | | "Terraform-managed." | | [generate_key](variables.tf#L29) | Generate a key for service account. | bool | | false | @@ -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. | map(list(string)) | | {} | | [iam_billing_roles](variables.tf#L49) | Billing account roles granted to this service account, by billing account id. Non-authoritative. | map(list(string)) | | {} | | [iam_folder_roles](variables.tf#L56) | Folder roles granted to this service account, by folder id. Non-authoritative. | map(list(string)) | | {} | -| [iam_organization_roles](variables.tf#L63) | Organization roles granted to this service account, by organization id. Non-authoritative. | map(list(string)) | | {} | -| [iam_project_roles](variables.tf#L70) | Project roles granted to this service account, by project id. | map(list(string)) | | {} | -| [iam_sa_roles](variables.tf#L77) | Service account roles granted to this service account, by service account name. | map(list(string)) | | {} | -| [iam_storage_roles](variables.tf#L84) | Storage roles granted to this service account, by bucket name. | map(list(string)) | | {} | -| [prefix](variables.tf#L96) | Prefix applied to service account names. | string | | null | -| [public_keys_directory](variables.tf#L111) | Path to public keys data files to upload to the service account (should have `.pem` extension). | string | | "" | -| [service_account_create](variables.tf#L117) | Create service account. When set to false, uses a data source to reference an existing service account. | bool | | true | +| [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. | map(object({…})) | | {} | +| [iam_organization_roles](variables.tf#L73) | Organization roles granted to this service account, by organization id. Non-authoritative. | map(list(string)) | | {} | +| [iam_project_roles](variables.tf#L80) | Project roles granted to this service account, by project id. | map(list(string)) | | {} | +| [iam_sa_roles](variables.tf#L87) | Service account roles granted to this service account, by service account name. | map(list(string)) | | {} | +| [iam_storage_roles](variables.tf#L94) | Storage roles granted to this service account, by bucket name. | map(list(string)) | | {} | +| [prefix](variables.tf#L106) | Prefix applied to service account names. | string | | null | +| [public_keys_directory](variables.tf#L121) | Path to public keys data files to upload to the service account (should have `.pem` extension). | string | | "" | +| [service_account_create](variables.tf#L127) | Create service account. When set to false, uses a data source to reference an existing service account. | bool | | true | ## Outputs @@ -70,5 +70,4 @@ module "myproject-default-service-accounts" { | [name](outputs.tf#L48) | Service account name. | | | [service_account](outputs.tf#L57) | Service account resource. | | | [service_account_credentials](outputs.tf#L62) | Service account json credential templates for uploaded public keys data. | | - diff --git a/modules/iam-service-account/iam.tf b/modules/iam-service-account/iam.tf index 02c879d9..ae388c8e 100644 --- a/modules/iam-service-account/iam.tf +++ b/modules/iam-service-account/iam.tf @@ -134,6 +134,13 @@ resource "google_service_account_iam_member" "additive" { 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" { for_each = { for pair in local.iam_storage_pairs : diff --git a/modules/iam-service-account/variables.tf b/modules/iam-service-account/variables.tf index a9f60bf2..3594fbbc 100644 --- a/modules/iam-service-account/variables.tf +++ b/modules/iam-service-account/variables.tf @@ -60,6 +60,16 @@ variable "iam_folder_roles" { 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" { description = "Organization roles granted to this service account, by organization id. Non-authoritative." type = map(list(string)) diff --git a/modules/kms/README.md b/modules/kms/README.md index 446325d8..7b8eb487 100644 --- a/modules/kms/README.md +++ b/modules/kms/README.md @@ -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. + +- [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) + + ## 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`. @@ -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" } keys = { key-a = null @@ -56,7 +73,7 @@ module "kms" { key-c = { rotation_period = null, labels = { env = "test" } } } } -# tftest modules=1 resources=9 inventory=basic.yaml +# tftest modules=1 resources=10 ``` ### Crypto key purpose @@ -80,22 +97,23 @@ module "kms" { # tftest modules=1 resources=4 ``` - ## Variables | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [keyring](variables.tf#L70) | Keyring attributes. | object({…}) | ✓ | | -| [project_id](variables.tf#L93) | Project id where the keyring will be created. | string | ✓ | | +| [keyring](variables.tf#L91) | Keyring attributes. | object({…}) | ✓ | | +| [project_id](variables.tf#L114) | Project id where the keyring will be created. | string | ✓ | | | [iam](variables.tf#L17) | Keyring IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L23) | Keyring IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [key_iam](variables.tf#L29) | Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | -| [key_iam_additive](variables.tf#L35) | Key IAM additive bindings in {ROLE => [MEMBERS]} format. | map(map(list(string))) | | {} | -| [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. | map(object({…})) | | {} | -| [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. | object({…}) | | {…} | -| [keyring_create](variables.tf#L78) | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | -| [keys](variables.tf#L84) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | -| [tag_bindings](variables.tf#L98) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | null | +| [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. | map(object({…})) | | {} | +| [key_iam](variables.tf#L39) | Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | +| [key_iam_additive](variables.tf#L45) | Key IAM additive bindings in {KEY => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | +| [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. | map(object({…})) | | {} | +| [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. | map(object({…})) | | {} | +| [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. | object({…}) | | {…} | +| [keyring_create](variables.tf#L99) | Set to false to manage keys and IAM bindings in an existing keyring. | bool | | true | +| [keys](variables.tf#L105) | Key names and base attributes. Set attributes to null if not needed. | map(object({…})) | | {} | +| [tag_bindings](variables.tf#L119) | Tag bindings for this keyring, in key => tag value id format. | map(string) | | null | ## Outputs @@ -107,5 +125,4 @@ module "kms" { | [keys](outputs.tf#L44) | Key resources. | | | [location](outputs.tf#L52) | Keyring location. | | | [name](outputs.tf#L60) | Keyring name. | | - diff --git a/modules/kms/iam.tf b/modules/kms/iam.tf new file mode 100644 index 00000000..3da20d08 --- /dev/null +++ b/modules/kms/iam.tf @@ -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 +} diff --git a/modules/kms/main.tf b/modules/kms/main.tf index 88ac158e..26624f15 100644 --- a/modules/kms/main.tf +++ b/modules/kms/main.tf @@ -15,34 +15,6 @@ */ 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 = { for key, attrs in var.keys : key => try( var.key_purpose[key], var.key_purpose_defaults @@ -69,23 +41,6 @@ resource "google_kms_key_ring" "default" { 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" { for_each = var.keys 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 -} diff --git a/modules/kms/variables.tf b/modules/kms/variables.tf index f5f0fb1b..01e96c04 100644 --- a/modules/kms/variables.tf +++ b/modules/kms/variables.tf @@ -26,6 +26,16 @@ variable "iam_additive" { 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" { description = "Key IAM bindings in {KEY => {ROLE => [MEMBERS]}} format." type = map(map(list(string))) @@ -33,11 +43,22 @@ variable "key_iam" { } 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))) 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" { 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({ diff --git a/modules/net-vpc-firewall-policy/README.md b/modules/net-firewall-policy/README.md similarity index 94% rename from modules/net-vpc-firewall-policy/README.md rename to modules/net-firewall-policy/README.md index dd2b2064..ffdd15c2 100644 --- a/modules/net-vpc-firewall-policy/README.md +++ b/modules/net-firewall-policy/README.md @@ -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. + +- [Examples](#examples) + - [Hierarchical Policy](#hierarchical-policy) + - [Global Network policy](#global-network-policy) + - [Regional Network policy](#regional-network-policy) + - [Factory](#factory) +- [Variables](#variables) +- [Outputs](#outputs) + + ## Examples ### Hierarchical Policy ```hcl module "firewall-policy" { - source = "./fabric/modules/net-vpc-firewall-policy" + source = "./fabric/modules/net-firewall-policy" name = "test-1" parent_id = "folders/1234567890" attachments = { @@ -67,9 +77,10 @@ module "vpc" { } module "firewall-policy" { - source = "./fabric/modules/net-vpc-firewall-policy" + source = "./fabric/modules/net-firewall-policy" name = "test-1" parent_id = "my-project" + region = "global" attachments = { my-vpc = module.vpc.self_link } @@ -119,7 +130,7 @@ module "vpc" { } module "firewall-policy" { - source = "./fabric/modules/net-vpc-firewall-policy" + source = "./fabric/modules/net-firewall-policy" name = "test-1" parent_id = "my-project" region = "europe-west8" @@ -164,7 +175,7 @@ This is an example of a simple factory: ```hcl module "firewall-policy" { - source = "./fabric/modules/net-vpc-firewall-policy" + source = "./fabric/modules/net-firewall-policy" name = "test-1" parent_id = "folders/1234567890" attachments = { @@ -219,7 +230,6 @@ icmp: layer4_configs: - protocol: icmp ``` - ## Variables @@ -231,7 +241,7 @@ icmp: | [description](variables.tf#L24) | Policy description. | string | | null | | [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. | map(object({…})) | | {} | | [ingress_rules](variables.tf#L71) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next'. | map(object({…})) | | {} | -| [region](variables.tf#L125) | Policy region. Leave null for hierarchical policy, or global network policy. | string | | null | +| [region](variables.tf#L125) | Policy region. Leave null for hierarchical policy, set to 'global' for a global network policy. | string | | null | | [rules_factory_config](variables.tf#L131) | Configuration for the optional rules factory. | object({…}) | | {} | ## Outputs diff --git a/modules/net-vpc-firewall-policy/factory.tf b/modules/net-firewall-policy/factory.tf similarity index 86% rename from modules/net-vpc-firewall-policy/factory.tf rename to modules/net-firewall-policy/factory.tf index a0e655c7..4a9c8558 100644 --- a/modules/net-vpc-firewall-policy/factory.tf +++ b/modules/net-firewall-policy/factory.tf @@ -15,23 +15,17 @@ */ locals { - _factory_egress_rules = ( - var.rules_factory_config.egress_rules_file_path == null - ? {} - : yamldecode(file(var.rules_factory_config.egress_rules_file_path)) + _factory_egress_rules = try( + yamldecode(file(var.rules_factory_config.egress_rules_file_path)), {} ) - _factory_ingress_rules = ( - var.rules_factory_config.ingress_rules_file_path == null - ? {} - : yamldecode(file(var.rules_factory_config.ingress_rules_file_path)) + _factory_ingress_rules = try( + yamldecode(file(var.rules_factory_config.ingress_rules_file_path)), {} ) - factory_cidrs = ( - var.rules_factory_config.cidr_file_path == null - ? {} - : yamldecode(file(var.rules_factory_config.cidr_file_path)) + factory_cidrs = try( + yamldecode(file(var.rules_factory_config.cidr_file_path)), {} ) 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" direction = "EGRESS" priority = v.priority @@ -74,7 +68,7 @@ locals { } } 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" direction = "INGRESS" priority = v.priority diff --git a/modules/net-vpc-firewall-policy/hierarchical.tf b/modules/net-firewall-policy/hierarchical.tf similarity index 100% rename from modules/net-vpc-firewall-policy/hierarchical.tf rename to modules/net-firewall-policy/hierarchical.tf diff --git a/modules/net-vpc-firewall-policy/main.tf b/modules/net-firewall-policy/main.tf similarity index 84% rename from modules/net-vpc-firewall-policy/main.tf rename to modules/net-firewall-policy/main.tf index f253e221..093c8bf2 100644 --- a/modules/net-vpc-firewall-policy/main.tf +++ b/modules/net-firewall-policy/main.tf @@ -27,6 +27,7 @@ locals { local.factory_egress_rules, local.factory_ingress_rules, local._rules_egress, local._rules_ingress ) - use_hierarchical = strcontains(var.parent_id, "/") ? true : false - use_regional = !local.use_hierarchical && var.region != null + # do not depend on the parent id as that might be dynamic and prevent count + use_hierarchical = var.region == null + use_regional = !local.use_hierarchical && var.region != "global" } diff --git a/modules/net-vpc-firewall-policy/net-global.tf b/modules/net-firewall-policy/net-global.tf similarity index 100% rename from modules/net-vpc-firewall-policy/net-global.tf rename to modules/net-firewall-policy/net-global.tf diff --git a/modules/net-vpc-firewall-policy/net-regional.tf b/modules/net-firewall-policy/net-regional.tf similarity index 100% rename from modules/net-vpc-firewall-policy/net-regional.tf rename to modules/net-firewall-policy/net-regional.tf diff --git a/modules/net-vpc-firewall-policy/outputs.tf b/modules/net-firewall-policy/outputs.tf similarity index 71% rename from modules/net-vpc-firewall-policy/outputs.tf rename to modules/net-firewall-policy/outputs.tf index 44c0a7a6..0f16d2a6 100644 --- a/modules/net-vpc-firewall-policy/outputs.tf +++ b/modules/net-firewall-policy/outputs.tf @@ -16,9 +16,13 @@ output "id" { description = "Fully qualified firewall policy id." - value = coalesce([ - try(google_compute_firewall_policy.hierarchical.0.id, null), - try(google_compute_network_firewall_policy.net-global.0.id, null), - try(google_compute_region_network_firewall_policy.net-regional.0.id, null) - ]) + value = ( + local.use_hierarchical + ? google_compute_firewall_policy.hierarchical.0.id + : ( + local.use_regional + ? google_compute_region_network_firewall_policy.net-regional.0.id + : google_compute_network_firewall_policy.net-global.0.id + ) + ) } diff --git a/modules/net-vpc-firewall-policy/variables.tf b/modules/net-firewall-policy/variables.tf similarity index 98% rename from modules/net-vpc-firewall-policy/variables.tf rename to modules/net-firewall-policy/variables.tf index a1694d50..b7d48d96 100644 --- a/modules/net-vpc-firewall-policy/variables.tf +++ b/modules/net-firewall-policy/variables.tf @@ -123,7 +123,7 @@ variable "parent_id" { } 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 default = null } diff --git a/modules/net-vpc-firewall-policy/versions.tf b/modules/net-firewall-policy/versions.tf similarity index 100% rename from modules/net-vpc-firewall-policy/versions.tf rename to modules/net-firewall-policy/versions.tf diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md index 4b8c4f8d..8106e1d9 100644 --- a/modules/net-vpc/README.md +++ b/modules/net-vpc/README.md @@ -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 @@ -534,10 +541,11 @@ module "vpc" { | [shared_vpc_service_projects](variables.tf#L161) | Shared VPC service projects to register with this host. | list(string) | | [] | | [subnet_iam](variables.tf#L167) | Subnet IAM bindings in {REGION/NAME => {ROLE => [MEMBERS]} format. | map(map(list(string))) | | {} | | [subnet_iam_additive](variables.tf#L173) | Subnet IAM additive bindings in {REGION/NAME => {ROLE => [MEMBERS]}} format. | map(map(list(string))) | | {} | -| [subnets](variables.tf#L180) | Subnet configuration. | list(object({…})) | | [] | -| [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. | list(object({…})) | | [] | -| [subnets_psc](variables.tf#L218) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | -| [vpc_create](variables.tf#L229) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | +| [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. | map(object({…})) | | {} | +| [subnets](variables.tf#L191) | Subnet configuration. | list(object({…})) | | [] | +| [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. | list(object({…})) | | [] | +| [subnets_psc](variables.tf#L229) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] | +| [vpc_create](variables.tf#L240) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true | ## Outputs diff --git a/modules/net-vpc/subnets.tf b/modules/net-vpc/subnets.tf index 262c22da..16f6398c 100644 --- a/modules/net-vpc/subnets.tf +++ b/modules/net-vpc/subnets.tf @@ -189,3 +189,12 @@ resource "google_compute_subnetwork_iam_member" "binding" { role = each.value.role 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 +} diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf index 733692d8..4e114dfe 100644 --- a/modules/net-vpc/variables.tf +++ b/modules/net-vpc/variables.tf @@ -177,6 +177,17 @@ variable "subnet_iam_additive" { 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" { description = "Subnet configuration." type = list(object({ diff --git a/modules/organization/README.md b/modules/organization/README.md index cf1e64f8..d232dfd4 100644 --- a/modules/organization/README.md +++ b/modules/organization/README.md @@ -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. ## TOC + - [TOC](#toc) - [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 Custom Constraints](#organization-policy-custom-constraints) - [Organization Policy Custom Constraints Factory](#organization-policy-custom-constraints-factory) -- [Hierarchical Firewall Policies](#hierarchical-firewall-policies) - - [Directly Defined Firewall Policies](#directly-defined-firewall-policies) - - [Firewall Policy Factory](#firewall-policy-factory) +- [Hierarchical Firewall Policy Attachments](#hierarchical-firewall-policy-attachments) - [Log Sinks](#log-sinks) - [Data Access Logs](#data-access-logs) - [Custom Roles](#custom-roles) @@ -46,6 +45,12 @@ module "org" { iam_additive_members = { "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 = { allowexternal = { 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 -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 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. ``` -## Hierarchical Firewall Policies +## Hierarchical Firewall Policy Attachments -Hierarchical firewall policies can be managed in two ways: - -- 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 +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: ```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" { source = "./fabric/modules/organization" organization_id = var.organization_id - firewall_policies = { - iap-policy = { - 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" + # attachment via the organization module + firewall_policy_associations = { + test-1 = module.firewall-policy.id } } -# tftest modules=1 resources=4 inventory=hfw.yaml -``` - -### 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 +# tftest modules=2 resources=2 ``` ## Log Sinks @@ -530,18 +456,14 @@ module "org" { ``` - - - ## Files | name | description | resources | |---|---|---| -| [firewall-policies.tf](./firewall-policies.tf) | Hierarchical firewall policies. | google_compute_firewall_policy · google_compute_firewall_policy_association · google_compute_firewall_policy_rule | | [iam.tf](./iam.tf) | IAM bindings, roles and audit logging resources. | google_organization_iam_binding · google_organization_iam_custom_role · google_organization_iam_member · google_organization_iam_policy | | [logging.tf](./logging.tf) | Log sinks and data access logs. | google_bigquery_dataset_iam_member · google_logging_organization_exclusion · google_logging_organization_sink · google_organization_iam_audit_config · google_project_iam_member · google_pubsub_topic_iam_member · google_storage_bucket_iam_member | -| [main.tf](./main.tf) | Module-level locals and resources. | google_essential_contacts_contact | +| [main.tf](./main.tf) | Module-level locals and resources. | google_compute_firewall_policy_association · google_essential_contacts_contact | | [org-policy-custom-constraints.tf](./org-policy-custom-constraints.tf) | None | google_org_policy_custom_constraint | | [organization-policies.tf](./organization-policies.tf) | Organization-level organization policies. | google_org_policy_policy | | [outputs.tf](./outputs.tf) | Module outputs. | | @@ -553,27 +475,26 @@ module "org" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [organization_id](variables.tf#L226) | Organization id in organizations/nnnnnn format. | string | ✓ | | +| [organization_id](variables.tf#L209) | Organization id in organizations/nnnnnn format. | string | ✓ | | | [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. | map(list(string)) | | {} | | [custom_roles](variables.tf#L24) | Map of role name => list of permissions to create in this project. | map(list(string)) | | {} | -| [firewall_policies](variables.tf#L31) | Hierarchical firewall policy rules created in the organization. | map(map(object({…}))) | | {} | -| [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. | map(string) | | {} | -| [firewall_policy_factory](variables.tf#L55) | Configuration for the firewall policy factory. | object({…}) | | null | -| [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. | map(list(string)) | | {} | -| [iam](variables.tf#L72) | IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_additive](variables.tf#L79) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | -| [iam_additive_members](variables.tf#L86) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [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. | map(list(string)) | | null | -| [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. | map(map(list(string))) | | {} | -| [logging_exclusions](variables.tf#L114) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | -| [logging_sinks](variables.tf#L121) | Logging sinks to create for the organization. | map(object({…})) | | {} | -| [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. | map(object({…})) | | {} | -| [org_policies](variables.tf#L173) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | -| [org_policies_data_path](variables.tf#L200) | Path containing org policies in YAML format. | string | | null | -| [org_policy_custom_constraints](variables.tf#L206) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} | -| [org_policy_custom_constraints_data_path](variables.tf#L220) | Path containing org policy custom constraints in YAML format. | string | | null | -| [tag_bindings](variables.tf#L235) | Tag bindings for this organization, in key => tag value id format. | map(string) | | null | -| [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. | map(object({…})) | | {} | +| [firewall_policy_associations](variables.tf#L31) | Hierarchical firewall policies to associate to this folder, in association name => policy id format. | map(string) | | {} | +| [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. | map(list(string)) | | {} | +| [iam](variables.tf#L45) | IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_additive](variables.tf#L52) | Non authoritative IAM bindings, in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_additive_members](variables.tf#L59) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | +| [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. | map(object({…})) | | {} | +| [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. | map(list(string)) | | null | +| [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. | map(map(list(string))) | | {} | +| [logging_exclusions](variables.tf#L97) | Logging exclusions for this organization in the form {NAME -> FILTER}. | map(string) | | {} | +| [logging_sinks](variables.tf#L104) | Logging sinks to create for the organization. | map(object({…})) | | {} | +| [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. | map(object({…})) | | {} | +| [org_policies](variables.tf#L156) | Organization policies applied to this organization keyed by policy name. | map(object({…})) | | {} | +| [org_policies_data_path](variables.tf#L183) | Path containing org policies in YAML format. | string | | null | +| [org_policy_custom_constraints](variables.tf#L189) | Organization policy custom constraints keyed by constraint name. | map(object({…})) | | {} | +| [org_policy_custom_constraints_data_path](variables.tf#L203) | Path containing org policy custom constraints in YAML format. | string | | null | +| [tag_bindings](variables.tf#L218) | Tag bindings for this organization, in key => tag value id format. | map(string) | | null | +| [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. | map(object({…})) | | {} | ## Outputs @@ -582,13 +503,11 @@ module "org" { | [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_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. | | -| [firewall_policy_id](outputs.tf#L45) | Map of firewall policy ids created in the organization. | | -| [id](outputs.tf#L50) | Fully qualified organization id. | | -| [network_tag_keys](outputs.tf#L67) | Tag key resources. | | -| [network_tag_values](outputs.tf#L76) | Tag value resources. | | -| [organization_id](outputs.tf#L86) | Organization id dependent on module resources. | | -| [sink_writer_identities](outputs.tf#L103) | Writer identities created for each sink. | | -| [tag_keys](outputs.tf#L111) | Tag key resources. | | -| [tag_values](outputs.tf#L120) | Tag value resources. | | +| [id](outputs.tf#L40) | Fully qualified organization id. | | +| [network_tag_keys](outputs.tf#L57) | Tag key resources. | | +| [network_tag_values](outputs.tf#L66) | Tag value resources. | | +| [organization_id](outputs.tf#L76) | Organization id dependent on module resources. | | +| [sink_writer_identities](outputs.tf#L93) | Writer identities created for each sink. | | +| [tag_keys](outputs.tf#L101) | Tag key resources. | | +| [tag_values](outputs.tf#L110) | Tag value resources. | | diff --git a/modules/organization/firewall-policies.tf b/modules/organization/firewall-policies.tf deleted file mode 100644 index b5c635af..00000000 --- a/modules/organization/firewall-policies.tf +++ /dev/null @@ -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) -} - diff --git a/modules/organization/iam.tf b/modules/organization/iam.tf index 410c9ece..f5d3b2d7 100644 --- a/modules/organization/iam.tf +++ b/modules/organization/iam.tf @@ -73,6 +73,13 @@ resource "google_organization_iam_member" "additive" { 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" { count = var.iam_policy != null ? 1 : 0 org_id = local.organization_id_numeric diff --git a/modules/organization/main.tf b/modules/organization/main.tf index cc757bc6..cd35bb01 100644 --- a/modules/organization/main.tf +++ b/modules/organization/main.tf @@ -26,3 +26,10 @@ resource "google_essential_contacts_contact" "contact" { language_tag = "en" 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 +} diff --git a/modules/organization/outputs.tf b/modules/organization/outputs.tf index 1d149f7e..9c1ec187 100644 --- a/modules/organization/outputs.tf +++ b/modules/organization/outputs.tf @@ -37,16 +37,6 @@ output "custom_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" { description = "Fully qualified organization id." value = var.organization_id diff --git a/modules/organization/variables.tf b/modules/organization/variables.tf index 7c2d33e4..65f0672b 100644 --- a/modules/organization/variables.tf +++ b/modules/organization/variables.tf @@ -28,40 +28,13 @@ variable "custom_roles" { nullable = false } -variable "firewall_policies" { - description = "Hierarchical firewall policy rules created in the organization." - 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." +variable "firewall_policy_associations" { + description = "Hierarchical firewall policies to associate to this folder, in association name => policy id format." type = map(string) default = {} 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" { 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)) @@ -90,6 +63,16 @@ variable "iam_additive_members" { 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" { 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)) diff --git a/modules/project/README.md b/modules/project/README.md index a39df3ce..8a867b51 100644 --- a/modules/project/README.md +++ b/modules/project/README.md @@ -10,7 +10,9 @@ This module implements the creation and management of one GCP project including - [IAM](#iam) - [Authoritative IAM](#authoritative-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) - [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) @@ -49,7 +51,7 @@ module "project" { 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 -- `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 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 by Role + +Additive IAM is supported via the `iam_additive` variable which is keyed by role: + ```hcl module "project" { source = "./fabric/modules/project" @@ -129,7 +135,9 @@ module "project" { # 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 module "project" { @@ -144,6 +152,33 @@ module "project" { # 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 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`. + ```hcl module "host-project" { source = "./fabric/modules/project" @@ -622,7 +658,7 @@ output "compute_robot" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L161) | Project name and id suffix. | string | ✓ | | +| [name](variables.tf#L171) | Project name and id suffix. | string | ✓ | | | [auto_create_network](variables.tf#L17) | Whether to create the default network for the project. | bool | | false | | [billing_account](variables.tf#L23) | Billing account id. | string | | null | | [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. | map(list(string)) | | {} | @@ -633,30 +669,31 @@ output "compute_robot" { | [iam](variables.tf#L62) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L69) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L76) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [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. | map(list(string)) | | null | -| [labels](variables.tf#L88) | Resource labels. | map(string) | | {} | -| [lien_reason](variables.tf#L95) | If non-empty, creates a project lien with this description. | string | | "" | -| [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. | map(map(list(string))) | | {} | -| [logging_exclusions](variables.tf#L116) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} | -| [logging_sinks](variables.tf#L123) | Logging sinks to create for this project. | map(object({…})) | | {} | -| [metric_scopes](variables.tf#L154) | List of projects that will act as metric scopes for this project. | list(string) | | [] | -| [org_policies](variables.tf#L166) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} | -| [org_policies_data_path](variables.tf#L193) | Path containing org policies in YAML format. | string | | null | -| [oslogin](variables.tf#L199) | Enable OS Login. | bool | | false | -| [oslogin_admins](variables.tf#L205) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string) | | [] | -| [oslogin_users](variables.tf#L213) | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string) | | [] | -| [parent](variables.tf#L220) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null | -| [prefix](variables.tf#L230) | Optional prefix used to generate project id and name. | string | | null | -| [project_create](variables.tf#L240) | Create project. When set to false, uses a data source to reference existing project. | bool | | true | -| [service_config](variables.tf#L246) | Configure service API activation. | object({…}) | | {…} | -| [service_encryption_key_ids](variables.tf#L258) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string)) | | {} | -| [service_perimeter_bridges](variables.tf#L265) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string) | | null | -| [service_perimeter_standard](variables.tf#L272) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string | | null | -| [services](variables.tf#L278) | Service APIs to enable. | list(string) | | [] | -| [shared_vpc_host_config](variables.tf#L284) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null | -| [shared_vpc_service_config](variables.tf#L293) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} | -| [skip_delete](variables.tf#L315) | Allows the underlying resources to be destroyed without destroying the project itself. | bool | | false | -| [tag_bindings](variables.tf#L321) | Tag bindings for this project, in key => tag value id format. | map(string) | | null | +| [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. | map(object({…})) | | {} | +| [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. | map(list(string)) | | null | +| [labels](variables.tf#L98) | Resource labels. | map(string) | | {} | +| [lien_reason](variables.tf#L105) | If non-empty, creates a project lien with this description. | string | | "" | +| [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. | map(map(list(string))) | | {} | +| [logging_exclusions](variables.tf#L126) | Logging exclusions for this project in the form {NAME -> FILTER}. | map(string) | | {} | +| [logging_sinks](variables.tf#L133) | Logging sinks to create for this project. | map(object({…})) | | {} | +| [metric_scopes](variables.tf#L164) | List of projects that will act as metric scopes for this project. | list(string) | | [] | +| [org_policies](variables.tf#L176) | Organization policies applied to this project keyed by policy name. | map(object({…})) | | {} | +| [org_policies_data_path](variables.tf#L203) | Path containing org policies in YAML format. | string | | null | +| [oslogin](variables.tf#L209) | Enable OS Login. | bool | | false | +| [oslogin_admins](variables.tf#L215) | List of IAM-style identities that will be granted roles necessary for OS Login administrators. | list(string) | | [] | +| [oslogin_users](variables.tf#L223) | List of IAM-style identities that will be granted roles necessary for OS Login users. | list(string) | | [] | +| [parent](variables.tf#L230) | Parent folder or organization in 'folders/folder_id' or 'organizations/org_id' format. | string | | null | +| [prefix](variables.tf#L240) | Optional prefix used to generate project id and name. | string | | null | +| [project_create](variables.tf#L250) | Create project. When set to false, uses a data source to reference existing project. | bool | | true | +| [service_config](variables.tf#L256) | Configure service API activation. | object({…}) | | {…} | +| [service_encryption_key_ids](variables.tf#L268) | Cloud KMS encryption key in {SERVICE => [KEY_URL]} format. | map(list(string)) | | {} | +| [service_perimeter_bridges](variables.tf#L275) | Name of VPC-SC Bridge perimeters to add project into. See comment in the variables file for format. | list(string) | | null | +| [service_perimeter_standard](variables.tf#L282) | Name of VPC-SC Standard perimeter to add project into. See comment in the variables file for format. | string | | null | +| [services](variables.tf#L288) | Service APIs to enable. | list(string) | | [] | +| [shared_vpc_host_config](variables.tf#L294) | Configures this project as a Shared VPC host project (mutually exclusive with shared_vpc_service_project). | object({…}) | | null | +| [shared_vpc_service_config](variables.tf#L303) | Configures this project as a Shared VPC service project (mutually exclusive with shared_vpc_host_config). | object({…}) | | {…} | +| [skip_delete](variables.tf#L325) | Allows the underlying resources to be destroyed without destroying the project itself. | bool | | false | +| [tag_bindings](variables.tf#L331) | Tag bindings for this project, in key => tag value id format. | map(string) | | null | ## Outputs diff --git a/modules/project/iam.tf b/modules/project/iam.tf index 2ee8a63c..7b67d895 100644 --- a/modules/project/iam.tf +++ b/modules/project/iam.tf @@ -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" { for_each = var.oslogin ? toset(distinct(concat(var.oslogin_admins, var.oslogin_users))) : toset([]) project = local.project.project_id diff --git a/modules/project/variables.tf b/modules/project/variables.tf index eb6b5da7..4ab51062 100644 --- a/modules/project/variables.tf +++ b/modules/project/variables.tf @@ -79,6 +79,16 @@ variable "iam_additive_members" { 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" { 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)) diff --git a/modules/source-repository/README.md b/modules/source-repository/README.md index 17662fea..4cfae8ea 100644 --- a/modules/source-repository/README.md +++ b/modules/source-repository/README.md @@ -2,6 +2,15 @@ This module allows managing a single Cloud Source Repository, including IAM bindings and basic Cloud Build triggers. + +- [Examples](#examples) + - [Repository with IAM](#repository-with-iam) + - [Repository with Cloud Build trigger](#repository-with-cloud-build-trigger) +- [Files](#files) +- [Variables](#variables) +- [Outputs](#outputs) + + ## Examples ### Repository with IAM @@ -14,8 +23,14 @@ module "repo" { iam = { "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 @@ -46,7 +61,6 @@ module "repo" { - ## Files | name | description | resources | @@ -61,13 +75,14 @@ module "repo" { | name | description | type | required | default | |---|---|:---:|:---:|:---:| -| [name](variables.tf#L44) | Repository name. | string | ✓ | | -| [project_id](variables.tf#L49) | Project used for resources. | string | ✓ | | +| [name](variables.tf#L54) | Repository name. | string | ✓ | | +| [project_id](variables.tf#L59) | Project used for resources. | string | ✓ | | | [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. | map(list(string)) | | {} | | [iam](variables.tf#L24) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive](variables.tf#L31) | IAM additive bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [iam_additive_members](variables.tf#L38) | IAM additive bindings in {MEMBERS => [ROLE]} format. This might break if members are dynamic values. | map(list(string)) | | {} | -| [triggers](variables.tf#L54) | Cloud Build triggers. | map(object({…})) | | {} | +| [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. | map(object({…})) | | {} | +| [triggers](variables.tf#L64) | Cloud Build triggers. | map(object({…})) | | {} | ## Outputs @@ -76,5 +91,4 @@ module "repo" { | [id](outputs.tf#L17) | Fully qualified repository id. | | | [name](outputs.tf#L22) | Repository name. | | | [url](outputs.tf#L27) | Repository URL. | | - diff --git a/modules/source-repository/iam.tf b/modules/source-repository/iam.tf index e5c3ec49..f1ec3ebc 100644 --- a/modules/source-repository/iam.tf +++ b/modules/source-repository/iam.tf @@ -65,3 +65,11 @@ resource "google_sourcerepo_repository_iam_member" "additive" { role = each.value.role 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 +} diff --git a/modules/source-repository/variables.tf b/modules/source-repository/variables.tf index 587b0f6d..74e44aa4 100644 --- a/modules/source-repository/variables.tf +++ b/modules/source-repository/variables.tf @@ -41,6 +41,16 @@ variable "iam_additive_members" { 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" { description = "Repository name." type = string diff --git a/tests/blueprints/data_solutions/shielded_folder/examples/simple.yaml b/tests/blueprints/data_solutions/shielded_folder/examples/simple.yaml index 23bc8b7a..661dd97f 100644 --- a/tests/blueprints/data_solutions/shielded_folder/examples/simple.yaml +++ b/tests/blueprints/data_solutions/shielded_folder/examples/simple.yaml @@ -13,21 +13,611 @@ # limitations under the License. values: - module.test.module.folder.google_compute_firewall_policy.policy["prefix-fw-policy"]: - short_name: prefix-fw-policy + module.test.module.firewall-policy.google_compute_firewall_policy.hierarchical[0]: + 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]: display_name: ShieldedMVP 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]: + auto_create_network: false billing_account: 123456-123456-123456 + labels: null + name: 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]: parent: organizations/1122334455 + timeouts: null title: shielded-folder module.test.module.vpc-sc[0].google_access_context_manager_service_perimeter.regular["shielded"]: description: null 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 + use_explicit_dry_run_spec: true counts: google_access_context_manager_access_policy: 1 @@ -47,5 +637,7 @@ counts: google_project_service_identity: 1 google_projects: 1 google_storage_project_service_account: 1 - modules: 6 + modules: 7 resources: 38 + +outputs: {} diff --git a/tests/fast/stages/s2_networking_a_peering/stage.yaml b/tests/fast/stages/s2_networking_a_peering/stage.yaml index 9a16a6b4..2c2ca3da 100644 --- a/tests/fast/stages/s2_networking_a_peering/stage.yaml +++ b/tests/fast/stages/s2_networking_a_peering/stage.yaml @@ -13,5 +13,5 @@ # limitations under the License. counts: - modules: 27 + modules: 28 resources: 151 diff --git a/tests/fast/stages/s2_networking_b_vpn/stage.yaml b/tests/fast/stages/s2_networking_b_vpn/stage.yaml index 70c5c30a..9cb8ee83 100644 --- a/tests/fast/stages/s2_networking_b_vpn/stage.yaml +++ b/tests/fast/stages/s2_networking_b_vpn/stage.yaml @@ -13,5 +13,5 @@ # limitations under the License. counts: - modules: 29 + modules: 30 resources: 188 diff --git a/tests/fast/stages/s2_networking_c_nva/stage.yaml b/tests/fast/stages/s2_networking_c_nva/stage.yaml index e2d1aaf6..3da9b352 100644 --- a/tests/fast/stages/s2_networking_c_nva/stage.yaml +++ b/tests/fast/stages/s2_networking_c_nva/stage.yaml @@ -13,5 +13,5 @@ # limitations under the License. counts: - modules: 41 + modules: 42 resources: 197 diff --git a/tests/fast/stages/s2_networking_d_separate_envs/stage.yaml b/tests/fast/stages/s2_networking_d_separate_envs/stage.yaml index 4b24b412..f60257c4 100644 --- a/tests/fast/stages/s2_networking_d_separate_envs/stage.yaml +++ b/tests/fast/stages/s2_networking_d_separate_envs/stage.yaml @@ -13,5 +13,5 @@ # limitations under the License. counts: - modules: 20 + modules: 21 resources: 168 diff --git a/tests/fast/stages/s2_networking_e_nva_bgp/stage.yaml b/tests/fast/stages/s2_networking_e_nva_bgp/stage.yaml index ffde4a39..fa62dac0 100644 --- a/tests/fast/stages/s2_networking_e_nva_bgp/stage.yaml +++ b/tests/fast/stages/s2_networking_e_nva_bgp/stage.yaml @@ -13,5 +13,5 @@ # limitations under the License. counts: - modules: 35 + modules: 36 resources: 210 diff --git a/tests/fixtures.py b/tests/fixtures.py index 98bf3a26..7a295df6 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -168,6 +168,7 @@ def plan_validator(module_path, inventory_paths, basedir, tf_var_files=None, for path in inventory_paths: # allow tfvars and inventory to be relative to the caller path = basedir / path + relative_path = path.relative_to(_REPO_ROOT) try: inventory = yaml.safe_load(path.read_text()) 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'] for address, expected_value in expected_values.items(): 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(): 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] 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: expected_counts = inventory['counts'] for type_, expected_count in expected_counts.items(): 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_] 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: expected_outputs = inventory['outputs'] for output_name, expected_output in expected_outputs.items(): 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] # assert 'value' in output, \ # f'output `{output_name}` does not have a value (is it sensitive or dynamic?)' plan_output = output.get('value', '__missing__') 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 diff --git a/tests/modules/cloud_run/examples/gen2.yaml b/tests/modules/cloud_run/examples/gen2.yaml new file mode 100644 index 00000000..e38bffad --- /dev/null +++ b/tests/modules/cloud_run/examples/gen2.yaml @@ -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 diff --git a/tests/modules/compute_mig/examples/stateful.yaml b/tests/modules/compute_mig/examples/stateful.yaml index 5fcd7ff4..4783e23e 100644 --- a/tests/modules/compute_mig/examples/stateful.yaml +++ b/tests/modules/compute_mig/examples/stateful.yaml @@ -13,25 +13,49 @@ # limitations under the License. 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"]: + instance_group_manager: mig-test minimal_action: NONE most_disruptive_allowed_action: REPLACE name: instance-1 preserved_state: - disk: - delete_rule: NEVER - device_name: persistent-disk-1 + device_name: data-1 mode: READ_WRITE - source: test-disk + source: projects/my-prj/zones/europe-west8-b/disks/test-data-1 metadata: foo: bar - project: my-project + project: my-prj remove_instance_state_on_destroy: false timeouts: null - zone: europe-west1-b + zone: europe-west8-b counts: - google_compute_autoscaler: 1 google_compute_instance_group_manager: 1 google_compute_instance_template: 1 google_compute_per_instance_config: 1 + modules: 2 + resources: 3 + +outputs: {} diff --git a/tests/modules/dataplex_datascan/examples/datascan_iam.yaml b/tests/modules/dataplex_datascan/examples/datascan_iam.yaml index b2d7a985..28885cfe 100644 --- a/tests/modules/dataplex_datascan/examples/datascan_iam.yaml +++ b/tests/modules/dataplex_datascan/examples/datascan_iam.yaml @@ -11,6 +11,7 @@ # 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.dataplex-datascan.google_dataplex_datascan.datascan: data: @@ -57,11 +58,19 @@ values: - group:user-group@example.com project: my-project-name role: roles/dataplex.dataScanViewer + module.dataplex-datascan.google_dataplex_datascan_iam_member.members["am1-viewer"]: + condition: [] + data_scan_id: test-datascan + location: us-central1 + member: user:am1@example.com + project: my-project-name + role: roles/dataplex.dataScanViewer counts: google_dataplex_datascan: 1 google_dataplex_datascan_iam_binding: 3 + google_dataplex_datascan_iam_member: 1 modules: 1 - resources: 4 + resources: 5 -outputs: {} \ No newline at end of file +outputs: {} diff --git a/tests/modules/folder/examples/iam.yaml b/tests/modules/folder/examples/iam.yaml index 6f0fe2e5..541fbf86 100644 --- a/tests/modules/folder/examples/iam.yaml +++ b/tests/modules/folder/examples/iam.yaml @@ -16,6 +16,7 @@ values: module.folder.google_folder.folder[0]: display_name: Folder name parent: organizations/1234567890 + timeouts: null module.folder.google_folder_iam_binding.authoritative["roles/owner"]: condition: [] members: @@ -52,8 +53,16 @@ values: condition: [] member: user:am2@example.org role: roles/storage.objectViewer + module.folder.google_folder_iam_member.members["am1-storage-admin"]: + condition: [] + member: user:am1@example.org + role: roles/storage.admin counts: google_folder: 1 google_folder_iam_binding: 3 - google_folder_iam_member: 5 + google_folder_iam_member: 6 + modules: 1 + resources: 10 + +outputs: {} diff --git a/tests/modules/kms/examples/basic.yaml b/tests/modules/kms/examples/basic.yaml index acd7ac29..e29297a1 100644 --- a/tests/modules/kms/examples/basic.yaml +++ b/tests/modules/kms/examples/basic.yaml @@ -19,12 +19,14 @@ values: purpose: ENCRYPT_DECRYPT rotation_period: null skip_initial_version_creation: null + timeouts: null module.kms.google_kms_crypto_key.default["key-b"]: labels: null name: key-b purpose: ENCRYPT_DECRYPT rotation_period: 604800s skip_initial_version_creation: null + timeouts: null module.kms.google_kms_crypto_key.default["key-c"]: labels: env: test @@ -32,23 +34,29 @@ values: purpose: ENCRYPT_DECRYPT rotation_period: null skip_initial_version_creation: null + timeouts: null module.kms.google_kms_crypto_key_iam_binding.default["key-a.roles/cloudkms.admin"]: condition: [] members: - user:user3@example.com role: roles/cloudkms.admin - module.kms.google_kms_crypto_key_iam_member.default["key-b.roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user4@example.com"]: - condition: [] + ? module.kms.google_kms_crypto_key_iam_member.default["key-b.roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user4@example.com"] + : condition: [] member: user:user4@example.com role: roles/cloudkms.cryptoKeyEncrypterDecrypter - module.kms.google_kms_crypto_key_iam_member.default["key-b.roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user5@example.com"]: - condition: [] + ? module.kms.google_kms_crypto_key_iam_member.default["key-b.roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user5@example.com"] + : condition: [] member: user:user5@example.com role: roles/cloudkms.cryptoKeyEncrypterDecrypter + module.kms.google_kms_crypto_key_iam_member.members["key-b-am1"]: + condition: [] + member: user:am1@example.com + role: roles/cloudkms.cryptoKeyEncrypterDecrypter module.kms.google_kms_key_ring.default[0]: location: europe-west1 name: test project: my-project + timeouts: null module.kms.google_kms_key_ring_iam_member.default["roles/cloudkms.cryptoKeyEncrypterDecrypteruser:user1@example.com"]: condition: [] member: user:user1@example.com @@ -61,6 +69,10 @@ values: counts: google_kms_crypto_key: 3 google_kms_crypto_key_iam_binding: 1 - google_kms_crypto_key_iam_member: 2 + google_kms_crypto_key_iam_member: 3 google_kms_key_ring: 1 google_kms_key_ring_iam_member: 2 + modules: 1 + resources: 10 + +outputs: {} diff --git a/tests/modules/net_vpc_firewall_policy/examples/factory.yaml b/tests/modules/net_firewall_policy/examples/factory.yaml similarity index 98% rename from tests/modules/net_vpc_firewall_policy/examples/factory.yaml rename to tests/modules/net_firewall_policy/examples/factory.yaml index b3709fe6..216a945a 100644 --- a/tests/modules/net_vpc_firewall_policy/examples/factory.yaml +++ b/tests/modules/net_firewall_policy/examples/factory.yaml @@ -18,7 +18,7 @@ values: module.firewall-policy.google_compute_firewall_policy_association.hierarchical["test"]: attachment_target: folders/4567890123 name: test-1-test - module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["egress/icmp"]: + module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["ingress/icmp"]: action: allow direction: INGRESS disabled: false @@ -41,7 +41,7 @@ values: priority: 1000 target_resources: null target_service_accounts: null - module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["ingress/smtp"]: + module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["egress/smtp"]: action: deny direction: EGRESS disabled: false diff --git a/tests/modules/net_vpc_firewall_policy/examples/global-net.yaml b/tests/modules/net_firewall_policy/examples/global-net.yaml similarity index 100% rename from tests/modules/net_vpc_firewall_policy/examples/global-net.yaml rename to tests/modules/net_firewall_policy/examples/global-net.yaml diff --git a/tests/modules/net_vpc_firewall_policy/examples/hierarchical.yaml b/tests/modules/net_firewall_policy/examples/hierarchical.yaml similarity index 100% rename from tests/modules/net_vpc_firewall_policy/examples/hierarchical.yaml rename to tests/modules/net_firewall_policy/examples/hierarchical.yaml diff --git a/tests/modules/net_vpc_firewall_policy/examples/regional-net.yaml b/tests/modules/net_firewall_policy/examples/regional-net.yaml similarity index 100% rename from tests/modules/net_vpc_firewall_policy/examples/regional-net.yaml rename to tests/modules/net_firewall_policy/examples/regional-net.yaml diff --git a/tests/modules/net_vpc/examples/subnet-iam.yaml b/tests/modules/net_vpc/examples/subnet-iam.yaml index 8aa5bed8..2c6dcccc 100644 --- a/tests/modules/net_vpc/examples/subnet-iam.yaml +++ b/tests/modules/net_vpc/examples/subnet-iam.yaml @@ -14,17 +14,63 @@ values: module.vpc.google_compute_network.network[0]: + auto_create_subnetworks: false + delete_default_routes_on_create: false + description: Terraform-managed. + enable_ula_internal_ipv6: null name: my-network + network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL project: my-project + routing_mode: GLOBAL + timeouts: null + module.vpc.google_compute_route.gateway["private-googleapis"]: + description: Terraform-managed. + dest_range: 199.36.153.8/30 + name: my-network-private-googleapis + next_hop_gateway: default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 1000 + project: my-project + tags: null + timeouts: null + module.vpc.google_compute_route.gateway["restricted-googleapis"]: + description: Terraform-managed. + dest_range: 199.36.153.4/30 + name: my-network-restricted-googleapis + next_hop_gateway: default-internet-gateway + next_hop_ilb: null + next_hop_instance: null + next_hop_vpn_tunnel: null + priority: 1000 + project: my-project + tags: null + timeouts: null module.vpc.google_compute_subnetwork.subnetwork["europe-west1/subnet-1"]: + description: Terraform-managed. + ip_cidr_range: 10.0.1.0/24 + ipv6_access_type: null + log_config: [] name: subnet-1 + private_ip_google_access: true project: my-project region: europe-west1 + role: null + secondary_ip_range: [] + timeouts: null module.vpc.google_compute_subnetwork.subnetwork["europe-west1/subnet-2"]: + description: Terraform-managed. + ip_cidr_range: 10.0.1.0/24 + ipv6_access_type: null + log_config: [] name: subnet-2 private_ip_google_access: true project: my-project region: europe-west1 + role: null + secondary_ip_range: [] + timeouts: null module.vpc.google_compute_subnetwork_iam_binding.binding["europe-west1/subnet-1.roles/compute.networkUser"]: condition: [] members: @@ -34,16 +80,23 @@ values: region: europe-west1 role: roles/compute.networkUser subnetwork: subnet-1 - module.vpc.google_compute_subnetwork_iam_member.binding["europe-west1/subnet-2.roles/compute.networkUser.user:user2@example.com"]: - condition: [] + ? module.vpc.google_compute_subnetwork_iam_member.binding["europe-west1/subnet-2.roles/compute.networkUser.group:group2@example.com"] + : condition: [] + member: group:group2@example.com + project: my-project + region: europe-west1 + role: roles/compute.networkUser + subnetwork: subnet-2 + ? module.vpc.google_compute_subnetwork_iam_member.binding["europe-west1/subnet-2.roles/compute.networkUser.user:user2@example.com"] + : condition: [] member: user:user2@example.com project: my-project region: europe-west1 role: roles/compute.networkUser subnetwork: subnet-2 - module.vpc.google_compute_subnetwork_iam_member.binding["europe-west1/subnet-2.roles/compute.networkUser.group:group2@example.com"]: + module.vpc.google_compute_subnetwork_iam_member.members["subnet-2-am1"]: condition: [] - member: group:group2@example.com + member: user:am1@example.com project: my-project region: europe-west1 role: roles/compute.networkUser @@ -51,7 +104,11 @@ values: counts: google_compute_network: 1 + google_compute_route: 2 google_compute_subnetwork: 2 google_compute_subnetwork_iam_binding: 1 - google_compute_subnetwork_iam_member: 2 - google_compute_route: 2 + google_compute_subnetwork_iam_member: 3 + modules: 1 + resources: 9 + +outputs: {} diff --git a/tests/modules/organization/examples/basic.yaml b/tests/modules/organization/examples/basic.yaml index 9960a712..3c8bf8a1 100644 --- a/tests/modules/organization/examples/basic.yaml +++ b/tests/modules/organization/examples/basic.yaml @@ -25,6 +25,7 @@ values: deny_all: null enforce: 'TRUE' values: [] + timeouts: null module.org.google_org_policy_policy.default["compute.skipDefaultNetworkCreation"]: name: organizations/1234567890/policies/compute.skipDefaultNetworkCreation parent: organizations/1234567890 @@ -37,6 +38,7 @@ values: deny_all: null enforce: 'TRUE' values: [] + timeouts: null module.org.google_org_policy_policy.default["compute.trustedImageProjects"]: name: organizations/1234567890/policies/compute.trustedImageProjects parent: organizations/1234567890 @@ -52,6 +54,7 @@ values: - allowed_values: - projects/my-project denied_values: null + timeouts: null module.org.google_org_policy_policy.default["compute.vmExternalIpAccess"]: name: organizations/1234567890/policies/compute.vmExternalIpAccess parent: organizations/1234567890 @@ -64,6 +67,20 @@ values: deny_all: 'TRUE' enforce: null values: [] + timeouts: null + module.org.google_org_policy_policy.default["custom.gkeEnableAutoUpgrade"]: + name: organizations/1234567890/policies/custom.gkeEnableAutoUpgrade + parent: organizations/1234567890 + spec: + - inherit_from_parent: null + reset: null + rules: + - allow_all: null + condition: [] + deny_all: null + enforce: 'TRUE' + values: [] + timeouts: null module.org.google_org_policy_policy.default["iam.allowedPolicyMemberDomains"]: name: organizations/1234567890/policies/iam.allowedPolicyMemberDomains parent: organizations/1234567890 @@ -95,6 +112,7 @@ values: - C0xxxxxxx - C0yyyyyyy denied_values: null + timeouts: null module.org.google_org_policy_policy.default["iam.disableServiceAccountKeyCreation"]: name: organizations/1234567890/policies/iam.disableServiceAccountKeyCreation parent: organizations/1234567890 @@ -107,6 +125,7 @@ values: deny_all: null enforce: 'TRUE' values: [] + timeouts: null module.org.google_org_policy_policy.default["iam.disableServiceAccountKeyUpload"]: name: organizations/1234567890/policies/iam.disableServiceAccountKeyUpload parent: organizations/1234567890 @@ -128,6 +147,7 @@ values: deny_all: null enforce: 'FALSE' values: [] + timeouts: null module.org.google_organization_iam_binding.authoritative["roles/owner"]: condition: [] members: @@ -156,20 +176,34 @@ values: member: user:compute@example.org org_id: '1234567890' role: roles/container.viewer + module.org.google_organization_iam_member.members["am1-storage-admin"]: + condition: [] + member: user:am1@example.org + org_id: '1234567890' + role: roles/storage.admin module.org.google_tags_tag_key.default["allowexternal"]: description: Allow external identities. parent: organizations/1234567890 purpose: null purpose_data: null short_name: allowexternal + timeouts: null module.org.google_tags_tag_value.default["allowexternal/false"]: + description: Managed by the Terraform organization module. short_name: 'false' + timeouts: null module.org.google_tags_tag_value.default["allowexternal/true"]: + description: Managed by the Terraform organization module. short_name: 'true' + timeouts: null counts: google_org_policy_policy: 8 google_organization_iam_binding: 3 - google_organization_iam_member: 2 + google_organization_iam_member: 3 google_tags_tag_key: 1 google_tags_tag_value: 2 + modules: 1 + resources: 17 + +outputs: {} diff --git a/tests/modules/organization/firewall_policies_factory_combined.tfvars b/tests/modules/organization/firewall_policies_factory_combined.tfvars deleted file mode 100644 index 7ea51bb0..00000000 --- a/tests/modules/organization/firewall_policies_factory_combined.tfvars +++ /dev/null @@ -1,51 +0,0 @@ -firewall_policies = { - policy1 = { - allow-ingress = { - description = "" - direction = "INGRESS" - action = "allow" - priority = 100 - ranges = ["10.0.0.0/8"] - ports = { - tcp = ["22"] - } - target_service_accounts = null - target_resources = null - logging = false - } - deny-egress = { - description = "" - direction = "EGRESS" - action = "deny" - priority = 200 - ranges = ["192.168.0.0/24"] - ports = { - tcp = ["443"] - } - target_service_accounts = null - target_resources = null - logging = false - } - } - policy2 = { - allow-ingress = { - description = "" - direction = "INGRESS" - action = "allow" - priority = 100 - ranges = ["10.0.0.0/8"] - ports = { - tcp = ["22"] - } - target_service_accounts = null - target_resources = null - logging = false - } - } -} - -firewall_policy_factory = { - cidr_file = "../../tests/modules/organization/data/firewall-cidrs.yaml" - policy_name = "factory-1" - rules_file = "../../tests/modules/organization/data/firewall-rules.yaml" -} diff --git a/tests/modules/organization/firewall_policies_factory_combined.yaml b/tests/modules/organization/firewall_policies_factory_combined.yaml deleted file mode 100644 index 3b5cf6cc..00000000 --- a/tests/modules/organization/firewall_policies_factory_combined.yaml +++ /dev/null @@ -1,27 +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. - -values: - google_compute_firewall_policy.policy["factory-1"]: {} - google_compute_firewall_policy.policy["policy1"]: {} - google_compute_firewall_policy.policy["policy2"]: {} - google_compute_firewall_policy_rule.rule["factory-1-allow-admins"]: {} - google_compute_firewall_policy_rule.rule["factory-1-allow-ssh-from-iap"]: {} - google_compute_firewall_policy_rule.rule["policy1-allow-ingress"]: {} - google_compute_firewall_policy_rule.rule["policy1-deny-egress"]: {} - google_compute_firewall_policy_rule.rule["policy2-allow-ingress"]: {} - -counts: - google_compute_firewall_policy: 3 - google_compute_firewall_policy_rule: 5 diff --git a/tests/modules/organization/tftest.yaml b/tests/modules/organization/tftest.yaml index d179d057..2f1cec23 100644 --- a/tests/modules/organization/tftest.yaml +++ b/tests/modules/organization/tftest.yaml @@ -21,5 +21,4 @@ tests: org_policies_list: org_policies_boolean: org_policies_custom_constraints: - firewall_policies_factory_combined: tags: diff --git a/tests/modules/project/examples/iam-members.yaml b/tests/modules/project/examples/iam-members.yaml new file mode 100644 index 00000000..b80fc2bf --- /dev/null +++ b/tests/modules/project/examples/iam-members.yaml @@ -0,0 +1,48 @@ +# 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.project.google_project.project[0]: + auto_create_network: false + billing_account: null + folder_id: null + labels: null + name: project-example + org_id: null + project_id: project-example + skip_delete: false + timeouts: null + module.project.google_project_iam_member.members["one-owner"]: + condition: [] + member: user:one@example.org + project: project-example + role: roles/owner + module.project.google_project_iam_member.members["two-compute-admin"]: + condition: [] + member: user:two@example.org + project: project-example + role: roles/compute.admin + module.project.google_project_iam_member.members["two-viewer"]: + condition: [] + member: user:two@example.org + project: project-example + role: roles/viewer + +counts: + google_project: 1 + google_project_iam_member: 3 + modules: 1 + resources: 4 + +outputs: {} diff --git a/tests/modules/source_repository/examples/simple.yaml b/tests/modules/source_repository/examples/simple.yaml index 41d6aea6..249b6af1 100644 --- a/tests/modules/source_repository/examples/simple.yaml +++ b/tests/modules/source_repository/examples/simple.yaml @@ -17,6 +17,7 @@ values: name: my-repo project: my-project pubsub_configs: [] + timeouts: null module.repo.google_sourcerepo_repository_iam_binding.authoritative["roles/source.reader"]: condition: [] members: @@ -24,7 +25,18 @@ values: project: my-project repository: my-repo role: roles/source.reader + module.repo.google_sourcerepo_repository_iam_member.members["am1-reader"]: + condition: [] + member: user:am1@example.com + project: my-project + repository: my-repo + role: roles/source.reader counts: google_sourcerepo_repository: 1 google_sourcerepo_repository_iam_binding: 1 + google_sourcerepo_repository_iam_member: 1 + modules: 1 + resources: 3 + +outputs: {}