diff --git a/modules/net-vpc-firewall-yaml/README.md b/modules/net-vpc-firewall-yaml/README.md new file mode 100644 index 00000000..95508092 --- /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 = "./prod" + 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" +} +# tftest:skip +``` + +### Configuration Structure + +```bash +├── dev +│   ├── core +│   │   └── common-rules.yaml +│   ├── team-a +│   │   ├── databases.yaml +│   │   └── webb-app-a.yaml +│   └── team-b +│   ├── backend.yaml +│   └── frontend.yaml +└── prod + ├── core + │   └── 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 ./prod/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 egress +deny-all: + deny: + - ports: [] + protocol: all + direction: EGRESS + priority: 65535 + destination_ranges: + - 0.0.0.0/0 + +cat ./dev/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..e401f3b9 --- /dev/null +++ b/modules/net-vpc-firewall-yaml/main.tf @@ -0,0 +1,100 @@ +/** + * 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-%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 + ) + 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" +}