From fcd44c2b782b7586ce761cdbfc7c9f894069ea2a Mon Sep 17 00:00:00 2001 From: averbukh Date: Sun, 14 Mar 2021 03:03:36 +0100 Subject: [PATCH 1/4] Added FW-Yaml module for distributed-firewall network example. --- modules/net-vpc-firewall-yaml/README.md | 147 +++++++++++++++++++++ modules/net-vpc-firewall-yaml/main.tf | 99 ++++++++++++++ modules/net-vpc-firewall-yaml/outputs.tf | 47 +++++++ modules/net-vpc-firewall-yaml/variables.tf | 38 ++++++ modules/net-vpc-firewall-yaml/versions.tf | 20 +++ 5 files changed, 351 insertions(+) create mode 100644 modules/net-vpc-firewall-yaml/README.md create mode 100644 modules/net-vpc-firewall-yaml/main.tf create mode 100644 modules/net-vpc-firewall-yaml/outputs.tf create mode 100644 modules/net-vpc-firewall-yaml/variables.tf create mode 100644 modules/net-vpc-firewall-yaml/versions.tf diff --git a/modules/net-vpc-firewall-yaml/README.md b/modules/net-vpc-firewall-yaml/README.md new file mode 100644 index 00000000..2cfc2176 --- /dev/null +++ b/modules/net-vpc-firewall-yaml/README.md @@ -0,0 +1,147 @@ +# Google Cloud VPC Firewall - Yaml + +This module allows creation and management of different types of firewall rules by defining them in well formatted `yaml` files. + +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. + +## Example + +### Terraform code + +```hcl +module "prod-firewall" { + source = "./modules/net-vpc-firewall-yaml" + project_id = "my-prod-project" + network = "my-prod-network" + config_path = "./production" + 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 = "./development" +} +``` + +### Configuration Structure + +```bash +├── development +│   ├── core-network +│   │   └── common-rules.yaml +│   ├── team-a +│   │   ├── databases.yaml +│   │   └── webb-app-a.yaml +│   └── team-b +│   ├── backend.yaml +│   └── frontend.yaml +└── production + ├── core-network + │   └── common-rules.yaml + ├── team-a + │   ├── databases.yaml + │   └── webb-app-a.yaml + └── team-b + ├── backend.yaml + └── frontend.yaml +``` + +### Rule definition format and structure + +Firewall rules configuration should be placed in a set of yaml files in a folder/s. Firewall rule entry structure is following: + +```yaml +rule-name: # descriptive name, naming convention is adjusted by the module + allow: # `allow` or `deny` + - 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` + 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 + destination_ranges: # list of destination ranges, should be specified only for `EGRESS` rule + - 0.0.0.0/0 + source_tags: ['some-tag'] # list of source tags, should be specified only for `INGRESS` rule + source_service_accounts: # list of source service accounts, should be specified only for `INGRESS` rule, can not be specified together with `source_tags` or `target_tags` + - myapp@myproject-id.iam.gserviceaccount.com + target_tags: ['some-tag'] # list of target tags + target_service_accounts: # list of target service accounts, , can not be specified together with `source_tags` or `target_tags` + - myapp@myproject-id.iam.gserviceaccount.com +``` + + +Firewall rules example yaml configuration + +```bash +cat ./production/core-network/common-rules.yaml +# allow ingress from GCLB to all instances in the network +lb-health-checks: + allow: + - ports: [] + protocol: tcp + direction: INGRESS + priority: 1001 + source_ranges: + - 35.191.0.0/16 + - 130.211.0.0/22 + +# deny all eggress +deny-all: + deny: + - ports: [] + protocol: all + direction: EGRESS + priority: 65535 + destination_ranges: + - 0.0.0.0/0 + +cat ./development/team-a/web-app-a.yaml +# Myapp egress +web-app-a-egress: + allow: + - ports: [443] + protocol: tcp + direction: EGRESS + destination_ranges: + - 192.168.0.0/24 + target_service_accounts: + - myapp@myproject-id.iam.gserviceaccount.com +# Myapp ingress +web-app-a-ingress: + allow: + - ports: [1234] + protocol: tcp + direction: INGRESS + source_service_accounts: + - frontend-sa@myproject-id.iam.gserviceaccount.com + target_service_accounts: + - web-app-a@myproject-id.iam.gserviceaccount.com +``` + + +## Variables + +| 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 | ✓ | | +| 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 | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| egress_allow_rules | Egress rules with allow blocks. | | +| egress_deny_rules | Egress rules with allow blocks. | | +| ingress_allow_rules | Ingress rules with allow blocks. | | +| ingress_deny_rules | Ingress rules with deny blocks. | | + + diff --git a/modules/net-vpc-firewall-yaml/main.tf b/modules/net-vpc-firewall-yaml/main.tf new file mode 100644 index 00000000..dc0b5b75 --- /dev/null +++ b/modules/net-vpc-firewall-yaml/main.tf @@ -0,0 +1,99 @@ +/** + * 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. + */ + +locals { + firewall_rules = merge( + [ + for config_file in fileset("${path.root}/${var.config_path}", "**/*.yaml") : + try(yamldecode(file("${path.root}/${var.config_path}/${config_file}")), {}) + ]... + ) +} + +resource "time_static" "timestamp" { + for_each = local.firewall_rules + triggers = { + name = md5(jsonencode(each.value)) + } +} + +resource "google_compute_firewall" "rules" { + for_each = local.firewall_rules + project = var.project_id + name = format( + "fwr-%s-%s-%s", + (try(each.value.target_service_accounts, null) != null ? "sac" : try(each.value.target_tags, null) != null ? "vpc" : "all"), + substr(lower(each.value.direction), 0, 1), + each.key + ) + description = format( + "%s rule in network %s for %s created at %s", + each.value.direction, + var.network, + each.key, + time_static.timestamp[each.key].rfc3339 + ) + + network = var.network + direction = each.value.direction + priority = try(each.value.priority, 1000) + disabled = try(each.value.disabled, null) + + source_ranges = try(each.value.source_ranges, each.value.direction == "INGRESS" ? [] : null) + source_tags = try(each.value.source_tags, null) + source_service_accounts = try(each.value.source_service_accounts, null) + + destination_ranges = try(each.value.destination_ranges, each.value.direction == "EGRESS" ? [] : null) + target_tags = try(each.value.target_tags, null) + target_service_accounts = try(each.value.target_service_accounts, null) + + dynamic "allow" { + for_each = { for block in try(each.value.allow, []) : + "${block.protocol}-${join("-", block.ports)}" => { + ports = [for port in block.ports : tostring(port)] + protocol = block.protocol + } + } + content { + protocol = allow.value.protocol + ports = allow.value.ports + } + } + + dynamic "deny" { + for_each = { for block in try(each.value.deny, []) : + "${block.protocol}-${join("-", block.ports)}" => { + ports = [for port in block.ports : tostring(port)] + protocol = block.protocol + } + } + content { + protocol = deny.value.protocol + ports = deny.value.ports + } + } + + dynamic "log_config" { + for_each = var.log_config != null ? [""] : [] + content { + metadata = var.log_config.metadata + } + } + + lifecycle { + create_before_destroy = true + } +} diff --git a/modules/net-vpc-firewall-yaml/outputs.tf b/modules/net-vpc-firewall-yaml/outputs.tf new file mode 100644 index 00000000..63c3d0c8 --- /dev/null +++ b/modules/net-vpc-firewall-yaml/outputs.tf @@ -0,0 +1,47 @@ +/** + * 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. + */ + +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 + ] +} + +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 + ] +} + +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 + ] +} + +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 + ] +} diff --git a/modules/net-vpc-firewall-yaml/variables.tf b/modules/net-vpc-firewall-yaml/variables.tf new file mode 100644 index 00000000..0d5d4da3 --- /dev/null +++ b/modules/net-vpc-firewall-yaml/variables.tf @@ -0,0 +1,38 @@ +/** + * 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. + */ + +variable "network" { + description = "Name of the network this set of firewall rules applies to." + type = string +} + +variable "project_id" { + description = "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 "log_config" { + description = "Log configuration. Possible values for `metadata` are `EXCLUDE_ALL_METADATA` and `INCLUDE_ALL_METADATA`. Set to `null` for disabling firewall logging." + type = object({ + metadata = string + }) + default = null +} diff --git a/modules/net-vpc-firewall-yaml/versions.tf b/modules/net-vpc-firewall-yaml/versions.tf new file mode 100644 index 00000000..ea8877ca --- /dev/null +++ b/modules/net-vpc-firewall-yaml/versions.tf @@ -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 + * + * 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. + */ + + +terraform { + required_version = ">= 0.13.3" +} From 3428e9d562a1cce2249fa3f6e87de79b5c777290 Mon Sep 17 00:00:00 2001 From: averbukh Date: Sun, 14 Mar 2021 03:10:16 +0100 Subject: [PATCH 2/4] Fix typo. --- modules/net-vpc-firewall-yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/net-vpc-firewall-yaml/README.md b/modules/net-vpc-firewall-yaml/README.md index 2cfc2176..55082d90 100644 --- a/modules/net-vpc-firewall-yaml/README.md +++ b/modules/net-vpc-firewall-yaml/README.md @@ -92,7 +92,7 @@ lb-health-checks: - 35.191.0.0/16 - 130.211.0.0/22 -# deny all eggress +# deny all egress deny-all: deny: - ports: [] From 16e11fa7c1be53e2862980789841066d007734ed Mon Sep 17 00:00:00 2001 From: averbukh Date: Sun, 14 Mar 2021 10:14:39 +0100 Subject: [PATCH 3/4] Adjust FW rule name with network name to prevent name clashing within the same project. --- modules/net-vpc-firewall-yaml/README.md | 14 +++++++------- modules/net-vpc-firewall-yaml/main.tf | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/net-vpc-firewall-yaml/README.md b/modules/net-vpc-firewall-yaml/README.md index 55082d90..57ef8495 100644 --- a/modules/net-vpc-firewall-yaml/README.md +++ b/modules/net-vpc-firewall-yaml/README.md @@ -15,7 +15,7 @@ module "prod-firewall" { source = "./modules/net-vpc-firewall-yaml" project_id = "my-prod-project" network = "my-prod-network" - config_path = "./production" + config_path = "./prod" log_config = { metadata = "INCLUDE_ALL_METADATA" } @@ -25,15 +25,16 @@ module "dev-firewall" { source = "./modules/net-vpc-firewall-yaml" project_id = "my-dev-project" network = "my-dev-network" - config_path = "./development" + config_path = "./dev" } +# tftest:skip ``` ### Configuration Structure ```bash -├── development -│   ├── core-network +├── dev +│   ├── core │   │   └── common-rules.yaml │   ├── team-a │   │   ├── databases.yaml @@ -41,8 +42,8 @@ module "dev-firewall" { │   └── team-b │   ├── backend.yaml │   └── frontend.yaml -└── production - ├── core-network +└── prod + ├── core │   └── common-rules.yaml ├── team-a │   ├── databases.yaml @@ -144,4 +145,3 @@ web-app-a-ingress: | ingress_allow_rules | Ingress rules with allow blocks. | | | ingress_deny_rules | Ingress rules with deny blocks. | | - diff --git a/modules/net-vpc-firewall-yaml/main.tf b/modules/net-vpc-firewall-yaml/main.tf index dc0b5b75..e401f3b9 100644 --- a/modules/net-vpc-firewall-yaml/main.tf +++ b/modules/net-vpc-firewall-yaml/main.tf @@ -34,7 +34,8 @@ resource "google_compute_firewall" "rules" { for_each = local.firewall_rules project = var.project_id name = format( - "fwr-%s-%s-%s", + "fwr-%s-%s-%s-%s", + var.network, (try(each.value.target_service_accounts, null) != null ? "sac" : try(each.value.target_tags, null) != null ? "vpc" : "all"), substr(lower(each.value.direction), 0, 1), each.key From 5a796f363711a8c8228c53439927f05abdeedb09 Mon Sep 17 00:00:00 2001 From: averbukh Date: Sun, 14 Mar 2021 10:23:52 +0100 Subject: [PATCH 4/4] Shorten env names --- 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 57ef8495..95508092 100644 --- a/modules/net-vpc-firewall-yaml/README.md +++ b/modules/net-vpc-firewall-yaml/README.md @@ -81,7 +81,7 @@ rule-name: # descriptive name, naming convention is adjusted by the module Firewall rules example yaml configuration ```bash -cat ./production/core-network/common-rules.yaml +cat ./prod/core-network/common-rules.yaml # allow ingress from GCLB to all instances in the network lb-health-checks: allow: @@ -103,7 +103,7 @@ deny-all: destination_ranges: - 0.0.0.0/0 -cat ./development/team-a/web-app-a.yaml +cat ./dev/team-a/web-app-a.yaml # Myapp egress web-app-a-egress: allow: