From c0aab69bb7997f693e1ed1d6742f9e4aadebfe91 Mon Sep 17 00:00:00 2001 From: averbukh Date: Mon, 26 Jul 2021 09:22:40 +0200 Subject: [PATCH 01/12] feat: Decenrtalized firewall management example added. --- CHANGELOG.md | 1 + README.md | 2 +- modules/net-vpc-firewall-yaml/README.md | 39 +++-- modules/net-vpc-firewall-yaml/main.tf | 17 ++- modules/net-vpc-firewall-yaml/outputs.tf | 8 +- modules/net-vpc-firewall-yaml/variables.tf | 6 +- networking/README.md | 5 +- networking/decentralized-firewall/README.md | 28 ++++ .../decentralized-firewall/backend.tf.sample | 20 +++ networking/decentralized-firewall/diagram.png | Bin 0 -> 70256 bytes .../firewall/common/common-egress.yaml | 43 ++++++ .../firewall/common/iap-access.yaml | 23 +++ .../firewall/common/lb-access.yaml | 24 ++++ .../firewall/dev/app-1/app1-rules.yaml | 33 +++++ .../firewall/dev/app-2/app2-rules.yaml | 31 ++++ .../firewall/prod/app-1/app1-rules.yaml | 32 +++++ networking/decentralized-firewall/main.tf | 136 ++++++++++++++++++ networking/decentralized-firewall/outputs.tf | 53 +++++++ .../decentralized-firewall/variables.tf | 53 +++++++ 19 files changed, 528 insertions(+), 26 deletions(-) create mode 100644 networking/decentralized-firewall/README.md create mode 100644 networking/decentralized-firewall/backend.tf.sample create mode 100644 networking/decentralized-firewall/diagram.png create mode 100644 networking/decentralized-firewall/firewall/common/common-egress.yaml create mode 100644 networking/decentralized-firewall/firewall/common/iap-access.yaml create mode 100644 networking/decentralized-firewall/firewall/common/lb-access.yaml create mode 100644 networking/decentralized-firewall/firewall/dev/app-1/app1-rules.yaml create mode 100644 networking/decentralized-firewall/firewall/dev/app-2/app2-rules.yaml create mode 100644 networking/decentralized-firewall/firewall/prod/app-1/app1-rules.yaml create mode 100644 networking/decentralized-firewall/main.tf create mode 100644 networking/decentralized-firewall/outputs.tf create mode 100644 networking/decentralized-firewall/variables.tf diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a011824..69ae0414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - add support for VPC-SC perimeters in Data Foundation end to end example - fix `vpc-sc` module - new networking example showing how to use [Private Service Connect to call a Cloud Function from on-premises](networking/private-cloud-function-from-onprem/) +- new networking example showing how to organize [decentralized firewall](networking/decentralized-firewall/) management on GCP ## [5.0.0] - 2021-06-17 diff --git a/README.md b/README.md index df419fca..b51700b0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The examples in this repository are split in several main sections: **foundation Currently available examples: - **foundations** - [single level hierarchy](./foundations/environments/) (environments), [multiple level hierarchy](./foundations/business-units/) (business units + environments) -- **networking** - [hub and spoke via peering](./networking/hub-and-spoke-peering/), [hub and spoke via VPN](./networking/hub-and-spoke-vpn/), [DNS and Google Private Access for on-premises](./networking/onprem-google-access-dns/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [ILB as next hop](./networking/ilb-next-hop) +- **networking** - [hub and spoke via peering](./networking/hub-and-spoke-peering/), [hub and spoke via VPN](./networking/hub-and-spoke-vpn/), [DNS and Google Private Access for on-premises](./networking/onprem-google-access-dns/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [ILB as next hop](./networking/ilb-next-hop), [decentralized firewall](./networking/decentralized-firewall) - **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms/), [Cloud Storage to Bigquery with Cloud Dataflow](./data-solutions/gcs-to-bq-with-dataflow/) - **cloud operations** - [Resource tracking and remediation via Cloud Asset feeds](.//cloud-operations/asset-inventory-feed-remediation), [Granular Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Granular Cloud DNS IAM for Shared VPC](./cloud-operations/dns-shared-vpc), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq) - **third party solutions** - [OpenShift cluster on Shared VPC](./third-party-solutions/openshift) diff --git a/modules/net-vpc-firewall-yaml/README.md b/modules/net-vpc-firewall-yaml/README.md index 95508092..bf87557a 100644 --- a/modules/net-vpc-firewall-yaml/README.md +++ b/modules/net-vpc-firewall-yaml/README.md @@ -4,7 +4,7 @@ This module allows creation and management of different types of firewall rules Yaml abstraction for FW rules can simplify users onboarding and also makes rules definition simpler and clearer comparing to HCL. -Nested folder structure for yaml configurations is supported, which allows better and structured code management. +Nested folder structure for yaml configurations is supported, which allows better and structured code management for multiple teams and environments. ## Example @@ -12,20 +12,29 @@ Nested folder structure for yaml configurations is supported, which allows bette ```hcl module "prod-firewall" { - source = "./modules/net-vpc-firewall-yaml" - project_id = "my-prod-project" - network = "my-prod-network" - config_path = "./prod" + source = "./modules/net-vpc-firewall-yaml" + + project_id = "my-prod-project" + network = "my-prod-network" + config_directories = [ + "./prod", + "./common" + ] + log_config = { metadata = "INCLUDE_ALL_METADATA" } } module "dev-firewall" { - source = "./modules/net-vpc-firewall-yaml" - project_id = "my-dev-project" - network = "my-dev-network" - config_path = "./dev" + source = "./modules/net-vpc-firewall-yaml" + + project_id = "my-dev-project" + network = "my-dev-network" + config_directories = [ + "./prod", + "./common" + ] } # tftest:skip ``` @@ -33,9 +42,11 @@ module "dev-firewall" { ### Configuration Structure ```bash +├── common +│ ├── default-egress.yaml +│   ├── lb-rules.yaml +│   └── iap-ingress.yaml ├── dev -│   ├── core -│   │   └── common-rules.yaml │   ├── team-a │   │   ├── databases.yaml │   │   └── webb-app-a.yaml @@ -43,8 +54,6 @@ module "dev-firewall" { │   ├── backend.yaml │   └── frontend.yaml └── prod - ├── core - │   └── common-rules.yaml ├── team-a │   ├── databases.yaml │   └── webb-app-a.yaml @@ -63,7 +72,7 @@ rule-name: # descriptive name, naming convention is adjusted by the module - ports: ['443', '80'] # ports for a specific protocol, keep empty list `[]` for all ports protocol: tcp # protocol, put `all` for any protocol direction: EGRESS # EGRESS or INGRESS - disabled: false # `false` or `true`, FW rule is disabled when `true`, default value is `true` + disabled: false # `false` or `true`, FW rule is disabled when `true`, default value is `false` priority: 1000 # rule priority value, default value is 1000 source_ranges: # list of source ranges, should be specified only for `INGRESS` rule - 0.0.0.0/0 @@ -131,7 +140,7 @@ web-app-a-ingress: | name | description | type | required | default | |---|---|:---: |:---:|:---:| -| config_path | Path to a folder where firewall configs are stored in yaml format. Folder may include subfolders with configuration files. Files suffix must be `.yaml` | string | ✓ | | +| config_directories | List of paths to folders where firewall configs are stored in yaml format. Folder may include subfolders with configuration files. Files suffix must be `.yaml` | list(string) | ✓ | | | network | Name of the network this set of firewall rules applies to. | string | ✓ | | | project_id | Project Id. | string | ✓ | | | *log_config* | Log configuration. Possible values for `metadata` are `EXCLUDE_ALL_METADATA` and `INCLUDE_ALL_METADATA`. Set to `null` for disabling firewall logging. | object({...}) | | null | diff --git a/modules/net-vpc-firewall-yaml/main.tf b/modules/net-vpc-firewall-yaml/main.tf index e401f3b9..ab19b23a 100644 --- a/modules/net-vpc-firewall-yaml/main.tf +++ b/modules/net-vpc-firewall-yaml/main.tf @@ -15,10 +15,23 @@ */ locals { + firewall_rule_files = flatten( + [ + for config_path in var.config_directories : + concat( + [ + for config_file in fileset("${path.root}/${config_path}", "**/*.yaml") : + "${path.root}/${config_path}/${config_file}" + ] + ) + + ] + ) + firewall_rules = merge( [ - for config_file in fileset("${path.root}/${var.config_path}", "**/*.yaml") : - try(yamldecode(file("${path.root}/${var.config_path}/${config_file}")), {}) + for config_file in local.firewall_rule_files : + try(yamldecode(file(config_file)), {}) ]... ) } diff --git a/modules/net-vpc-firewall-yaml/outputs.tf b/modules/net-vpc-firewall-yaml/outputs.tf index 63c3d0c8..d964b5a9 100644 --- a/modules/net-vpc-firewall-yaml/outputs.tf +++ b/modules/net-vpc-firewall-yaml/outputs.tf @@ -18,7 +18,7 @@ output "ingress_allow_rules" { description = "Ingress rules with allow blocks." value = [ for rule in google_compute_firewall.rules : - rule.name if rule.direction == "INGRESS" && length(rule.allow) > 0 + rule if rule.direction == "INGRESS" && length(rule.allow) > 0 ] } @@ -26,7 +26,7 @@ output "ingress_deny_rules" { description = "Ingress rules with deny blocks." value = [ for rule in google_compute_firewall.rules : - rule.name if rule.direction == "INGRESS" && length(rule.deny) > 0 + rule if rule.direction == "INGRESS" && length(rule.deny) > 0 ] } @@ -34,7 +34,7 @@ output "egress_allow_rules" { description = "Egress rules with allow blocks." value = [ for rule in google_compute_firewall.rules : - rule.name if rule.direction == "EGRESS" && length(rule.allow) > 0 + rule if rule.direction == "EGRESS" && length(rule.allow) > 0 ] } @@ -42,6 +42,6 @@ output "egress_deny_rules" { description = "Egress rules with allow blocks." value = [ for rule in google_compute_firewall.rules : - rule.name if rule.direction == "EGRESS" && length(rule.deny) > 0 + rule if rule.direction == "EGRESS" && length(rule.deny) > 0 ] } diff --git a/modules/net-vpc-firewall-yaml/variables.tf b/modules/net-vpc-firewall-yaml/variables.tf index 0d5d4da3..d54d5a35 100644 --- a/modules/net-vpc-firewall-yaml/variables.tf +++ b/modules/net-vpc-firewall-yaml/variables.tf @@ -24,9 +24,9 @@ variable "project_id" { type = string } -variable "config_path" { - description = "Path to a folder where firewall configs are stored in yaml format. Folder may include subfolders with configuration files. Files suffix must be `.yaml`" - type = string +variable "config_directories" { + description = "List of paths to folders where firewall configs are stored in yaml format. Folder may include subfolders with configuration files. Files suffix must be `.yaml`" + type = list(string) } variable "log_config" { diff --git a/networking/README.md b/networking/README.md index 5a1489da..dcd948f4 100644 --- a/networking/README.md +++ b/networking/README.md @@ -40,4 +40,7 @@ It is meant to be used as a starting point for most Shared VPC configurations, a ### Calling a private Cloud Function from On-premises - This [example](./private-cloud-function-from-onprem/) shows how to invoke a [private Google Cloud Function](https://cloud.google.com/functions/docs/networking/network-settings) from the on-prem environment via a [Private Service Connect endpoint](https://cloud.google.com/vpc/docs/private-service-connect#benefits-apis). \ No newline at end of file + This [example](./private-cloud-function-from-onprem/) shows how to invoke a [private Google Cloud Function](https://cloud.google.com/functions/docs/networking/network-settings) from the on-prem environment via a [Private Service Connect endpoint](https://cloud.google.com/vpc/docs/private-service-connect#benefits-apis). + +### Decentralized firewall management + This [example](./decentralized-firewall/) shows how a decentralized firewall management can be organized using [firewall-yaml](../modules/net-vpc-firewall-yaml) module. diff --git a/networking/decentralized-firewall/README.md b/networking/decentralized-firewall/README.md new file mode 100644 index 00000000..d55d9044 --- /dev/null +++ b/networking/decentralized-firewall/README.md @@ -0,0 +1,28 @@ +# Decentralized firewall management + +This sample shows how a decentralized firewall management can be organized using [firewall-yaml](../../modules/net-vpc-firewall-yaml) module. + +This approach is a good fit when Shared VPCs are used across multiple application/infrastructure teams. A centrall repository keeps environment/team specific folders with firewall definitions in `yaml` format. This is the high level diagram: + +![High-level diagram](diagram.png "High-level diagram") + + +## Variables + +| name | description | type | required | default | +|---|---|:---: |:---:|:---:| +| billing_account_id | Billing account id used as default for new projects. | string | ✓ | | +| prefix | Prefix used for resources that need unique names. | string | ✓ | | +| root_node | Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'. | string | ✓ | | +| *ip_ranges* | Subnet IP CIDR ranges. | map(string) | | ... | +| *project_services* | Service APIs enabled by default in new projects. | list(string) | | ... | +| *region* | Region used. | string | | europe-west1 | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| fw_rules | Firewall rules. | | +| projects | Project ids. | | +| vpc | Shared VPCs. | | + diff --git a/networking/decentralized-firewall/backend.tf.sample b/networking/decentralized-firewall/backend.tf.sample new file mode 100644 index 00000000..99f84b17 --- /dev/null +++ b/networking/decentralized-firewall/backend.tf.sample @@ -0,0 +1,20 @@ +# Copyright 2021 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 +# +# https://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. + + +terraform { + backend "gcs" { + bucket = "" + } +} diff --git a/networking/decentralized-firewall/diagram.png b/networking/decentralized-firewall/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..bf655309ea3901836a5623a10b2964e39761cb73 GIT binary patch literal 70256 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 literal 0 HcmV?d00001 diff --git a/networking/decentralized-firewall/firewall/common/common-egress.yaml b/networking/decentralized-firewall/firewall/common/common-egress.yaml new file mode 100644 index 00000000..c958bf81 --- /dev/null +++ b/networking/decentralized-firewall/firewall/common/common-egress.yaml @@ -0,0 +1,43 @@ +# Copyright 2021 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 +# +# https://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. + +# Deny all egress (egress traffic is allowed by default) +deny-all: + deny: + - ports: [] + protocol: all + direction: EGRESS + priority: 65535 + destination_ranges: + - 0.0.0.0/0 + +# Allow access to GCP APIs via Private Google Access +# https://cloud.google.com/vpc/docs/access-apis-external-ip#config +gcp-pga-apis: + allow: + - ports: [443] + protocol: tcp + direction: EGRESS + priority: 500 + destination_ranges: + - 199.36.153.8/30 + +# Allow egress to internal networks +internal-egress: + allow: + - ports: [] + protocol: tcp + direction: EGRESS + destination_ranges: + - 10.0.0.0/16 \ No newline at end of file diff --git a/networking/decentralized-firewall/firewall/common/iap-access.yaml b/networking/decentralized-firewall/firewall/common/iap-access.yaml new file mode 100644 index 00000000..04a9a0cf --- /dev/null +++ b/networking/decentralized-firewall/firewall/common/iap-access.yaml @@ -0,0 +1,23 @@ +# Copyright 2021 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 +# +# https://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. + +# Access via SSH from IAP to all instancess https://cloud.google.com/iap/docs/using-tcp-forwarding#create-firewall-rule +iap-ssh-access: + allow: + - ports: [22] + protocol: tcp + direction: INGRESS + priority: 1001 + source_ranges: + - 35.235.240.0/20 diff --git a/networking/decentralized-firewall/firewall/common/lb-access.yaml b/networking/decentralized-firewall/firewall/common/lb-access.yaml new file mode 100644 index 00000000..f151ef7a --- /dev/null +++ b/networking/decentralized-firewall/firewall/common/lb-access.yaml @@ -0,0 +1,24 @@ +# Copyright 2021 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 +# +# https://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. + +# Access from GCP LBs https://cloud.google.com/load-balancing/docs/https/#firewall_rules +lb-health-checks: + allow: + - ports: [] + protocol: tcp + direction: INGRESS + priority: 1001 + source_ranges: + - 35.191.0.0/16 + - 130.211.0.0/22 \ No newline at end of file diff --git a/networking/decentralized-firewall/firewall/dev/app-1/app1-rules.yaml b/networking/decentralized-firewall/firewall/dev/app-1/app1-rules.yaml new file mode 100644 index 00000000..ca12f272 --- /dev/null +++ b/networking/decentralized-firewall/firewall/dev/app-1/app1-rules.yaml @@ -0,0 +1,33 @@ +# Copyright 2021 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 +# +# https://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. + +# Allow traffic from the frontend VMs +app1-backend: + allow: + - ports: ['443', '80'] + protocol: tcp + direction: INGRESS + source_tags: ['app1-frontend'] + target_tags: ['app1-backend'] + +# Allow traffic to MySQL Servers from App1 backend +app1-db: + allow: + - ports: ['3306'] + protocol: tcp + direction: INGRESS + source_tags: ['app1-backend'] + target_tags: ['mysql-server'] + + diff --git a/networking/decentralized-firewall/firewall/dev/app-2/app2-rules.yaml b/networking/decentralized-firewall/firewall/dev/app-2/app2-rules.yaml new file mode 100644 index 00000000..d15912bd --- /dev/null +++ b/networking/decentralized-firewall/firewall/dev/app-2/app2-rules.yaml @@ -0,0 +1,31 @@ +# Copyright 2021 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 +# +# https://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. + +# Allow traffic from app1 frontend +app2-backend: + allow: + - ports: ['443', '80'] + protocol: tcp + direction: INGRESS + source_tags: ['app1-frontend'] + target_tags: ['app2-backend'] + +# Allow traffic to MySQL servers from App2 backend +app2-db: + allow: + - ports: ['3306'] + protocol: tcp + direction: INGRESS + source_tags: ['app2-backend'] + target_tags: ['mysql-server'] \ No newline at end of file diff --git a/networking/decentralized-firewall/firewall/prod/app-1/app1-rules.yaml b/networking/decentralized-firewall/firewall/prod/app-1/app1-rules.yaml new file mode 100644 index 00000000..541cbf88 --- /dev/null +++ b/networking/decentralized-firewall/firewall/prod/app-1/app1-rules.yaml @@ -0,0 +1,32 @@ +# Copyright 2021 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 +# +# https://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. + +# Allow traffic from the frontend VMs +app1-backend: + allow: + - ports: ['443', '80'] + protocol: tcp + direction: INGRESS + source_tags: ['app1-frontend'] + target_tags: ['app1-backend'] + +# Allow traffic to MySQL Servers from App1 backend +app1-db: + allow: + - ports: ['3306'] + protocol: tcp + direction: INGRESS + source_tags: ['app1-backend'] + target_tags: ['mysql-server'] + diff --git a/networking/decentralized-firewall/main.tf b/networking/decentralized-firewall/main.tf new file mode 100644 index 00000000..74c71934 --- /dev/null +++ b/networking/decentralized-firewall/main.tf @@ -0,0 +1,136 @@ +# Copyright 2021 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 +# +# https://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. + +############################################################################### +# Shared VPC Host projects # +############################################################################### + +module "project-host-prod" { + source = "../../modules/project" + parent = var.root_node + billing_account = var.billing_account_id + prefix = var.prefix + name = "prod-host" + services = var.project_services + + shared_vpc_host_config = { + enabled = true + service_projects = [] + } +} + +module "project-host-dev" { + source = "../../modules/project" + parent = var.root_node + billing_account = var.billing_account_id + prefix = var.prefix + name = "dev-host" + services = var.project_services + + shared_vpc_host_config = { + enabled = true + service_projects = [] + } +} + +################################################################################ +# Networking # +################################################################################ + +module "vpc-prod" { + source = "../../modules/net-vpc" + project_id = module.project-host-prod.project_id + name = "prod-vpc" + subnets = [ + { + ip_cidr_range = var.ip_ranges.prod + name = "prod" + region = var.region + secondary_ip_range = {} + } + ] +} + +module "vpc-dev" { + source = "../../modules/net-vpc" + project_id = module.project-host-dev.project_id + name = "dev-vpc" + subnets = [ + { + ip_cidr_range = var.ip_ranges.dev + name = "dev" + region = var.region + secondary_ip_range = {} + } + ] +} + +############################################################################### +# Private Google Access DNS # +############################################################################### + +module "dns-api-prod" { + source = "../../modules/dns" + project_id = module.project-host-prod.project_id + type = "private" + name = "googleapis" + domain = "googleapis.com." + client_networks = [module.vpc-prod.self_link] + recordsets = [ + { name = "*", type = "CNAME", ttl = 300, records = ["private.googleapis.com."] }, + ] +} + +module "dns-api-dev" { + source = "../../modules/dns" + project_id = module.project-host-dev.project_id + type = "private" + name = "googleapis" + domain = "googleapis.com." + client_networks = [module.vpc-dev.self_link] + recordsets = [ + { name = "*", type = "CNAME", ttl = 300, records = ["private.googleapis.com."] }, + ] +} + +############################################################################### +# Distributed Firewall # +############################################################################### + +module "vpc-firewall-prod" { + source = "../../modules/net-vpc-firewall-yaml" + + project_id = module.project-host-prod.project_id + network = module.vpc-prod.name + config_directories = [ + "./firewall/common", + "./firewall/prod" + ] + + # Enable Firewall Logging for the production fwl rules + log_config = { + metadata = "INCLUDE_ALL_METADATA" + } +} + +module "vpc-firewall-dev" { + source = "../../modules/net-vpc-firewall-yaml" + + project_id = module.project-host-dev.project_id + network = module.vpc-dev.name + config_directories = [ + "./firewall/common", + "./firewall/dev" + ] +} diff --git a/networking/decentralized-firewall/outputs.tf b/networking/decentralized-firewall/outputs.tf new file mode 100644 index 00000000..f744821f --- /dev/null +++ b/networking/decentralized-firewall/outputs.tf @@ -0,0 +1,53 @@ +# Copyright 2021 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 +# +# https://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 "projects" { + description = "Project ids." + value = { + prod-host = module.project-host-prod.project_id + dev-host = module.project-host-dev.project_id + } +} + +output "vpc" { + description = "Shared VPCs." + value = { + prod = { + name = module.vpc-prod.name + subnets = module.vpc-prod.subnet_ips + } + dev = { + name = module.vpc-dev.name + subnets = module.vpc-dev.subnet_ips + } + } +} + +output "fw_rules" { + description = "Firewall rules." + value = { + prod = { + ingress_allow_rules = module.vpc-firewall-prod.ingress_allow_rules + ingress_deny_rules = module.vpc-firewall-prod.ingress_deny_rules + egress_allow_rules = module.vpc-firewall-prod.egress_allow_rules + egress_deny_rules = module.vpc-firewall-prod.egress_deny_rules + } + dev = { + ingress_allow_rules = module.vpc-firewall-dev.ingress_allow_rules + ingress_deny_rules = module.vpc-firewall-dev.ingress_deny_rules + egress_allow_rules = module.vpc-firewall-dev.egress_allow_rules + egress_deny_rules = module.vpc-firewall-dev.egress_deny_rules + } + } +} diff --git a/networking/decentralized-firewall/variables.tf b/networking/decentralized-firewall/variables.tf new file mode 100644 index 00000000..6e71fbc3 --- /dev/null +++ b/networking/decentralized-firewall/variables.tf @@ -0,0 +1,53 @@ +# Copyright 2021 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 +# +# https://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_id" { + description = "Billing account id used as default for new projects." + type = string +} + +variable "prefix" { + description = "Prefix used for resources that need unique names." + type = string +} + +variable "region" { + description = "Region used." + type = string + default = "europe-west1" +} + +variable "root_node" { + description = "Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'." + type = string +} + +variable "ip_ranges" { + description = "Subnet IP CIDR ranges." + type = map(string) + default = { + prod = "10.0.16.0/24" + dev = "10.0.32.0/24" + } +} + +variable "project_services" { + description = "Service APIs enabled by default in new projects." + type = list(string) + default = [ + "container.googleapis.com", + "dns.googleapis.com", + "stackdriver.googleapis.com", + ] +} From 9b30503ab73c31776a7ebdbc346b913a3f43841a Mon Sep 17 00:00:00 2001 From: averbukh Date: Mon, 26 Jul 2021 09:28:16 +0200 Subject: [PATCH 02/12] Formatting --- .../decentralized-firewall/firewall/common/common-egress.yaml | 2 +- .../decentralized-firewall/firewall/common/iap-access.yaml | 1 + .../decentralized-firewall/firewall/common/lb-access.yaml | 2 +- .../decentralized-firewall/firewall/dev/app-1/app1-rules.yaml | 2 -- .../decentralized-firewall/firewall/dev/app-2/app2-rules.yaml | 2 +- .../decentralized-firewall/firewall/prod/app-1/app1-rules.yaml | 1 - 6 files changed, 4 insertions(+), 6 deletions(-) diff --git a/networking/decentralized-firewall/firewall/common/common-egress.yaml b/networking/decentralized-firewall/firewall/common/common-egress.yaml index c958bf81..716c1498 100644 --- a/networking/decentralized-firewall/firewall/common/common-egress.yaml +++ b/networking/decentralized-firewall/firewall/common/common-egress.yaml @@ -40,4 +40,4 @@ internal-egress: protocol: tcp direction: EGRESS destination_ranges: - - 10.0.0.0/16 \ No newline at end of file + - 10.0.0.0/16 diff --git a/networking/decentralized-firewall/firewall/common/iap-access.yaml b/networking/decentralized-firewall/firewall/common/iap-access.yaml index 04a9a0cf..931a180e 100644 --- a/networking/decentralized-firewall/firewall/common/iap-access.yaml +++ b/networking/decentralized-firewall/firewall/common/iap-access.yaml @@ -21,3 +21,4 @@ iap-ssh-access: priority: 1001 source_ranges: - 35.235.240.0/20 + \ No newline at end of file diff --git a/networking/decentralized-firewall/firewall/common/lb-access.yaml b/networking/decentralized-firewall/firewall/common/lb-access.yaml index f151ef7a..975d3ca0 100644 --- a/networking/decentralized-firewall/firewall/common/lb-access.yaml +++ b/networking/decentralized-firewall/firewall/common/lb-access.yaml @@ -21,4 +21,4 @@ lb-health-checks: priority: 1001 source_ranges: - 35.191.0.0/16 - - 130.211.0.0/22 \ No newline at end of file + - 130.211.0.0/22 diff --git a/networking/decentralized-firewall/firewall/dev/app-1/app1-rules.yaml b/networking/decentralized-firewall/firewall/dev/app-1/app1-rules.yaml index ca12f272..9a26650b 100644 --- a/networking/decentralized-firewall/firewall/dev/app-1/app1-rules.yaml +++ b/networking/decentralized-firewall/firewall/dev/app-1/app1-rules.yaml @@ -29,5 +29,3 @@ app1-db: direction: INGRESS source_tags: ['app1-backend'] target_tags: ['mysql-server'] - - diff --git a/networking/decentralized-firewall/firewall/dev/app-2/app2-rules.yaml b/networking/decentralized-firewall/firewall/dev/app-2/app2-rules.yaml index d15912bd..d7b79b63 100644 --- a/networking/decentralized-firewall/firewall/dev/app-2/app2-rules.yaml +++ b/networking/decentralized-firewall/firewall/dev/app-2/app2-rules.yaml @@ -28,4 +28,4 @@ app2-db: protocol: tcp direction: INGRESS source_tags: ['app2-backend'] - target_tags: ['mysql-server'] \ No newline at end of file + target_tags: ['mysql-server'] diff --git a/networking/decentralized-firewall/firewall/prod/app-1/app1-rules.yaml b/networking/decentralized-firewall/firewall/prod/app-1/app1-rules.yaml index 541cbf88..9a26650b 100644 --- a/networking/decentralized-firewall/firewall/prod/app-1/app1-rules.yaml +++ b/networking/decentralized-firewall/firewall/prod/app-1/app1-rules.yaml @@ -29,4 +29,3 @@ app1-db: direction: INGRESS source_tags: ['app1-backend'] target_tags: ['mysql-server'] - From 75ef6dd0ec83f755a3dadb200de88483833f44ca Mon Sep 17 00:00:00 2001 From: averbukh Date: Mon, 26 Jul 2021 09:32:53 +0200 Subject: [PATCH 03/12] Fix decentralized-fwl example docs --- networking/decentralized-firewall/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/networking/decentralized-firewall/README.md b/networking/decentralized-firewall/README.md index d55d9044..3ea255ea 100644 --- a/networking/decentralized-firewall/README.md +++ b/networking/decentralized-firewall/README.md @@ -15,7 +15,7 @@ This approach is a good fit when Shared VPCs are used across multiple applicatio | prefix | Prefix used for resources that need unique names. | string | ✓ | | | root_node | Hierarchy node where projects will be created, 'organizations/org_id' or 'folders/folder_id'. | string | ✓ | | | *ip_ranges* | Subnet IP CIDR ranges. | map(string) | | ... | -| *project_services* | Service APIs enabled by default in new projects. | list(string) | | ... | +| *project_services* | Service APIs enabled by default in new projects. | list(string) | | ... | | *region* | Region used. | string | | europe-west1 | ## Outputs From 830e464e2ed43e0ade5366e412b8bbb0ccc4970f Mon Sep 17 00:00:00 2001 From: averbukh Date: Mon, 26 Jul 2021 09:50:33 +0200 Subject: [PATCH 04/12] Update firewall-yaml test fixture --- tests/modules/net_vpc_firewall_yaml/fixture/main.tf | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/modules/net_vpc_firewall_yaml/fixture/main.tf b/tests/modules/net_vpc_firewall_yaml/fixture/main.tf index 7db37c77..4dcc9b7c 100644 --- a/tests/modules/net_vpc_firewall_yaml/fixture/main.tf +++ b/tests/modules/net_vpc_firewall_yaml/fixture/main.tf @@ -15,9 +15,11 @@ */ module "firewall" { - source = "../../../../modules/net-vpc-firewall-yaml" - project_id = "my-project" - network = "my-network" - config_path = "./rules" - log_config = var.log_config + source = "../../../../modules/net-vpc-firewall-yaml" + project_id = "my-project" + network = "my-network" + config_directories = [ + "./rules" + ] + log_config = var.log_config } From 77f9d9dad9301f0cf04741eab8fa657efca6ce68 Mon Sep 17 00:00:00 2001 From: averbukh Date: Mon, 26 Jul 2021 10:07:55 +0200 Subject: [PATCH 05/12] Fix networking examples readme --- networking/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/networking/README.md b/networking/README.md index dcd948f4..ba3f63e7 100644 --- a/networking/README.md +++ b/networking/README.md @@ -43,4 +43,5 @@ It is meant to be used as a starting point for most Shared VPC configurations, a This [example](./private-cloud-function-from-onprem/) shows how to invoke a [private Google Cloud Function](https://cloud.google.com/functions/docs/networking/network-settings) from the on-prem environment via a [Private Service Connect endpoint](https://cloud.google.com/vpc/docs/private-service-connect#benefits-apis). ### Decentralized firewall management + This [example](./decentralized-firewall/) shows how a decentralized firewall management can be organized using [firewall-yaml](../modules/net-vpc-firewall-yaml) module. From 4d6586b7f31cd01210a6986c0f6b072da8141b34 Mon Sep 17 00:00:00 2001 From: averbukh Date: Mon, 26 Jul 2021 22:45:52 +0200 Subject: [PATCH 06/12] minor fixed to readme --- CHANGELOG.md | 4 ++-- networking/README.md | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69ae0414..cc7ba455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ All notable changes to this project will be documented in this file. - add support for CMEK keys in Data Foundation end to end example - add support for VPC-SC perimeters in Data Foundation end to end example - fix `vpc-sc` module -- new networking example showing how to use [Private Service Connect to call a Cloud Function from on-premises](networking/private-cloud-function-from-onprem/) -- new networking example showing how to organize [decentralized firewall](networking/decentralized-firewall/) management on GCP +- new networking example showing how to use [Private Service Connect to call a Cloud Function from on-premises](./networking/private-cloud-function-from-onprem/) +- new networking example showing how to organize [decentralized firewall](./networking/decentralized-firewall/) management on GCP ## [5.0.0] - 2021-06-17 diff --git a/networking/README.md b/networking/README.md index ba3f63e7..00611913 100644 --- a/networking/README.md +++ b/networking/README.md @@ -37,11 +37,14 @@ It is meant to be used as a starting point for most Shared VPC configurations, a ### ILB as next hop This [example](./ilb-next-hop/) allows testing [ILB as next hop](https://cloud.google.com/load-balancing/docs/internal/ilb-next-hop-overview) using simple Linux gateway VMS between two VPCs, to emulate virtual appliances. An optional additional ILB can be enabled to test multiple load balancer configurations and hashing. +
### Calling a private Cloud Function from On-premises This [example](./private-cloud-function-from-onprem/) shows how to invoke a [private Google Cloud Function](https://cloud.google.com/functions/docs/networking/network-settings) from the on-prem environment via a [Private Service Connect endpoint](https://cloud.google.com/vpc/docs/private-service-connect#benefits-apis). +
### Decentralized firewall management This [example](./decentralized-firewall/) shows how a decentralized firewall management can be organized using [firewall-yaml](../modules/net-vpc-firewall-yaml) module. +
From 07a70daab9ee49bab3e74ba147e9dfc0e2ab5d56 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 27 Jul 2021 16:44:46 +0200 Subject: [PATCH 07/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b51700b0..4cb79901 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The examples in this repository are split in several main sections: **foundation Currently available examples: - **foundations** - [single level hierarchy](./foundations/environments/) (environments), [multiple level hierarchy](./foundations/business-units/) (business units + environments) -- **networking** - [hub and spoke via peering](./networking/hub-and-spoke-peering/), [hub and spoke via VPN](./networking/hub-and-spoke-vpn/), [DNS and Google Private Access for on-premises](./networking/onprem-google-access-dns/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [ILB as next hop](./networking/ilb-next-hop), [decentralized firewall](./networking/decentralized-firewall) +- **networking** - [hub and spoke via peering](./networking/hub-and-spoke-peering/), [hub and spoke via VPN](./networking/hub-and-spoke-vpn/), [DNS and Google Private Access for on-premises](./networking/onprem-google-access-dns/), [Shared VPC with GKE support](./networking/shared-vpc-gke/), [ILB as next hop](./networking/ilb-next-hop), [PSC for on-premises Cloud Function invocation](./networking/private-cloud-function-from-onprem/), [decentralized firewall](./networking/decentralized-firewall) - **data solutions** - [GCE/GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms/), [Cloud Storage to Bigquery with Cloud Dataflow](./data-solutions/gcs-to-bq-with-dataflow/) - **cloud operations** - [Resource tracking and remediation via Cloud Asset feeds](.//cloud-operations/asset-inventory-feed-remediation), [Granular Cloud DNS IAM via Service Directory](./cloud-operations/dns-fine-grained-iam), [Granular Cloud DNS IAM for Shared VPC](./cloud-operations/dns-shared-vpc), [Compute Engine quota monitoring](./cloud-operations/quota-monitoring), [Scheduled Cloud Asset Inventory Export to Bigquery](./cloud-operations/scheduled-asset-inventory-export-bq) - **third party solutions** - [OpenShift cluster on Shared VPC](./third-party-solutions/openshift) From d745fd03dd4d52d62fce94cad2cd0efcda50cf40 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Tue, 27 Jul 2021 16:46:56 +0200 Subject: [PATCH 08/12] Update README.md --- networking/decentralized-firewall/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/networking/decentralized-firewall/README.md b/networking/decentralized-firewall/README.md index 3ea255ea..d718c1d3 100644 --- a/networking/decentralized-firewall/README.md +++ b/networking/decentralized-firewall/README.md @@ -1,8 +1,8 @@ # Decentralized firewall management -This sample shows how a decentralized firewall management can be organized using [firewall-yaml](../../modules/net-vpc-firewall-yaml) module. +This sample shows how a decentralized firewall management can be organized using the [firewall-yaml](../../modules/net-vpc-firewall-yaml) module. -This approach is a good fit when Shared VPCs are used across multiple application/infrastructure teams. A centrall repository keeps environment/team specific folders with firewall definitions in `yaml` format. This is the high level diagram: +This approach is a good fit when Shared VPCs are used across multiple application/infrastructure teams. A central repository keeps environment/team specific folders with firewall definitions in `yaml` format. This is the high level diagram: ![High-level diagram](diagram.png "High-level diagram") From 7792b913648164430a113f878c957efaceb187e5 Mon Sep 17 00:00:00 2001 From: averbukh Date: Wed, 28 Jul 2021 22:14:14 +0200 Subject: [PATCH 09/12] Add basic tests for decentralized firewall example --- networking/decentralized-firewall/main.tf | 8 +++--- .../decentralized_firewall/__init__.py | 13 +++++++++ .../decentralized_firewall/fixture/main.tf | 22 +++++++++++++++ .../fixture/variables.tf | 28 +++++++++++++++++++ .../decentralized_firewall/test_plan.py | 28 +++++++++++++++++++ 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 tests/networking/decentralized_firewall/__init__.py create mode 100644 tests/networking/decentralized_firewall/fixture/main.tf create mode 100644 tests/networking/decentralized_firewall/fixture/variables.tf create mode 100644 tests/networking/decentralized_firewall/test_plan.py diff --git a/networking/decentralized-firewall/main.tf b/networking/decentralized-firewall/main.tf index 74c71934..2502d41f 100644 --- a/networking/decentralized-firewall/main.tf +++ b/networking/decentralized-firewall/main.tf @@ -114,8 +114,8 @@ module "vpc-firewall-prod" { project_id = module.project-host-prod.project_id network = module.vpc-prod.name config_directories = [ - "./firewall/common", - "./firewall/prod" + "${path.module}/firewall/common", + "${path.module}/firewall/prod" ] # Enable Firewall Logging for the production fwl rules @@ -130,7 +130,7 @@ module "vpc-firewall-dev" { project_id = module.project-host-dev.project_id network = module.vpc-dev.name config_directories = [ - "./firewall/common", - "./firewall/dev" + "${path.module}/firewall/common", + "${path.module}/firewall/dev" ] } diff --git a/tests/networking/decentralized_firewall/__init__.py b/tests/networking/decentralized_firewall/__init__.py new file mode 100644 index 00000000..d46dbae5 --- /dev/null +++ b/tests/networking/decentralized_firewall/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 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/networking/decentralized_firewall/fixture/main.tf b/tests/networking/decentralized_firewall/fixture/main.tf new file mode 100644 index 00000000..9bef2d73 --- /dev/null +++ b/tests/networking/decentralized_firewall/fixture/main.tf @@ -0,0 +1,22 @@ +/** + * Copyright 2021 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. + */ + +module "test" { + source = "../../../../networking/decentralized-firewall" + billing_account_id = var.billing_account_id + prefix = var.prefix + root_node = var.root_node +} diff --git a/tests/networking/decentralized_firewall/fixture/variables.tf b/tests/networking/decentralized_firewall/fixture/variables.tf new file mode 100644 index 00000000..9646fe1b --- /dev/null +++ b/tests/networking/decentralized_firewall/fixture/variables.tf @@ -0,0 +1,28 @@ +# Copyright 2021 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 +# +# https://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_id" { + type = string + default = "ABCDE-12345-ABCDE" +} + +variable "prefix" { + type = string + default = "test" +} + +variable "root_node" { + type = string + default = "organizations/0123456789" +} diff --git a/tests/networking/decentralized_firewall/test_plan.py b/tests/networking/decentralized_firewall/test_plan.py new file mode 100644 index 00000000..5183e2a9 --- /dev/null +++ b/tests/networking/decentralized_firewall/test_plan.py @@ -0,0 +1,28 @@ +# Copyright 2021 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. + + +import os +import pytest + + +FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixture") + + +def test_resources(e2e_plan_runner): + "Test that plan works and the numbers of resources is as expected." + modules, resources = e2e_plan_runner(FIXTURES_DIR) + assert len(modules) == 8 + assert len(resources) == 50 + assert modules == "something" From 3a834235549ce4ade070b71f9ddd42840a1df36f Mon Sep 17 00:00:00 2001 From: averbukh Date: Wed, 28 Jul 2021 22:25:26 +0200 Subject: [PATCH 10/12] Cleaning up test for decentralized fwl --- tests/networking/decentralized_firewall/test_plan.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/networking/decentralized_firewall/test_plan.py b/tests/networking/decentralized_firewall/test_plan.py index 5183e2a9..cb1764a9 100644 --- a/tests/networking/decentralized_firewall/test_plan.py +++ b/tests/networking/decentralized_firewall/test_plan.py @@ -25,4 +25,3 @@ def test_resources(e2e_plan_runner): modules, resources = e2e_plan_runner(FIXTURES_DIR) assert len(modules) == 8 assert len(resources) == 50 - assert modules == "something" From a1008a83a8401d7ea41f16522337f939acbdfa2a Mon Sep 17 00:00:00 2001 From: averbukh Date: Thu, 29 Jul 2021 11:54:26 +0200 Subject: [PATCH 11/12] Fix firewall-yaml readme --- modules/net-vpc-firewall-yaml/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/net-vpc-firewall-yaml/README.md b/modules/net-vpc-firewall-yaml/README.md index bf87557a..2d4ab90e 100644 --- a/modules/net-vpc-firewall-yaml/README.md +++ b/modules/net-vpc-firewall-yaml/README.md @@ -4,7 +4,7 @@ This module allows creation and management of different types of firewall rules Yaml abstraction for FW rules can simplify users onboarding and also makes rules definition simpler and clearer comparing to HCL. -Nested folder structure for yaml configurations is supported, which allows better and structured code management for multiple teams and environments. +Nested folder structure for yaml configurations is supported, which allows better and structured code management for multiple teams and environments. ## Example @@ -32,7 +32,7 @@ module "dev-firewall" { project_id = "my-dev-project" network = "my-dev-network" config_directories = [ - "./prod", + "./dev", "./common" ] } From 2ab061baa90ad2d93350562229e326b558724545 Mon Sep 17 00:00:00 2001 From: averbukh Date: Fri, 30 Jul 2021 01:16:47 +0200 Subject: [PATCH 12/12] Note hierarcicall FW rules in the readme --- networking/decentralized-firewall/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/networking/decentralized-firewall/README.md b/networking/decentralized-firewall/README.md index d718c1d3..8a4c0066 100644 --- a/networking/decentralized-firewall/README.md +++ b/networking/decentralized-firewall/README.md @@ -2,7 +2,12 @@ This sample shows how a decentralized firewall management can be organized using the [firewall-yaml](../../modules/net-vpc-firewall-yaml) module. -This approach is a good fit when Shared VPCs are used across multiple application/infrastructure teams. A central repository keeps environment/team specific folders with firewall definitions in `yaml` format. This is the high level diagram: +This approach is a good fit when Shared VPCs are used across multiple application/infrastructure teams. A central repository keeps environment/team specific folders with firewall definitions in `yaml` format. + +In the current example multiple teams can define their [VPC Firewall Rules](https://cloud.google.com/vpc/docs/firewalls) for [dev](./firewall/dev) and [prod](./firewall/prod) environments using team specific subfolders. Rules defined in the [common](./firewall/common) folder are applied to both dev and prod environments. +> **_NOTE:_** Common rules are meant to be used for situations where [hierarchical rules](https://cloud.google.com/vpc/docs/firewall-policies) do not map precisely to requirements (e.g. SA, etc.) + +This is the high level diagram: ![High-level diagram](diagram.png "High-level diagram")