From 80f9ce630721196798784e098eaf5617160c5720 Mon Sep 17 00:00:00 2001 From: Luca Prete Date: Thu, 1 Aug 2024 11:41:31 +0200 Subject: [PATCH] [FAST] Add basic NGFW enterprise stage (#2410) --- fast/stage-links.sh | 15 ++ fast/stages/0-bootstrap/automation.tf | 1 + .../network_firewall_policies_admin.yaml | 22 +++ .../custom-roles/ngfw_enterprise_admin.yaml | 40 ++++ fast/stages/0-bootstrap/organization-iam.tf | 2 +- fast/stages/0-bootstrap/organization.tf | 39 ++-- fast/stages/1-resman/README.md | 54 +++--- fast/stages/1-resman/branch-netsec.tf | 76 ++++++++ fast/stages/1-resman/branch-networking.tf | 3 + fast/stages/1-resman/cicd-netsec.tf | 85 +++++++++ fast/stages/1-resman/iam.tf | 8 + fast/stages/1-resman/outputs.tf | 27 ++- .../1-resman/templates/providers.tf.tpl | 2 +- fast/stages/1-resman/variables-fast.tf | 10 +- fast/stages/1-resman/variables.tf | 6 + fast/stages/1-tenant-factory/README.md | 14 +- .../stages/1-tenant-factory/variables-fast.tf | 12 +- fast/stages/2-networking-a-simple/net-dev.tf | 1 + fast/stages/2-networking-a-simple/net-prod.tf | 1 + fast/stages/2-networking-b-nva/net-dev.tf | 3 +- fast/stages/2-networking-b-nva/net-prod.tf | 3 +- .../2-networking-c-separate-envs/net-dev.tf | 1 + .../2-networking-c-separate-envs/net-prod.tf | 1 + fast/stages/3-network-security/README.md | 172 ++++++++++++++++++ .../stages/3-network-security/data/cidrs.yaml | 18 ++ .../firewall-policy-rules/dev/egress.yaml | 18 ++ .../firewall-policy-rules/dev/ingress.yaml | 21 +++ .../firewall-policy-rules/prod/egress.yaml | 18 ++ .../firewall-policy-rules/prod/ingress.yaml | 21 +++ fast/stages/3-network-security/diagram.png | Bin 0 -> 73343 bytes fast/stages/3-network-security/diagram.svg | 1 + fast/stages/3-network-security/main.tf | 68 +++++++ fast/stages/3-network-security/net-dev.tf | 59 ++++++ fast/stages/3-network-security/net-prod.tf | 59 ++++++ fast/stages/3-network-security/outputs.tf | 28 +++ .../3-network-security/variables-fast.tf | 80 ++++++++ fast/stages/3-network-security/variables.tf | 49 +++++ tests/fast/stages/s0_bootstrap/checklist.yaml | 15 +- tests/fast/stages/s0_bootstrap/simple.yaml | 16 +- tests/fast/stages/s1_resman/checklist.tfvars | 10 +- tests/fast/stages/s1_resman/checklist.yaml | 22 +-- tests/fast/stages/s1_resman/simple.tfvars | 10 +- tests/fast/stages/s1_resman/simple.yaml | 22 +-- .../stages/s1_tenant_factory/simple.tfvars | 12 +- .../stages/s2_networking_a_simple/ncc.yaml | 7 +- .../stages/s2_networking_a_simple/simple.yaml | 6 +- .../stages/s2_networking_a_simple/vpn.yaml | 6 +- .../stages/s2_networking_b_nva/ncc-ra.yaml | 6 +- .../stages/s2_networking_b_nva/simple.yaml | 6 +- .../s2_networking_c_separate_envs/simple.yaml | 6 +- .../stages/s3_network_security/__init__.py | 13 ++ .../stages/s3_network_security/simple.tfvars | 22 +++ .../stages/s3_network_security/simple.yaml | 27 +++ 53 files changed, 1119 insertions(+), 125 deletions(-) create mode 100644 fast/stages/0-bootstrap/data/custom-roles/network_firewall_policies_admin.yaml create mode 100644 fast/stages/0-bootstrap/data/custom-roles/ngfw_enterprise_admin.yaml create mode 100644 fast/stages/1-resman/branch-netsec.tf create mode 100644 fast/stages/1-resman/cicd-netsec.tf create mode 100644 fast/stages/3-network-security/README.md create mode 100644 fast/stages/3-network-security/data/cidrs.yaml create mode 100644 fast/stages/3-network-security/data/firewall-policy-rules/dev/egress.yaml create mode 100644 fast/stages/3-network-security/data/firewall-policy-rules/dev/ingress.yaml create mode 100644 fast/stages/3-network-security/data/firewall-policy-rules/prod/egress.yaml create mode 100644 fast/stages/3-network-security/data/firewall-policy-rules/prod/ingress.yaml create mode 100644 fast/stages/3-network-security/diagram.png create mode 100644 fast/stages/3-network-security/diagram.svg create mode 100644 fast/stages/3-network-security/main.tf create mode 100644 fast/stages/3-network-security/net-dev.tf create mode 100644 fast/stages/3-network-security/net-prod.tf create mode 100644 fast/stages/3-network-security/outputs.tf create mode 100644 fast/stages/3-network-security/variables-fast.tf create mode 100644 fast/stages/3-network-security/variables.tf create mode 100644 tests/fast/stages/s3_network_security/__init__.py create mode 100644 tests/fast/stages/s3_network_security/simple.tfvars create mode 100644 tests/fast/stages/s3_network_security/simple.yaml diff --git a/fast/stage-links.sh b/fast/stage-links.sh index 5fb2a5e6f..7a2fed3d5 100755 --- a/fast/stage-links.sh +++ b/fast/stage-links.sh @@ -83,6 +83,21 @@ case $STAGE_NAME in tenants/$TENANT/tfvars/1-resman.auto.tfvars.json" fi ;; +"3-network-security"*) + if [[ -z "$TENANT" ]]; then + echo "# if this is a tenant stage, set a \$TENANT variable with the tenant shortname and run the command again" + PROVIDER="providers/3-netsec-providers.tf" + TFVARS="tfvars/0-bootstrap.auto.tfvars.json + tfvars/1-resman.auto.tfvars.json + tfvars/2-networking.auto.tfvars.json" + else + unset GLOBALS + PROVIDER="tenants/$TENANT/providers/3-netsec-providers.tf" + TFVARS="tenants/$TENANT/tfvars/0-bootstrap-tenant.auto.tfvars.json + tenants/$TENANT/tfvars/1-resman.auto.tfvars.json + tenants/$TENANT/tfvars/2-networking.auto.tfvars.json" + fi + ;; *) # check for a "dev" stage 3 echo "no stage found, trying for parent stage 3..." diff --git a/fast/stages/0-bootstrap/automation.tf b/fast/stages/0-bootstrap/automation.tf index 72d1419f7..772079b06 100644 --- a/fast/stages/0-bootstrap/automation.tf +++ b/fast/stages/0-bootstrap/automation.tf @@ -143,6 +143,7 @@ module "automation-project" { "essentialcontacts.googleapis.com", "iam.googleapis.com", "iamcredentials.googleapis.com", + "networksecurity.googleapis.com", "orgpolicy.googleapis.com", "pubsub.googleapis.com", "servicenetworking.googleapis.com", diff --git a/fast/stages/0-bootstrap/data/custom-roles/network_firewall_policies_admin.yaml b/fast/stages/0-bootstrap/data/custom-roles/network_firewall_policies_admin.yaml new file mode 100644 index 000000000..eb78791ac --- /dev/null +++ b/fast/stages/0-bootstrap/data/custom-roles/network_firewall_policies_admin.yaml @@ -0,0 +1,22 @@ +# Copyright 2024 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. + +name: networkFirewallPoliciesAdmin +includedPermissions: + - compute.networks.setFirewallPolicy + - networksecurity.firewallEndpointAssociations.create + - networksecurity.firewallEndpointAssociations.delete + - networksecurity.firewallEndpointAssociations.get + - networksecurity.firewallEndpointAssociations.list + - networksecurity.firewallEndpointAssociations.update diff --git a/fast/stages/0-bootstrap/data/custom-roles/ngfw_enterprise_admin.yaml b/fast/stages/0-bootstrap/data/custom-roles/ngfw_enterprise_admin.yaml new file mode 100644 index 000000000..3c54a5834 --- /dev/null +++ b/fast/stages/0-bootstrap/data/custom-roles/ngfw_enterprise_admin.yaml @@ -0,0 +1,40 @@ +# Copyright 2024 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. + +name: ngfwEnterpriseAdmin +includedPermissions: + - networksecurity.firewallEndpoints.create + - networksecurity.firewallEndpoints.delete + - networksecurity.firewallEndpoints.get + - networksecurity.firewallEndpoints.list + - networksecurity.firewallEndpoints.update + - networksecurity.firewallEndpoints.use + - networksecurity.locations.get + - networksecurity.locations.list + - networksecurity.operations.cancel + - networksecurity.operations.delete + - networksecurity.operations.get + - networksecurity.operations.list + - networksecurity.securityProfileGroups.create + - networksecurity.securityProfileGroups.delete + - networksecurity.securityProfileGroups.get + - networksecurity.securityProfileGroups.list + - networksecurity.securityProfileGroups.update + - networksecurity.securityProfileGroups.use + - networksecurity.securityProfiles.create + - networksecurity.securityProfiles.delete + - networksecurity.securityProfiles.get + - networksecurity.securityProfiles.list + - networksecurity.securityProfiles.update + - networksecurity.securityProfiles.use diff --git a/fast/stages/0-bootstrap/organization-iam.tf b/fast/stages/0-bootstrap/organization-iam.tf index b4fb4793a..bde1b2150 100644 --- a/fast/stages/0-bootstrap/organization-iam.tf +++ b/fast/stages/0-bootstrap/organization-iam.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/fast/stages/0-bootstrap/organization.tf b/fast/stages/0-bootstrap/organization.tf index da570da52..b7aa68d4a 100644 --- a/fast/stages/0-bootstrap/organization.tf +++ b/fast/stages/0-bootstrap/organization.tf @@ -163,23 +163,36 @@ module "organization" { # delegated role grant for resource manager service account iam_bindings = merge( { + organization_ngfw_enterprise_admin = { + members = [local.principals.gcp-network-admins] + role = module.organization.custom_role_id["ngfw_enterprise_admin"] + } organization_iam_admin_conditional = { members = [module.automation-tf-resman-sa.iam_email] role = module.organization.custom_role_id["organization_iam_admin"] condition = { - expression = format( - "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])", - join(",", formatlist("'%s'", [ - "roles/accesscontextmanager.policyAdmin", - "roles/cloudasset.viewer", - "roles/compute.orgFirewallPolicyAdmin", - "roles/compute.xpnAdmin", - "roles/orgpolicy.policyAdmin", - "roles/orgpolicy.policyViewer", - "roles/resourcemanager.organizationViewer", - module.organization.custom_role_id["service_project_network_admin"], - module.organization.custom_role_id["tenant_network_admin"], - ])) + expression = ( + format( + <<-EOT + api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s]) + || api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s]) + EOT + , join(",", formatlist("'%s'", [ + "roles/accesscontextmanager.policyAdmin", + "roles/cloudasset.viewer", + "roles/compute.orgFirewallPolicyAdmin", + "roles/compute.xpnAdmin", + "roles/orgpolicy.policyAdmin", + "roles/orgpolicy.policyViewer", + "roles/resourcemanager.organizationViewer" + ])) + , join(",", formatlist("'%s'", [ + module.organization.custom_role_id["network_firewall_policies_admin"], + module.organization.custom_role_id["ngfw_enterprise_admin"], + module.organization.custom_role_id["service_project_network_admin"], + module.organization.custom_role_id["tenant_network_admin"] + ])) + ) ) title = "automation_sa_delegated_grants" description = "Automation service account delegated grants." diff --git a/fast/stages/1-resman/README.md b/fast/stages/1-resman/README.md index cfe1ffcf6..7159e6f7d 100644 --- a/fast/stages/1-resman/README.md +++ b/fast/stages/1-resman/README.md @@ -236,6 +236,7 @@ A full reference of IAM roles managed by this stage [is available here](./IAM.md | [branch-data-platform.tf](./branch-data-platform.tf) | Data Platform stages resources. | folder · gcs · iam-service-account | | | [branch-gcve.tf](./branch-gcve.tf) | GCVE stage resources. | folder · gcs · iam-service-account | | | [branch-gke.tf](./branch-gke.tf) | GKE multitenant stage resources. | folder · gcs · iam-service-account | | +| [branch-netsec.tf](./branch-netsec.tf) | Network security stage resources. | gcs · iam-service-account | | | [branch-networking.tf](./branch-networking.tf) | Networking stage resources. | folder · gcs · iam-service-account | | | [branch-project-factory.tf](./branch-project-factory.tf) | Project factory stage resources. | gcs · iam-service-account | | | [branch-sandbox.tf](./branch-sandbox.tf) | Sandbox stage resources. | folder · gcs · iam-service-account | | @@ -244,6 +245,7 @@ A full reference of IAM roles managed by this stage [is available here](./IAM.md | [cicd-data-platform.tf](./cicd-data-platform.tf) | CI/CD resources for the data platform branch. | iam-service-account | | | [cicd-gcve.tf](./cicd-gcve.tf) | CI/CD resources for the GCVE branch. | iam-service-account | | | [cicd-gke.tf](./cicd-gke.tf) | CI/CD resources for the GKE multitenant branch. | iam-service-account | | +| [cicd-netsec.tf](./cicd-netsec.tf) | CI/CD resources for the networking branch. | iam-service-account | | | [cicd-networking.tf](./cicd-networking.tf) | CI/CD resources for the networking branch. | iam-service-account | | | [cicd-project-factory.tf](./cicd-project-factory.tf) | CI/CD resources for the project factories. | iam-service-account | | | [cicd-security.tf](./cicd-security.tf) | CI/CD resources for the security branch. | iam-service-account | | @@ -265,35 +267,35 @@ A full reference of IAM roles managed by this stage [is available here](./IAM.md |---|---|:---:|:---:|:---:|:---:| | [automation](variables-fast.tf#L19) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables-fast.tf#L42) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | object({…}) | ✓ | | 0-bootstrap | -| [logging](variables-fast.tf#L93) | Logging configuration for tenants. | object({…}) | ✓ | | 1-tenant-factory | -| [organization](variables-fast.tf#L106) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables-fast.tf#L124) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | -| [cicd_repositories](variables.tf#L20) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | -| [custom_roles](variables-fast.tf#L53) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | -| [factories_config](variables.tf#L116) | Configuration for the resource factories or external data. | object({…}) | | {} | | -| [fast_features](variables.tf#L127) | Selective control for top-level FAST features. | object({…}) | | {} | | -| [folder_iam](variables.tf#L140) | Authoritative IAM for top-level folders. | object({…}) | | {} | | -| [groups](variables-fast.tf#L65) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | 0-bootstrap | -| [locations](variables-fast.tf#L80) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | 0-bootstrap | -| [outputs_location](variables.tf#L154) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | -| [root_node](variables-fast.tf#L130) | Root node for the hierarchy, if running in tenant mode. | string | | null | 0-bootstrap | -| [tag_names](variables.tf#L160) | Customized names for resource management tags. | object({…}) | | {} | | -| [tags](variables.tf#L174) | Custom secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | -| [top_level_folders](variables.tf#L195) | Additional top-level folders. Keys are used for service account and bucket names, values implement the folders module interface with the addition of the 'automation' attribute. | map(object({…})) | | {} | | +| [logging](variables-fast.tf#L95) | Logging configuration for tenants. | object({…}) | ✓ | | 1-tenant-factory | +| [organization](variables-fast.tf#L108) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables-fast.tf#L126) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [cicd_repositories](variables.tf#L20) | CI/CD repository configuration. Identity providers reference keys in the `automation.federated_identity_providers` variable. Set to null to disable, or set individual repositories to null if not needed. | object({…}) | | null | | +| [custom_roles](variables-fast.tf#L53) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | +| [factories_config](variables.tf#L122) | Configuration for the resource factories or external data. | object({…}) | | {} | | +| [fast_features](variables.tf#L133) | Selective control for top-level FAST features. | object({…}) | | {} | | +| [folder_iam](variables.tf#L146) | Authoritative IAM for top-level folders. | object({…}) | | {} | | +| [groups](variables-fast.tf#L67) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | 0-bootstrap | +| [locations](variables-fast.tf#L82) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | 0-bootstrap | +| [outputs_location](variables.tf#L160) | Enable writing provider, tfvars and CI/CD workflow files to local filesystem. Leave null to disable. | string | | null | | +| [root_node](variables-fast.tf#L132) | Root node for the hierarchy, if running in tenant mode. | string | | null | 0-bootstrap | +| [tag_names](variables.tf#L166) | Customized names for resource management tags. | object({…}) | | {} | | +| [tags](variables.tf#L180) | Custom secure tags by key name. The `iam` attribute behaves like the similarly named one at module level. | map(object({…})) | | {} | | +| [top_level_folders](variables.tf#L201) | Additional top-level folders. Keys are used for service account and bucket names, values implement the folders module interface with the addition of the 'automation' attribute. | map(object({…})) | | {} | | ## Outputs | name | description | sensitive | consumers | |---|---|:---:|---| -| [cicd_repositories](outputs.tf#L377) | WIF configuration for CI/CD repositories. | | | -| [dataplatform](outputs.tf#L391) | Data for the Data Platform stage. | | | -| [folder_ids](outputs.tf#L407) | Folder ids. | | | -| [gcve](outputs.tf#L412) | Data for the GCVE stage. | | 03-gcve | -| [gke_multitenant](outputs.tf#L433) | Data for the GKE multitenant stage. | | 03-gke-multitenant | -| [networking](outputs.tf#L454) | Data for the networking stage. | | | -| [project_factories](outputs.tf#L463) | Data for the project factories stage. | | | -| [providers](outputs.tf#L482) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform | -| [sandbox](outputs.tf#L489) | Data for the sandbox stage. | | xx-sandbox | -| [security](outputs.tf#L503) | Data for the networking stage. | | 02-security | -| [tfvars](outputs.tf#L514) | Terraform variable files for the following stages. | ✓ | | +| [cicd_repositories](outputs.tf#L402) | WIF configuration for CI/CD repositories. | | | +| [dataplatform](outputs.tf#L416) | Data for the Data Platform stage. | | | +| [folder_ids](outputs.tf#L432) | Folder ids. | | | +| [gcve](outputs.tf#L437) | Data for the GCVE stage. | | 03-gcve | +| [gke_multitenant](outputs.tf#L458) | Data for the GKE multitenant stage. | | 03-gke-multitenant | +| [networking](outputs.tf#L479) | Data for the networking stage. | | | +| [project_factories](outputs.tf#L488) | Data for the project factories stage. | | | +| [providers](outputs.tf#L507) | Terraform provider files for this stage and dependent stages. | ✓ | 02-networking · 02-security · 03-dataplatform · 03-netsec | +| [sandbox](outputs.tf#L514) | Data for the sandbox stage. | | xx-sandbox | +| [security](outputs.tf#L528) | Data for the networking stage. | | 02-security | +| [tfvars](outputs.tf#L539) | Terraform variable files for the following stages. | ✓ | | diff --git a/fast/stages/1-resman/branch-netsec.tf b/fast/stages/1-resman/branch-netsec.tf new file mode 100644 index 000000000..3d941e1f6 --- /dev/null +++ b/fast/stages/1-resman/branch-netsec.tf @@ -0,0 +1,76 @@ +/** + * Copyright 2024 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 Network security stage resources. + +# automation service account + +module "branch-netsec-sa" { + source = "../../../modules/iam-service-account" + project_id = var.automation.project_id + name = "prod-resman-netsec-0" + display_name = "Terraform resman network security service account." + prefix = var.prefix + service_account_create = var.root_node == null + iam = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.branch-netsec-sa-cicd[0].iam_email, null) + ]) + } + iam_project_roles = { + (var.automation.project_id) = ["roles/serviceusage.serviceUsageConsumer"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectAdmin"] + } +} + +# automation read-only service account + +module "branch-netsec-r-sa" { + source = "../../../modules/iam-service-account" + project_id = var.automation.project_id + name = "prod-resman-netsec-0r" + display_name = "Terraform resman network security service account (read-only)." + prefix = var.prefix + iam = { + "roles/iam.serviceAccountTokenCreator" = compact([ + try(module.branch-netsec-r-sa-cicd[0].iam_email, null) + ]) + } + iam_project_roles = { + (var.automation.project_id) = ["roles/serviceusage.serviceUsageConsumer"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = [var.custom_roles["storage_viewer"]] + } +} + +# automation bucket + +module "branch-netsec-gcs" { + source = "../../../modules/gcs" + project_id = var.automation.project_id + name = "prod-resman-netsec-0" + prefix = var.prefix + location = var.locations.gcs + storage_class = local.gcs_storage_class + versioning = true + iam = { + "roles/storage.objectAdmin" = [module.branch-netsec-sa.iam_email] + "roles/storage.objectViewer" = [module.branch-netsec-r-sa.iam_email] + } +} diff --git a/fast/stages/1-resman/branch-networking.tf b/fast/stages/1-resman/branch-networking.tf index 285e0f500..cc6cac29c 100644 --- a/fast/stages/1-resman/branch-networking.tf +++ b/fast/stages/1-resman/branch-networking.tf @@ -28,6 +28,9 @@ locals { # read-only (plan) automation service account "roles/viewer" = [module.branch-network-r-sa.iam_email] "roles/resourcemanager.folderViewer" = [module.branch-network-r-sa.iam_email] + # netsec service account + "roles/serviceusage.serviceUsageAdmin" = [module.branch-netsec-sa.iam_email] + (var.custom_roles["network_firewall_policies_admin"]) = [module.branch-netsec-sa.iam_email] } # deep-merge FAST-specific IAM with user-provided bindings in var.folder_iam _network_folder_iam = merge( diff --git a/fast/stages/1-resman/cicd-netsec.tf b/fast/stages/1-resman/cicd-netsec.tf new file mode 100644 index 000000000..42810bd94 --- /dev/null +++ b/fast/stages/1-resman/cicd-netsec.tf @@ -0,0 +1,85 @@ +/** + * Copyright 2024 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 the networking branch. + +# read-write (apply) SA used by CI/CD workflows +# to impersonate netsec automation SA + +module "branch-netsec-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.netsec.name, null) != null + ? { 0 = local.cicd_repositories.netsec } + : {} + ) + project_id = var.automation.project_id + name = "prod-resman-netsec-1" + display_name = "Terraform CI/CD stage 2 network security service account." + prefix = var.prefix + iam = { + "roles/iam.workloadIdentityUser" = [ + each.value.branch == null + ? format( + local.identity_providers[each.value.identity_provider].principal_repo, + var.automation.federated_identity_pool, + each.value.name + ) + : format( + local.identity_providers[each.value.identity_provider].principal_branch, + var.automation.federated_identity_pool, + each.value.name, + each.value.branch + ) + ] + } + iam_project_roles = { + (var.automation.project_id) = ["roles/logging.logWriter"] + } + iam_storage_roles = { + (var.automation.outputs_bucket) = ["roles/storage.objectViewer"] + } +} + +# read-only (plan) SA used by CI/CD workflows to impersonate netsec automation SA + +module "branch-netsec-r-sa-cicd" { + source = "../../../modules/iam-service-account" + for_each = ( + try(local.cicd_repositories.netsec.name, null) != null + ? { 0 = local.cicd_repositories.netsec } + : {} + ) + project_id = var.automation.project_id + name = "prod-resman-netsec-1r" + display_name = "Terraform CI/CD stage 2 network security service account (read-only)." + prefix = var.prefix + iam = { + "roles/iam.workloadIdentityUser" = [ + format( + local.identity_providers[each.value.identity_provider].principal_repo, + var.automation.federated_identity_pool, + each.value.name + ) + ] + } + 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/iam.tf b/fast/stages/1-resman/iam.tf index 0a60182aa..2b54e4854 100644 --- a/fast/stages/1-resman/iam.tf +++ b/fast/stages/1-resman/iam.tf @@ -24,6 +24,14 @@ locals { member = module.branch-network-sa.iam_email role = "roles/compute.orgFirewallPolicyAdmin" } + sa_net_netsec_fw_policy_admin = { + member = module.branch-netsec-sa.iam_email + role = "roles/compute.orgFirewallPolicyAdmin" + } + sa_net_netsec_ngfw_enterprise_admin = { + member = module.branch-netsec-sa.iam_email + role = local.custom_roles["ngfw_enterprise_admin"], + } sa_net_xpn_admin = { member = module.branch-network-sa.iam_email role = "roles/compute.xpnAdmin" diff --git a/fast/stages/1-resman/outputs.tf b/fast/stages/1-resman/outputs.tf index 9cd19b489..975cbbde9 100644 --- a/fast/stages/1-resman/outputs.tf +++ b/fast/stages/1-resman/outputs.tf @@ -83,6 +83,17 @@ locals { } tf_var_files = local.cicd_workflow_var_files.stage_3 } + netsec = { + service_accounts = { + apply = try(module.branch-netsec-sa-cicd[0].email, null) + plan = try(module.branch-netsec-r-sa-cicd[0].email, null) + } + tf_providers_files = { + apply = "3-netsec-providers.tf" + plan = "3-netsec-r-providers.tf" + } + tf_var_files = local.cicd_workflow_var_files.stage_3 + } networking = { service_accounts = { apply = try(module.branch-network-sa-cicd[0].email, null) @@ -198,6 +209,18 @@ locals { name = "security" sa = module.branch-security-r-sa.email }) + "3-netsec" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-netsec-gcs.name + name = "netsec" + sa = module.branch-netsec-sa.email + }) + "3-netsec-r" = templatefile(local._tpl_providers, { + backend_extra = null + bucket = module.branch-network-gcs.name + name = "netsec" + sa = module.branch-netsec-r-sa.email + }) }, { for k, v in module.top-level-sa : @@ -347,6 +370,8 @@ locals { gke-dev-r = try(module.branch-gke-dev-r-sa[0].email, null) gke-prod = try(module.branch-gke-prod-sa[0].email, null) gke-prod-r = try(module.branch-gke-prod-r-sa[0].email, null) + netsec = module.branch-netsec-sa.email + netsec-r = module.branch-netsec-r-sa.email networking = module.branch-network-sa.email networking-r = module.branch-network-r-sa.email project-factory = try(module.branch-pf-sa[0].email, null) @@ -480,7 +505,7 @@ output "project_factories" { # ready to use provider configurations for subsequent stages output "providers" { - # tfdoc:output:consumers 02-networking 02-security 03-dataplatform + # tfdoc:output:consumers 02-networking 02-security 03-dataplatform 03-netsec description = "Terraform provider files for this stage and dependent stages." sensitive = true value = local.providers diff --git a/fast/stages/1-resman/templates/providers.tf.tpl b/fast/stages/1-resman/templates/providers.tf.tpl index d1c224c5c..817403cee 100644 --- a/fast/stages/1-resman/templates/providers.tf.tpl +++ b/fast/stages/1-resman/templates/providers.tf.tpl @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/fast/stages/1-resman/variables-fast.tf b/fast/stages/1-resman/variables-fast.tf index cd2b76fb9..c6b833362 100644 --- a/fast/stages/1-resman/variables-fast.tf +++ b/fast/stages/1-resman/variables-fast.tf @@ -54,10 +54,12 @@ variable "custom_roles" { # tfdoc:variable:source 0-bootstrap description = "Custom roles defined at the org level, in key => id format." type = object({ - gcve_network_admin = string - organization_admin_viewer = string - service_project_network_admin = string - storage_viewer = string + gcve_network_admin = string + network_firewall_policies_admin = string + ngfw_enterprise_admin = string + organization_admin_viewer = string + service_project_network_admin = string + storage_viewer = string }) default = null } diff --git a/fast/stages/1-resman/variables.tf b/fast/stages/1-resman/variables.tf index c7a4ff749..f5d8bf90f 100644 --- a/fast/stages/1-resman/variables.tf +++ b/fast/stages/1-resman/variables.tf @@ -56,6 +56,12 @@ variable "cicd_repositories" { branch = optional(string) identity_provider = optional(string) })) + netsec = optional(object({ + name = string + type = string + branch = optional(string) + identity_provider = optional(string) + })) networking = optional(object({ name = string type = string diff --git a/fast/stages/1-tenant-factory/README.md b/fast/stages/1-tenant-factory/README.md index 2cad091e1..2716f2f03 100644 --- a/fast/stages/1-tenant-factory/README.md +++ b/fast/stages/1-tenant-factory/README.md @@ -309,13 +309,13 @@ gcloud storage cp gs://{prefix}-{tenant-shortname}-prod-iac-core-0/tfvars/0-boot |---|---|:---:|:---:|:---:|:---:| | [automation](variables-fast.tf#L19) | Automation resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | | [billing_account](variables-fast.tf#L42) | Billing account id. If billing account is not part of the same org set `is_org_level` to `false`. To disable handling of billing IAM roles set `no_iam` to `true`. | object({…}) | ✓ | | 0-bootstrap | -| [logging](variables-fast.tf#L94) | Logging resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | -| [org_policy_tags](variables-fast.tf#L113) | Organization policy tags. | object({…}) | ✓ | | 0-bootstrap | -| [organization](variables-fast.tf#L103) | Organization details. | object({…}) | ✓ | | 0-bootstrap | -| [prefix](variables-fast.tf#L130) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | -| [custom_roles](variables-fast.tf#L53) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | -| [groups](variables-fast.tf#L66) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | 0-bootstrap | -| [locations](variables-fast.tf#L81) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | 0-bootstrap | +| [logging](variables-fast.tf#L96) | Logging resources created by the bootstrap stage. | object({…}) | ✓ | | 0-bootstrap | +| [org_policy_tags](variables-fast.tf#L115) | Organization policy tags. | object({…}) | ✓ | | 0-bootstrap | +| [organization](variables-fast.tf#L105) | Organization details. | object({…}) | ✓ | | 0-bootstrap | +| [prefix](variables-fast.tf#L132) | Prefix used for resources that need unique names. Use 9 characters or less. | string | ✓ | | 0-bootstrap | +| [custom_roles](variables-fast.tf#L53) | Custom roles defined at the org level, in key => id format. | object({…}) | | null | 0-bootstrap | +| [groups](variables-fast.tf#L68) | Group names or IAM-format principals to grant organization-level permissions. If just the name is provided, the 'group:' principal and organization domain are interpolated. | object({…}) | | {} | 0-bootstrap | +| [locations](variables-fast.tf#L83) | Optional locations for GCS, BigQuery, and logging buckets created here. | object({…}) | | {} | 0-bootstrap | | [outputs_location](variables.tf#L17) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | | | [root_node](variables.tf#L23) | Root folder under which tenants are created, in folders/nnnn format. Defaults to the organization if null. | string | | null | | | [tag_names](variables.tf#L36) | Customized names for resource management tags. | object({…}) | | {} | | diff --git a/fast/stages/1-tenant-factory/variables-fast.tf b/fast/stages/1-tenant-factory/variables-fast.tf index ca67b036e..b7eb168cf 100644 --- a/fast/stages/1-tenant-factory/variables-fast.tf +++ b/fast/stages/1-tenant-factory/variables-fast.tf @@ -54,11 +54,13 @@ variable "custom_roles" { # tfdoc:variable:source 0-bootstrap description = "Custom roles defined at the org level, in key => id format." type = object({ - gcve_network_admin = string - organization_admin_viewer = string - service_project_network_admin = string - storage_viewer = string - tenant_network_admin = string + gcve_network_admin = string + network_firewall_policies_admin = string + ngfw_enterprise_admin = string + organization_admin_viewer = string + service_project_network_admin = string + storage_viewer = string + tenant_network_admin = string }) default = null } diff --git a/fast/stages/2-networking-a-simple/net-dev.tf b/fast/stages/2-networking-a-simple/net-dev.tf index 561d95943..f48917ae7 100644 --- a/fast/stages/2-networking-a-simple/net-dev.tf +++ b/fast/stages/2-networking-a-simple/net-dev.tf @@ -29,6 +29,7 @@ module "dev-spoke-project" { "dns.googleapis.com", "iap.googleapis.com", "networkmanagement.googleapis.com", + "networksecurity.googleapis.com", "servicenetworking.googleapis.com", "stackdriver.googleapis.com", "vpcaccess.googleapis.com" diff --git a/fast/stages/2-networking-a-simple/net-prod.tf b/fast/stages/2-networking-a-simple/net-prod.tf index 4b8f260a4..f2e52abe5 100644 --- a/fast/stages/2-networking-a-simple/net-prod.tf +++ b/fast/stages/2-networking-a-simple/net-prod.tf @@ -29,6 +29,7 @@ module "prod-spoke-project" { "dns.googleapis.com", "iap.googleapis.com", "networkmanagement.googleapis.com", + "networksecurity.googleapis.com", "servicenetworking.googleapis.com", "stackdriver.googleapis.com", "vpcaccess.googleapis.com" diff --git a/fast/stages/2-networking-b-nva/net-dev.tf b/fast/stages/2-networking-b-nva/net-dev.tf index 11578eb52..7e1b7e38d 100644 --- a/fast/stages/2-networking-b-nva/net-dev.tf +++ b/fast/stages/2-networking-b-nva/net-dev.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ module "dev-spoke-project" { "dns.googleapis.com", "iap.googleapis.com", "networkmanagement.googleapis.com", + "networksecurity.googleapis.com", "servicenetworking.googleapis.com", "stackdriver.googleapis.com", "vpcaccess.googleapis.com" diff --git a/fast/stages/2-networking-b-nva/net-prod.tf b/fast/stages/2-networking-b-nva/net-prod.tf index 43e4eff0c..3d2447647 100644 --- a/fast/stages/2-networking-b-nva/net-prod.tf +++ b/fast/stages/2-networking-b-nva/net-prod.tf @@ -1,5 +1,5 @@ /** - * Copyright 2023 Google LLC + * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ module "prod-spoke-project" { "dns.googleapis.com", "iap.googleapis.com", "networkmanagement.googleapis.com", + "networksecurity.googleapis.com", "servicenetworking.googleapis.com", "stackdriver.googleapis.com", "vpcaccess.googleapis.com" diff --git a/fast/stages/2-networking-c-separate-envs/net-dev.tf b/fast/stages/2-networking-c-separate-envs/net-dev.tf index 0ba612055..3ee211ba7 100644 --- a/fast/stages/2-networking-c-separate-envs/net-dev.tf +++ b/fast/stages/2-networking-c-separate-envs/net-dev.tf @@ -28,6 +28,7 @@ module "dev-spoke-project" { "dns.googleapis.com", "iap.googleapis.com", "networkmanagement.googleapis.com", + "networksecurity.googleapis.com", "servicenetworking.googleapis.com", "stackdriver.googleapis.com", "vpcaccess.googleapis.com" diff --git a/fast/stages/2-networking-c-separate-envs/net-prod.tf b/fast/stages/2-networking-c-separate-envs/net-prod.tf index ba8860e7d..1f3432cd4 100644 --- a/fast/stages/2-networking-c-separate-envs/net-prod.tf +++ b/fast/stages/2-networking-c-separate-envs/net-prod.tf @@ -28,6 +28,7 @@ module "prod-spoke-project" { "dns.googleapis.com", "iap.googleapis.com", "networkmanagement.googleapis.com", + "networksecurity.googleapis.com", "servicenetworking.googleapis.com", "stackdriver.googleapis.com", "vpcaccess.googleapis.com" diff --git a/fast/stages/3-network-security/README.md b/fast/stages/3-network-security/README.md new file mode 100644 index 000000000..9e5cf2ad2 --- /dev/null +++ b/fast/stages/3-network-security/README.md @@ -0,0 +1,172 @@ +# Network Security + +This stage enables NGFW Enterprise in the dev `dev` and `prod` VPCs. This includes: + +- security profiles +- security profile groups +- NGFW endpoints +- NGFW endpoint associations +- global network firewall policies and some recommended firewall policy rules + +The following diagram is a high level reference of the resources created and managed here (excludes projects and VPCs): + +

+ Network security NGFW diagram +

+ + +- [Design overview and choices](#design-overview-and-choices) +- [How to run this stage](#how-to-run-this-stage) + - [Provider and Terraform variables](#provider-and-terraform-variables) + - [Impersonating the automation service account](#impersonating-the-automation-service-account) + - [Variable configuration](#variable-configuration) + - [Running the stage](#running-the-stage) +- [Customizations](#customizations) + - [Firewall policy rules factories](#firewall-policy-rules-factories) + - [NGFW Enterprise configuration](#ngfw-enterprise-configuration) +- [Files](#files) +- [Variables](#variables) +- [Outputs](#outputs) + + +## Design overview and choices + +- We create one security profile (and security profile group) per environment in the spoke VPCs only. That's usually where inspection is needed, as it's where workloads run. +- By default, we create NGFW Enterprise endpoints in three zones in the default, primary region (europe-west1). You can adapt this, depending on where your workloads run, using the dedicated variable. +- We install default firewall policy rules in each spoke, so that we allow and inspect all traffic going to the Internet and we allow egress towards RFC-1918 addresses. In ingress, you'll need to add your own rules. We provided some examples that need to be adapted to your topology (number of regions, subnets). +- We use global network firewall policies, as legacy VPC firewall rules are not compatible with NGFW Enterprise. These policies coexist with the legacy VPC firewall rules that we create in the netwroking stage. +- For your convenience, firewall policy rules leverage factories, so that you can define firewall policy rules using yaml files. The path of these files is configurable. Look in the [Customization](#customizations) section for more details. +- NGFW Enterprise endpoints are org-level resources that need to reference a quota project for billing purposes. By default, we create a dedicated `xxx-net-ngfw-0` quota project. Anyway, you can choose to leverage an existing project. Look in the [Customization](#customizations) section for more details. + +## How to run this stage + +This stage is meant to be executed after any [networking](../2-networking-a-simple) stage has run and it leverages dedicated automation service accounts and a bucket created in the [resman](../1-resman) stage. + +It's to run this stage in isolation, but that's outside the scope of this document, and you would need to refer to the code for the bootstrap and resman stages for the roles needed. + +Before running this stage, you need to make sure you have the correct credentials and permissions, and localize variables by assigning values that match your configuration. + +### Provider and Terraform variables + +As all other FAST stages, the [mechanism used to pass variable values and pre-built provider files from one stage to the next](../0-bootstrap/README.md#output-files-and-cross-stage-variables) is also leveraged here. + +The commands to link or copy the provider and terraform variable files can be easily derived from the `stage-links.sh` script in the FAST root folder, passing it a single argument with the local output files folder (if configured) or the GCS output bucket in the automation project (derived from stage 0 outputs). The following examples demonstrate both cases, and the resulting commands that then need to be copy/pasted and run. + +```bash +../../stage-links.sh ~/fast-config + +# copy and paste the following commands for '3-network-security' + +ln -s ~/fast-config/providers/3-netsec-providers.tf ./ +ln -s ~/fast-config/tfvars/0-globals.auto.tfvars.json ./ +ln -s ~/fast-config/tfvars/0-bootstrap.auto.tfvars.json ./ +ln -s ~/fast-config/tfvars/1-resman.auto.tfvars.json ./ +ln -s ~/fast-config/tfvars/2-networking.auto.tfvars.json ./ +``` + +```bash +../../stage-links.sh gs://xxx-prod-iac-core-outputs-0 + +# copy and paste the following commands for '3-network-security' + +gcloud storage cp gs://xxx-prod-iac-core-outputs-0/providers/3-netsec-providers.tf ./ +gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/0-globals.auto.tfvars.json ./ +gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/0-bootstrap.auto.tfvars.json ./ +gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/1-resman.auto.tfvars.json ./ +gcloud storage cp gs://xxx-prod-iac-core-outputs-0/tfvars/2-networking.auto.tfvars.json ./ +``` + +### Impersonating the automation service account + +The preconfigured provider file uses impersonation to run with this stage's automation service account's credentials. The `gcp-devops` and `organization-admins` groups have the necessary IAM bindings in place to do that, so make sure the current user is a member of one of those groups. + +### Variable configuration + +Variables in this stage -- like most other FAST stages -- are broadly divided into three separate sets: + +- variables which refer to global values for the whole organization (org id, billing account id, prefix, etc.), which are pre-populated via the `0-globals.auto.tfvars.json` file linked or copied above +- variables which refer to resources managed by previous stages, which are prepopulated here via the `0-bootstrap.auto.tfvars.json`, `1-resman.auto.tfvars.json` and `2-networking.auto.tfvars.json` files linked or copied above +- and finally variables that optionally control this stage's behaviour and customizations, and can to be set in a custom `terraform.tfvars` file + +The latter set is explained in the [Customization](#customizations) sections below, and the full list can be found in the [Variables](#variables) table at the bottom of this document. + +Note that the `outputs_location` variable is disabled by default, you need to explicitly set it in your `terraform.tfvars` file if you want output files to be generated by this stage. This is a sample `terraform.tfvars` that configures it, refer to the [bootstrap stage documentation](../0-bootstrap/README.md#output-files-and-cross-stage-variables) for more details: + +```tfvars +outputs_location = "~/fast-config" +``` + +### Running the stage + +Once provider and variable values are in place and the correct user is configured, the stage can be run: + +```bash +terraform init +terraform apply +``` + +## Customizations + +You can optionally customize a few options adding a `terraform.tfvars` file to this stage. + +### Firewall policy rules factories + +By default, firewall policy rules yaml files are contained in the `data` folder within this module. Anyway, you can customize this location. + +### NGFW Enterprise configuration + +You can decide the zones where to deploy the NGFW Enterprise endpoints. These are set by default to `europe-west1-b`, `europe-west1-c` and `europe-west1-d`. + +```tfvars +ngfw_enterprise_config = { + endpoint_zones = [ + "us-east4-a", + "us-east4-b", + "australia-southeast1-b", + "australia-southeast1-c" + ] +} +``` + +Instead of creating a dedicated NGFW Enterprise billing/quota project, you can choose to leverage an existing project. These can even be one of your existing networking projects. +You'll need to make sure your network security service account can activate the `networksecurity.googleapis.com` on that project (for example, assigning the `roles/serviceusage.serviceUsageAdmin` role). + +```tfvars +ngfw_enterprise_config = { + quota_project_id = "your-quota-project-id" +} +``` + + + +## Files + +| name | description | modules | resources | +|---|---|---|---| +| [main.tf](./main.tf) | Next-Generation Firewall Enterprise configuration. | project | google_network_security_firewall_endpoint | +| [net-dev.tf](./net-dev.tf) | Security components for dev spoke VPC. | net-firewall-policy | google_network_security_firewall_endpoint_association · google_network_security_security_profile · google_network_security_security_profile_group | +| [net-prod.tf](./net-prod.tf) | Security components for prod spoke VPC. | net-firewall-policy | google_network_security_firewall_endpoint_association · google_network_security_security_profile · google_network_security_security_profile_group | +| [outputs.tf](./outputs.tf) | Module outputs. | | | +| [variables-fast.tf](./variables-fast.tf) | None | | | +| [variables.tf](./variables.tf) | Module variables. | | | + +## Variables + +| name | description | type | required | default | producer | +|---|---|:---:|:---:|:---:|:---:| +| [billing_account](variables-fast.tf#L17) | Billing account id. If billing account is not part of the same org set `is_org_level` to false. | object({…}) | ✓ | | 0-bootstrap | +| [folder_ids](variables-fast.tf#L30) | Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | object({…}) | ✓ | | 1-resman | +| [organization](variables-fast.tf#L52) | Organization details. | object({…}) | ✓ | | 00-globals | +| [prefix](variables-fast.tf#L62) | Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants. | string | ✓ | | 0-bootstrap | +| [vpc_self_links](variables-fast.tf#L72) | Self link for the shared VPC. | object({…}) | ✓ | | 2-networking | +| [factories_config](variables.tf#L17) | Configuration for network resource factories. | object({…}) | | {…} | | +| [host_project_ids](variables-fast.tf#L41) | Host project for the shared VPC. | object({…}) | | {} | 2-networking | +| [ngfw_enterprise_config](variables.tf#L35) | NGFW Enterprise configuration. | object({…}) | | {…} | | + +## Outputs + +| name | description | sensitive | consumers | +|---|---|:---:|---| +| [ngfw_enterprise_endpoint_ids](outputs.tf#L17) | The NGFW Enterprise endpoint ids. | | | +| [ngfw_enterprise_endpoints_quota_project](outputs.tf#L25) | The NGFW Enterprise endpoints quota project. | | | + diff --git a/fast/stages/3-network-security/data/cidrs.yaml b/fast/stages/3-network-security/data/cidrs.yaml new file mode 100644 index 000000000..3591e95a0 --- /dev/null +++ b/fast/stages/3-network-security/data/cidrs.yaml @@ -0,0 +1,18 @@ +# skip boilerplate check +--- +# Terraform will be unable to decode this file if it does not contain valid YAML +# You can retain `---` (start of the document) to indicate an empty document. + +healthchecks: + - 35.191.0.0/16 + - 130.211.0.0/22 + - 209.85.152.0/22 + - 209.85.204.0/22 + +rfc1918: + - 10.0.0.0/8 + - 172.16.0.0/12 + - 192.168.0.0/16 + +onprem_probes: + - 10.255.255.254/32 diff --git a/fast/stages/3-network-security/data/firewall-policy-rules/dev/egress.yaml b/fast/stages/3-network-security/data/firewall-policy-rules/dev/egress.yaml new file mode 100644 index 000000000..a6edadf1c --- /dev/null +++ b/fast/stages/3-network-security/data/firewall-policy-rules/dev/egress.yaml @@ -0,0 +1,18 @@ +# skip boilerplate check +--- +egress-allow-rfc1918: + description: "Allow all hosts to RFC-1918" + priority: 2147483546 + match: + destination_ranges: + - rfc1918 + action: "allow" + +egress-inspect-internet: + description: "Inspect egress traffic from all dev hosts to Internet" + priority: 2147483547 + match: + destination_ranges: + - "0.0.0.0/0" + action: "apply_security_profile_group" + security_profile_group: "dev" diff --git a/fast/stages/3-network-security/data/firewall-policy-rules/dev/ingress.yaml b/fast/stages/3-network-security/data/firewall-policy-rules/dev/ingress.yaml new file mode 100644 index 000000000..272431fa9 --- /dev/null +++ b/fast/stages/3-network-security/data/firewall-policy-rules/dev/ingress.yaml @@ -0,0 +1,21 @@ +# skip boilerplate check +--- +# Following are some NGFW Enterprise ingress rules examples + +# ingress-allow-inspect-cross: +# description: "Allow and inspect cross-env traffic from prod." +# priority: 1 +# match: +# source_ranges: +# - prod (to be defined) +# action: "apply_security_profile_group" +# security_profile_group: "dev" + +# ingress-allow-inspect-intra: +# description: "Allow and inspect same-env (intra-vpc) traffic." +# priority: 2 +# match: +# source_ranges: +# - dev (to be defined) +# action: "apply_security_profile_group" +# security_profile_group: "dev" diff --git a/fast/stages/3-network-security/data/firewall-policy-rules/prod/egress.yaml b/fast/stages/3-network-security/data/firewall-policy-rules/prod/egress.yaml new file mode 100644 index 000000000..397acebe2 --- /dev/null +++ b/fast/stages/3-network-security/data/firewall-policy-rules/prod/egress.yaml @@ -0,0 +1,18 @@ +# skip boilerplate check +--- +egress-allow-rfc1918: + description: "Allow all hosts to RFC-1918" + priority: 2147483546 + match: + destination_ranges: + - rfc1918 + action: "allow" + +egress-inspect-internet: + description: "Inspect egress traffic from all prod hosts to Internet" + priority: 2147483547 + match: + destination_ranges: + - "0.0.0.0/0" + action: "apply_security_profile_group" + security_profile_group: "prod" diff --git a/fast/stages/3-network-security/data/firewall-policy-rules/prod/ingress.yaml b/fast/stages/3-network-security/data/firewall-policy-rules/prod/ingress.yaml new file mode 100644 index 000000000..e0aa293c5 --- /dev/null +++ b/fast/stages/3-network-security/data/firewall-policy-rules/prod/ingress.yaml @@ -0,0 +1,21 @@ +# skip boilerplate check +--- +# Following are some NGFW Enterprise ingress rules examples + +# ingress-allow-inspect-cross: +# description: "Allow and inspect cross-env traffic." +# priority: 1 +# match: +# source_ranges: +# - dev (to be defined) +# action: "apply_security_profile_group" +# security_profile_group: "prod" + +# ingress-allow-inspect-intra: +# description: "Allow and inspect intra-VPC traffic." +# priority: 2 +# match: +# source_ranges: +# - prod (to be defined) +# action: "apply_security_profile_group" +# security_profile_group: "prod" diff --git a/fast/stages/3-network-security/diagram.png b/fast/stages/3-network-security/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..960cbff204d70030582497791bc6a790ca2bd5f2 GIT binary patch literal 73343 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 \ No newline at end of file diff --git a/fast/stages/3-network-security/main.tf b/fast/stages/3-network-security/main.tf new file mode 100644 index 000000000..946dba43f --- /dev/null +++ b/fast/stages/3-network-security/main.tf @@ -0,0 +1,68 @@ +/** + * Copyright 2024 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 Next-Generation Firewall Enterprise configuration. + +locals { + create_quota_project = ( + var.ngfw_enterprise_config.quota_project_id == null + ? true + : false + ) + vpc_ids = { + for k, v in var.vpc_self_links + : k => replace(v, "https://www.googleapis.com/compute/v1/", "") + } +} + +# Dedicated quota project for ngfw enterprise endpoints +module "ngfw-quota-project" { + source = "../../../modules/project" + name = ( + local.create_quota_project + ? "net-ngfw-0" + : var.ngfw_enterprise_config.quota_project_id + ) + billing_account = ( + local.create_quota_project + ? var.billing_account.id + : null + ) + parent = ( + local.create_quota_project + ? var.folder_ids.networking + : null + ) + prefix = ( + local.create_quota_project + ? var.prefix + : null + ) + project_create = ( + local.create_quota_project + ? true + : false + ) + services = ["networksecurity.googleapis.com"] +} + +resource "google_network_security_firewall_endpoint" "firewall_endpoint" { + for_each = toset(var.ngfw_enterprise_config.endpoint_zones) + name = "${var.prefix}-ngfw-endpoint-${each.key}" + parent = "organizations/${var.organization.id}" + location = each.value + billing_project_id = module.ngfw-quota-project.id +} diff --git a/fast/stages/3-network-security/net-dev.tf b/fast/stages/3-network-security/net-dev.tf new file mode 100644 index 000000000..408a4e4ec --- /dev/null +++ b/fast/stages/3-network-security/net-dev.tf @@ -0,0 +1,59 @@ +/** + * Copyright 2024 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 Security components for dev spoke VPC. + +resource "google_network_security_security_profile" "dev_sec_profile" { + name = "${var.prefix}-dev-sp-0" + type = "THREAT_PREVENTION" + parent = "organizations/${var.organization.id}" + location = "global" +} + +resource "google_network_security_security_profile_group" "dev_sec_profile_group" { + name = "${var.prefix}-dev-spg-0" + parent = "organizations/${var.organization.id}" + location = "global" + description = "Dev security profile group." + threat_prevention_profile = try(google_network_security_security_profile.dev_sec_profile.id, null) +} + +resource "google_network_security_firewall_endpoint_association" "dev_fw_ep_association" { + for_each = toset(var.ngfw_enterprise_config.endpoint_zones) + name = "${var.prefix}-dev-epa-${each.key}" + parent = "projects/${try(var.host_project_ids.dev-spoke-0, null)}" + location = each.value + firewall_endpoint = google_network_security_firewall_endpoint.firewall_endpoint[each.key].id + network = try(local.vpc_ids.dev-spoke-0, null) +} + +module "dev-spoke-firewall-policy" { + source = "../../../modules/net-firewall-policy" + name = "${var.prefix}-dev-fw-policy" + parent_id = try(var.host_project_ids.dev-spoke-0, null) + region = "global" + security_profile_group_ids = { + dev = "//networksecurity.googleapis.com/${try(google_network_security_security_profile_group.dev_sec_profile_group.id, "")}" + } + attachments = { + dev-spoke = try(var.vpc_self_links.dev-spoke-0, null) + } + factories_config = { + cidr_file_path = var.factories_config.cidrs + egress_rules_file_path = "${var.factories_config.firewall_policy_rules.dev}/egress.yaml" + ingress_rules_file_path = "${var.factories_config.firewall_policy_rules.dev}/ingress.yaml" + } +} diff --git a/fast/stages/3-network-security/net-prod.tf b/fast/stages/3-network-security/net-prod.tf new file mode 100644 index 000000000..bffdf133c --- /dev/null +++ b/fast/stages/3-network-security/net-prod.tf @@ -0,0 +1,59 @@ +/** + * Copyright 2024 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 Security components for prod spoke VPC. + +resource "google_network_security_security_profile" "prod_sec_profile" { + name = "${var.prefix}-prod-sp-0" + type = "THREAT_PREVENTION" + parent = "organizations/${var.organization.id}" + location = "global" +} + +resource "google_network_security_security_profile_group" "prod_sec_profile_group" { + name = "${var.prefix}-prod-spg-0" + parent = "organizations/${var.organization.id}" + location = "global" + description = "prod security profile group." + threat_prevention_profile = try(google_network_security_security_profile.prod_sec_profile.id, null) +} + +resource "google_network_security_firewall_endpoint_association" "prod_fw_ep_association" { + for_each = toset(var.ngfw_enterprise_config.endpoint_zones) + name = "${var.prefix}-prod-epa-${each.key}" + parent = "projects/${try(var.host_project_ids.prod-spoke-0, null)}" + location = each.value + firewall_endpoint = google_network_security_firewall_endpoint.firewall_endpoint[each.key].id + network = try(local.vpc_ids.prod-spoke-0, null) +} + +module "prod-spoke-firewall-policy" { + source = "../../../modules/net-firewall-policy" + name = "${var.prefix}-prod-fw-policy" + parent_id = try(var.host_project_ids.prod-spoke-0, null) + region = "global" + security_profile_group_ids = { + prod = "//networksecurity.googleapis.com/${try(google_network_security_security_profile_group.prod_sec_profile_group.id, "")}" + } + attachments = { + prod-spoke = try(var.vpc_self_links.prod-spoke-0, null) + } + factories_config = { + cidr_file_path = var.factories_config.cidrs + egress_rules_file_path = "${var.factories_config.firewall_policy_rules.prod}/egress.yaml" + ingress_rules_file_path = "${var.factories_config.firewall_policy_rules.prod}/ingress.yaml" + } +} diff --git a/fast/stages/3-network-security/outputs.tf b/fast/stages/3-network-security/outputs.tf new file mode 100644 index 000000000..ce93d8e51 --- /dev/null +++ b/fast/stages/3-network-security/outputs.tf @@ -0,0 +1,28 @@ +/** + * Copyright 2024 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. + */ + +output "ngfw_enterprise_endpoint_ids" { + description = "The NGFW Enterprise endpoint ids." + value = { + for _, v in google_network_security_firewall_endpoint.firewall_endpoint + : v.location => v.id + } +} + +output "ngfw_enterprise_endpoints_quota_project" { + description = "The NGFW Enterprise endpoints quota project." + value = module.ngfw-quota-project.id +} diff --git a/fast/stages/3-network-security/variables-fast.tf b/fast/stages/3-network-security/variables-fast.tf new file mode 100644 index 000000000..7a65658b0 --- /dev/null +++ b/fast/stages/3-network-security/variables-fast.tf @@ -0,0 +1,80 @@ +/** + * Copyright 2024 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. + */ + +variable "billing_account" { + # tfdoc:variable:source 0-bootstrap + description = "Billing account id. If billing account is not part of the same org set `is_org_level` to false." + type = object({ + id = string + is_org_level = optional(bool, true) + }) + validation { + condition = var.billing_account.is_org_level != null + error_message = "Invalid `null` value for `billing_account.is_org_level`." + } +} + +variable "folder_ids" { + # tfdoc:variable:source 1-resman + description = "Folders to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created." + type = object({ + networking = string + networking-dev = string + networking-prod = string + }) + nullable = false +} + +variable "host_project_ids" { + # tfdoc:variable:source 2-networking + description = "Host project for the shared VPC." + type = object({ + dev-spoke-0 = optional(string) + prod-spoke-0 = optional(string) + }) + nullable = false + default = {} +} + +variable "organization" { + # tfdoc:variable:source 00-globals + description = "Organization details." + type = object({ + domain = string + id = number + customer_id = string + }) +} + +variable "prefix" { + # tfdoc:variable:source 0-bootstrap + description = "Prefix used for resources that need unique names. Use a maximum of 9 chars for organizations, and 11 chars for tenants." + type = string + validation { + condition = try(length(var.prefix), 0) < 12 + error_message = "Use a maximum of 9 chars for organizations, and 11 chars for tenants." + } +} + +variable "vpc_self_links" { + # tfdoc:variable:source 2-networking + description = "Self link for the shared VPC." + type = object({ + dev-spoke-0 = string + prod-spoke-0 = string + }) + nullable = false +} diff --git a/fast/stages/3-network-security/variables.tf b/fast/stages/3-network-security/variables.tf new file mode 100644 index 000000000..d291577fc --- /dev/null +++ b/fast/stages/3-network-security/variables.tf @@ -0,0 +1,49 @@ +/** + * Copyright 2024 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. + */ + +variable "factories_config" { + description = "Configuration for network resource factories." + type = object({ + cidrs = optional(string, "data/cidrs.yaml") + firewall_policy_rules = optional(object({ + dev = string + prod = string + })) + }) + nullable = false + default = { + firewall_policy_rules = { + dev = "data/firewall-policy-rules/dev" + prod = "data/firewall-policy-rules/prod" + } + } +} + +variable "ngfw_enterprise_config" { + description = "NGFW Enterprise configuration." + type = object({ + endpoint_zones = list(string) + quota_project_id = optional(string, null) + }) + nullable = false + default = { + endpoint_zones = [ + "europe-west1-b", + "europe-west1-c", + "europe-west1-d" + ] + } +} diff --git a/tests/fast/stages/s0_bootstrap/checklist.yaml b/tests/fast/stages/s0_bootstrap/checklist.yaml index 17ceb250e..61d5a41c1 100644 --- a/tests/fast/stages/s0_bootstrap/checklist.yaml +++ b/tests/fast/stages/s0_bootstrap/checklist.yaml @@ -201,7 +201,10 @@ values: module.organization.google_organization_iam_binding.bindings["organization_iam_admin_conditional"]: condition: - description: Automation service account delegated grants. - expression: api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/cloudasset.viewer','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/orgpolicy.policyViewer','roles/resourcemanager.organizationViewer','organizations/123456789012/roles/serviceProjectNetworkAdmin','organizations/123456789012/roles/tenantNetworkAdmin']) + expression: | + api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/accesscontextmanager.policyAdmin','roles/cloudasset.viewer','roles/compute.orgFirewallPolicyAdmin','roles/compute.xpnAdmin','roles/orgpolicy.policyAdmin','roles/orgpolicy.policyViewer','roles/resourcemanager.organizationViewer']) + || api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['organizations/123456789012/roles/networkFirewallPoliciesAdmin','organizations/123456789012/roles/ngfwEnterpriseAdmin','organizations/123456789012/roles/serviceProjectNetworkAdmin','organizations/123456789012/roles/tenantNetworkAdmin']) + title: automation_sa_delegated_grants members: - serviceAccount:fast-prod-resman-0@fast-prod-iac-core-0.iam.gserviceaccount.com @@ -376,15 +379,15 @@ counts: google_logging_organization_sink: 4 google_logging_project_bucket_config: 4 google_org_policy_policy: 22 - google_organization_iam_binding: 28 - google_organization_iam_custom_role: 7 + google_organization_iam_binding: 29 + google_organization_iam_custom_role: 9 google_organization_iam_member: 41 google_project: 3 google_project_iam_audit_config: 1 google_project_iam_binding: 19 google_project_iam_member: 16 - google_project_service: 30 - google_project_service_identity: 6 + google_project_service: 31 + google_project_service_identity: 7 google_service_account: 6 google_service_account_iam_binding: 2 google_service_account_iam_member: 1 @@ -396,4 +399,4 @@ counts: google_tags_tag_key: 1 google_tags_tag_value: 1 modules: 21 - resources: 230 + resources: 235 diff --git a/tests/fast/stages/s0_bootstrap/simple.yaml b/tests/fast/stages/s0_bootstrap/simple.yaml index f31425e69..76a252143 100644 --- a/tests/fast/stages/s0_bootstrap/simple.yaml +++ b/tests/fast/stages/s0_bootstrap/simple.yaml @@ -20,15 +20,15 @@ counts: google_logging_organization_sink: 4 google_logging_project_bucket_config: 4 google_org_policy_policy: 22 - google_organization_iam_binding: 28 - google_organization_iam_custom_role: 7 + google_organization_iam_binding: 29 + google_organization_iam_custom_role: 9 google_organization_iam_member: 28 google_project: 3 google_project_iam_audit_config: 1 google_project_iam_binding: 19 google_project_iam_member: 16 - google_project_service: 30 - google_project_service_identity: 6 + google_project_service: 31 + google_project_service_identity: 7 google_service_account: 6 google_service_account_iam_binding: 2 google_service_account_iam_member: 1 @@ -41,11 +41,16 @@ counts: google_tags_tag_value: 1 local_file: 10 modules: 20 - resources: 224 + resources: 229 + outputs: + automation: __missing__ + billing_dataset: __missing__ cicd_repositories: {} custom_roles: gcve_network_admin: organizations/123456789012/roles/gcveNetworkAdmin + network_firewall_policies_admin: organizations/123456789012/roles/networkFirewallPoliciesAdmin + ngfw_enterprise_admin: organizations/123456789012/roles/ngfwEnterpriseAdmin organization_admin_viewer: organizations/123456789012/roles/organizationAdminViewer organization_iam_admin: organizations/123456789012/roles/organizationIamAdmin service_project_network_admin: organizations/123456789012/roles/serviceProjectNetworkAdmin @@ -66,4 +71,3 @@ outputs: workload_identity_pool: pool: null providers: {} - diff --git a/tests/fast/stages/s1_resman/checklist.tfvars b/tests/fast/stages/s1_resman/checklist.tfvars index cd037f897..762942805 100644 --- a/tests/fast/stages/s1_resman/checklist.tfvars +++ b/tests/fast/stages/s1_resman/checklist.tfvars @@ -13,10 +13,12 @@ billing_account = { } custom_roles = { # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin", - gcve_network_admin = "organizations/123456789012/roles/gcveNetworkAdmin" - organization_admin_viewer = "organizations/123456789012/roles/organizationAdminViewer" - service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin" - storage_viewer = "organizations/123456789012/roles/storageViewer" + gcve_network_admin = "organizations/123456789012/roles/gcveNetworkAdmin" + network_firewall_policies_admin = "organizations/123456789012/roles/networkFirewallPoliciesAdmin" + ngfw_enterprise_admin = "organizations/123456789012/roles/ngfwEnterpriseAdmin" + organization_admin_viewer = "organizations/123456789012/roles/organizationAdminViewer" + service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin" + storage_viewer = "organizations/123456789012/roles/storageViewer" } factories_config = { checklist_data = "checklist-data.json" diff --git a/tests/fast/stages/s1_resman/checklist.yaml b/tests/fast/stages/s1_resman/checklist.yaml index eed5d8498..5128a8291 100644 --- a/tests/fast/stages/s1_resman/checklist.yaml +++ b/tests/fast/stages/s1_resman/checklist.yaml @@ -416,17 +416,17 @@ values: counts: google_folder: 56 - google_folder_iam_binding: 69 - google_organization_iam_member: 6 - google_project_iam_member: 4 - google_service_account: 4 - google_service_account_iam_binding: 4 - google_storage_bucket: 2 - google_storage_bucket_iam_binding: 4 - google_storage_bucket_iam_member: 4 - google_storage_bucket_object: 5 + google_folder_iam_binding: 71 + google_organization_iam_member: 8 + google_project_iam_member: 6 + google_service_account: 6 + google_service_account_iam_binding: 6 + google_storage_bucket: 3 + google_storage_bucket_iam_binding: 6 + google_storage_bucket_iam_member: 6 + google_storage_bucket_object: 7 google_tags_tag_binding: 4 google_tags_tag_key: 2 google_tags_tag_value: 9 - modules: 63 - resources: 173 + modules: 66 + resources: 190 diff --git a/tests/fast/stages/s1_resman/simple.tfvars b/tests/fast/stages/s1_resman/simple.tfvars index 520722b48..16c762f1f 100644 --- a/tests/fast/stages/s1_resman/simple.tfvars +++ b/tests/fast/stages/s1_resman/simple.tfvars @@ -13,10 +13,12 @@ billing_account = { } custom_roles = { # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin", - gcve_network_admin = "organizations/123456789012/roles/gcveNetworkAdmin" - organization_admin_viewer = "organizations/123456789012/roles/organizationAdminViewer" - service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin" - storage_viewer = "organizations/123456789012/roles/storageViewer" + gcve_network_admin = "organizations/123456789012/roles/gcveNetworkAdmin" + network_firewall_policies_admin = "organizations/123456789012/roles/networkFirewallPoliciesAdmin" + ngfw_enterprise_admin = "organizations/123456789012/roles/ngfwEnterpriseAdmin" + organization_admin_viewer = "organizations/123456789012/roles/organizationAdminViewer" + service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin" + storage_viewer = "organizations/123456789012/roles/storageViewer" } groups = { gcp-billing-admins = "gcp-billing-admins", diff --git a/tests/fast/stages/s1_resman/simple.yaml b/tests/fast/stages/s1_resman/simple.yaml index 06108848e..fdcd50cce 100644 --- a/tests/fast/stages/s1_resman/simple.yaml +++ b/tests/fast/stages/s1_resman/simple.yaml @@ -14,17 +14,17 @@ counts: google_folder: 4 - google_folder_iam_binding: 23 - google_organization_iam_member: 6 - google_project_iam_member: 4 - google_service_account: 4 - google_service_account_iam_binding: 4 - google_storage_bucket: 2 - google_storage_bucket_iam_binding: 4 - google_storage_bucket_iam_member: 4 - google_storage_bucket_object: 5 + google_folder_iam_binding: 25 + google_organization_iam_member: 8 + google_project_iam_member: 6 + google_service_account: 6 + google_service_account_iam_binding: 6 + google_storage_bucket: 3 + google_storage_bucket_iam_binding: 6 + google_storage_bucket_iam_member: 6 + google_storage_bucket_object: 7 google_tags_tag_binding: 4 google_tags_tag_key: 2 google_tags_tag_value: 9 - modules: 11 - resources: 75 + modules: 14 + resources: 92 diff --git a/tests/fast/stages/s1_tenant_factory/simple.tfvars b/tests/fast/stages/s1_tenant_factory/simple.tfvars index dc6636d13..cad9f280d 100644 --- a/tests/fast/stages/s1_tenant_factory/simple.tfvars +++ b/tests/fast/stages/s1_tenant_factory/simple.tfvars @@ -14,11 +14,13 @@ billing_account = { } custom_roles = { # organization_iam_admin = "organizations/123456789012/roles/organizationIamAdmin", - gcve_network_admin = "organizations/123456789012/roles/gcveNetworkAdmin" - organization_admin_viewer = "organizations/123456789012/roles/organizationAdminViewer" - service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin" - storage_viewer = "organizations/123456789012/roles/storageViewer" - tenant_network_admin = "organizations/123456789012/roles/tenantNetworkAdmin" + gcve_network_admin = "organizations/123456789012/roles/gcveNetworkAdmin" + network_firewall_policies_admin = "organizations/123456789012/roles/networkFirewallPoliciesAdmin" + ngfw_enterprise_admin = "organizations/123456789012/roles/ngfwEnterpriseAdmin" + organization_admin_viewer = "organizations/123456789012/roles/organizationAdminViewer" + service_project_network_admin = "organizations/123456789012/roles/xpnServiceAdmin" + storage_viewer = "organizations/123456789012/roles/storageViewer" + tenant_network_admin = "organizations/123456789012/roles/tenantNetworkAdmin" } groups = { gcp-billing-admins = "gcp-billing-admins", diff --git a/tests/fast/stages/s2_networking_a_simple/ncc.yaml b/tests/fast/stages/s2_networking_a_simple/ncc.yaml index 997240ce9..1e8f149de 100644 --- a/tests/fast/stages/s2_networking_a_simple/ncc.yaml +++ b/tests/fast/stages/s2_networking_a_simple/ncc.yaml @@ -38,8 +38,9 @@ counts: google_project: 3 google_project_iam_binding: 4 google_project_iam_member: 18 - google_project_service: 22 - google_project_service_identity: 16 + google_project_service: 24 + google_project_service_identity: 18 google_storage_bucket_object: 2 + google_vpc_access_connector: 2 modules: 24 - resources: 169 + resources: 173 diff --git a/tests/fast/stages/s2_networking_a_simple/simple.yaml b/tests/fast/stages/s2_networking_a_simple/simple.yaml index be0dc8ab8..844d6216f 100644 --- a/tests/fast/stages/s2_networking_a_simple/simple.yaml +++ b/tests/fast/stages/s2_networking_a_simple/simple.yaml @@ -42,10 +42,10 @@ counts: google_project: 3 google_project_iam_binding: 4 google_project_iam_member: 17 - google_project_service: 21 - google_project_service_identity: 15 + google_project_service: 23 + google_project_service_identity: 17 google_storage_bucket_object: 2 google_vpc_access_connector: 2 modules: 29 random_id: 1 - resources: 181 + resources: 185 diff --git a/tests/fast/stages/s2_networking_a_simple/vpn.yaml b/tests/fast/stages/s2_networking_a_simple/vpn.yaml index dc854c64d..a96742561 100644 --- a/tests/fast/stages/s2_networking_a_simple/vpn.yaml +++ b/tests/fast/stages/s2_networking_a_simple/vpn.yaml @@ -40,10 +40,10 @@ counts: google_project: 3 google_project_iam_binding: 4 google_project_iam_member: 17 - google_project_service: 21 - google_project_service_identity: 15 + google_project_service: 23 + google_project_service_identity: 17 google_storage_bucket_object: 2 google_vpc_access_connector: 2 modules: 31 random_id: 5 - resources: 218 + resources: 222 diff --git a/tests/fast/stages/s2_networking_b_nva/ncc-ra.yaml b/tests/fast/stages/s2_networking_b_nva/ncc-ra.yaml index 7734e7714..69c26f8c4 100644 --- a/tests/fast/stages/s2_networking_b_nva/ncc-ra.yaml +++ b/tests/fast/stages/s2_networking_b_nva/ncc-ra.yaml @@ -45,10 +45,10 @@ counts: google_project: 3 google_project_iam_binding: 4 google_project_iam_member: 18 - google_project_service: 22 - google_project_service_identity: 16 + google_project_service: 24 + google_project_service_identity: 18 google_storage_bucket_object: 2 google_vpc_access_connector: 2 modules: 39 random_id: 2 - resources: 249 + resources: 253 diff --git a/tests/fast/stages/s2_networking_b_nva/simple.yaml b/tests/fast/stages/s2_networking_b_nva/simple.yaml index c367869b8..d88b77b4a 100644 --- a/tests/fast/stages/s2_networking_b_nva/simple.yaml +++ b/tests/fast/stages/s2_networking_b_nva/simple.yaml @@ -47,10 +47,10 @@ counts: google_project: 3 google_project_iam_binding: 4 google_project_iam_member: 17 - google_project_service: 21 - google_project_service_identity: 15 + google_project_service: 23 + google_project_service_identity: 17 google_storage_bucket_object: 2 google_vpc_access_connector: 2 modules: 43 random_id: 2 - resources: 232 + resources: 236 diff --git a/tests/fast/stages/s2_networking_c_separate_envs/simple.yaml b/tests/fast/stages/s2_networking_c_separate_envs/simple.yaml index 582ff98c0..e76d911b1 100644 --- a/tests/fast/stages/s2_networking_c_separate_envs/simple.yaml +++ b/tests/fast/stages/s2_networking_c_separate_envs/simple.yaml @@ -40,10 +40,10 @@ counts: google_project: 2 google_project_iam_binding: 4 google_project_iam_member: 14 - google_project_service: 16 - google_project_service_identity: 12 + google_project_service: 18 + google_project_service_identity: 14 google_storage_bucket_object: 2 google_vpc_access_connector: 2 modules: 22 random_id: 2 - resources: 200 + resources: 204 diff --git a/tests/fast/stages/s3_network_security/__init__.py b/tests/fast/stages/s3_network_security/__init__.py new file mode 100644 index 000000000..633bb7f1e --- /dev/null +++ b/tests/fast/stages/s3_network_security/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2024 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. diff --git a/tests/fast/stages/s3_network_security/simple.tfvars b/tests/fast/stages/s3_network_security/simple.tfvars new file mode 100644 index 000000000..f713e5af7 --- /dev/null +++ b/tests/fast/stages/s3_network_security/simple.tfvars @@ -0,0 +1,22 @@ +billing_account = { + id = "000000-111111-222222" +} +folder_ids = { + networking = "folders/12345678900" + networking-dev = "folders/12345678901" + networking-prod = "folders/12345678902" +} +host_project_ids = { + dev-spoke-0 = "dev-project" + prod-spoke-0 = "prod-project" +} +organization = { + domain = "fast.example.com" + id = 123456789012 + customer_id = "C00000000" +} +prefix = "fast2" +vpc_self_links = { + dev-spoke-0 = "https://www.googleapis.com/compute/v1/projects/123456789/networks/vpc-1" + prod-spoke-0 = "https://www.googleapis.com/compute/v1/projects/123456789/networks/vpc-2" +} diff --git a/tests/fast/stages/s3_network_security/simple.yaml b/tests/fast/stages/s3_network_security/simple.yaml new file mode 100644 index 000000000..4deac063d --- /dev/null +++ b/tests/fast/stages/s3_network_security/simple.yaml @@ -0,0 +1,27 @@ +# Copyright 2024 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. + +counts: + google_compute_network_firewall_policy: 2 + google_compute_network_firewall_policy_association: 2 + google_compute_network_firewall_policy_rule: 4 + google_network_security_firewall_endpoint: 3 + google_network_security_firewall_endpoint_association: 6 + google_network_security_security_profile: 2 + google_network_security_security_profile_group: 2 + google_project: 1 + google_project_service: 1 + google_project_service_identity: 1 + modules: 3 + resources: 24