From edb44a7678538da4c5791605d36b8ef7c86de622 Mon Sep 17 00:00:00 2001 From: Ludovico Magnocavallo Date: Fri, 10 Mar 2023 09:21:49 +0100 Subject: [PATCH] Network firewall policy module (#1232) * validated, untested * tested * typo in README --- README.md | 2 +- modules/README.md | 1 + modules/net-vpc-firewall-policy/README.md | 72 +++++++++ modules/net-vpc-firewall-policy/main.tf | 150 +++++++++++++++++++ modules/net-vpc-firewall-policy/variables.tf | 121 +++++++++++++++ modules/net-vpc-firewall-policy/versions.tf | 29 ++++ 6 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 modules/net-vpc-firewall-policy/README.md create mode 100644 modules/net-vpc-firewall-policy/main.tf create mode 100644 modules/net-vpc-firewall-policy/variables.tf create mode 100644 modules/net-vpc-firewall-policy/versions.tf diff --git a/README.md b/README.md index 3a499a3712..a77b77589d 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The current list of modules supports most of the core foundational and networkin Currently available modules: - **foundational** - [billing budget](./modules/billing-budget), [Cloud Identity group](./modules/cloud-identity-group/), [folder](./modules/folder), [service accounts](./modules/iam-service-account), [logging bucket](./modules/logging-bucket), [organization](./modules/organization), [project](./modules/project), [projects-data-source](./modules/projects-data-source) -- **networking** - [DNS](./modules/dns), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [Global Load Balancer (classic)](./modules/net-glb/), [L4 ILB](./modules/net-ilb), [L7 ILB](./modules/net-ilb-l7), [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory) +- **networking** - [DNS](./modules/dns), [Cloud Endpoints](./modules/endpoints), [address reservation](./modules/net-address), [NAT](./modules/net-cloudnat), [Global Load Balancer (classic)](./modules/net-glb/), [L4 ILB](./modules/net-ilb), [L7 ILB](./modules/net-ilb-l7), [VPC](./modules/net-vpc), [VPC firewall](./modules/net-vpc-firewall), [VPC firewall policy](./modules/net-vpc-firewall-policy), [VPC peering](./modules/net-vpc-peering), [VPN dynamic](./modules/net-vpn-dynamic), [HA VPN](./modules/net-vpn-ha), [VPN static](./modules/net-vpn-static), [Service Directory](./modules/service-directory) - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool) - **data** - [BigQuery dataset](./modules/bigquery-dataset), [Bigtable instance](./modules/bigtable-instance), [Cloud SQL instance](./modules/cloudsql-instance), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Datafusion](./modules/datafusion), [Dataproc](./modules/dataproc), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub) - **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository) diff --git a/modules/README.md b/modules/README.md index 955c526b69..8773fb5bd3 100644 --- a/modules/README.md +++ b/modules/README.md @@ -50,6 +50,7 @@ These modules are used in the examples included in this repository. If you are u - [L7 ILB](./net-ilb-l7) - [VPC](./net-vpc) - [VPC firewall](./net-vpc-firewall) +- [VPC firewall policy](./net-vpc-firewall-policy) - [VPC peering](./net-vpc-peering) - [VPN dynamic](./net-vpn-dynamic) - [HA VPN](./net-vpn-ha) diff --git a/modules/net-vpc-firewall-policy/README.md b/modules/net-vpc-firewall-policy/README.md new file mode 100644 index 0000000000..2151dd1ba4 --- /dev/null +++ b/modules/net-vpc-firewall-policy/README.md @@ -0,0 +1,72 @@ +# Google Cloud Network Firewall Policies + +This module allows creation and management of a [global](https://cloud.google.com/vpc/docs/network-firewall-policies) or [regional](https://cloud.google.com/vpc/docs/regional-firewall-policies) network firewall policy, including its associations and rules. + +The module interface deviates slightly from the [`net-vpc-firewall`](../net-vpc-firewall/) module since the underlying resources and API objects are different. + +It also makes fewer assumptions about implicit defaults, only using one to set `match.layer4_configs` to `[{ protocol = "all" }]` if no explicit set of protocols and ports has been specified. + +A factory implementation will be added in a subsequent release. + +## Example + +```hcl +module "firewall-policy" { + source = "./fabric/modules/net-vpc-firewall-policy" + name = "test-1" + project_id = "my-project" + # specify a region to create and manage a regional policy + # region = "europe-west8" + target_vpcs = [ + "projects/my-project/global/networks/shared-vpc" + ] + egress_rules = { + smtp = { + priority = 900 + match = { + destination_ranges = ["0.0.0.0/0"] + layer4_configs = [{ protocol = "tcp", ports = ["25"] }] + } + } + } + ingress_rules = { + icmp = { + priority = 1000 + match = { + source_ranges = ["0.0.0.0/0"] + layer4_configs = [{ protocol = "icmp" }] + } + } + mgmt = { + priority = 1001 + match = { + source_ranges = ["10.1.1.0/24"] + } + } + ssh = { + priority = 1002 + match = { + source_ranges = ["10.0.0.0/8"] + # source_tags = ["tagValues/123456"] + layer4_configs = [{ protocol = "tcp", ports = ["22"] }] + } + } + } +} +# tftest modules=1 resources=6 +``` + + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [name](variables.tf#L98) | Policy name. | string | ✓ | | +| [project_id](variables.tf#L104) | Project id of the project that holds the network. | string | ✓ | | +| [description](variables.tf#L17) | Policy description. | string | | null | +| [egress_rules](variables.tf#L23) | List of egress rule definitions, action can be 'allow', 'deny', 'goto_next'. The match.layer4configs map is in protocol => optional [ports] format. | map(object({…})) | | {} | +| [ingress_rules](variables.tf#L60) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next'. | map(object({…})) | | {} | +| [region](variables.tf#L110) | Policy region. Leave null for global policy. | string | | null | +| [target_vpcs](variables.tf#L116) | VPC ids to which this policy will be attached. | list(string) | | [] | + + diff --git a/modules/net-vpc-firewall-policy/main.tf b/modules/net-vpc-firewall-policy/main.tf new file mode 100644 index 0000000000..d35d77ff73 --- /dev/null +++ b/modules/net-vpc-firewall-policy/main.tf @@ -0,0 +1,150 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + rules = merge( + local._rules_egress, local._rules_ingress + ) + _rules_egress = { + for name, rule in merge(var.egress_rules) : + name => merge(rule, { direction = "EGRESS" }) + } + _rules_ingress = { + for name, rule in merge(var.ingress_rules) : + name => merge(rule, { direction = "INGRESS" }) + } +} + +############################################################################### +# global policy # +############################################################################### + +resource "google_compute_network_firewall_policy" "default" { + count = var.region == null ? 1 : 0 + project = var.project_id + name = var.name + description = var.description +} + +resource "google_compute_network_firewall_policy_association" "default" { + for_each = toset(var.region == null ? var.target_vpcs : []) + project = var.project_id + name = "${var.name}-${reverse(split("/", each.key))[0]}" + attachment_target = each.key + firewall_policy = google_compute_network_firewall_policy.default.0.name +} + +resource "google_compute_network_firewall_policy_rule" "default" { + provider = google-beta + for_each = var.region == null ? local.rules : {} + project = var.project_id + firewall_policy = google_compute_network_firewall_policy.default.0.name + rule_name = each.key + action = each.value.action + description = each.value.description + direction = each.value.direction + disabled = each.value.disabled + enable_logging = each.value.enable_logging + priority = each.value.priority + target_service_accounts = each.value.target_service_accounts + match { + dest_ip_ranges = each.value.match.destination_ranges + src_ip_ranges = each.value.match.source_ranges + dynamic "layer4_configs" { + for_each = each.value.match.layer4_configs + content { + ip_protocol = layer4_configs.value.protocol + ports = layer4_configs.value.ports + } + } + dynamic "src_secure_tags" { + for_each = toset(coalesce(each.value.match.source_tags, [])) + content { + name = src_secure_tags.key + } + } + } + dynamic "target_secure_tags" { + for_each = toset( + each.value.target_tags == null ? [] : each.value.target_tags + ) + content { + name = target_secure_tags.value + } + } +} + +############################################################################### +# regional policy # +############################################################################### + +resource "google_compute_region_network_firewall_policy" "default" { + count = var.region != null ? 1 : 0 + project = var.project_id + name = var.name + description = var.description + region = var.region +} + +resource "google_compute_region_network_firewall_policy_association" "default" { + for_each = toset(var.region != null ? var.target_vpcs : []) + project = var.project_id + name = "${var.name}-${reverse(split("/", each.key))[0]}" + region = var.region + attachment_target = each.key + firewall_policy = google_compute_region_network_firewall_policy.default.0.name +} + +resource "google_compute_region_network_firewall_policy_rule" "default" { + provider = google-beta + for_each = var.region != null ? local.rules : {} + project = var.project_id + region = var.region + firewall_policy = google_compute_region_network_firewall_policy.default.0.name + rule_name = each.key + action = each.value.action + description = each.value.description + direction = each.value.direction + disabled = each.value.disabled + enable_logging = each.value.enable_logging + priority = each.value.priority + target_service_accounts = each.value.target_service_accounts + match { + dest_ip_ranges = each.value.match.destination_ranges + src_ip_ranges = each.value.match.source_ranges + dynamic "layer4_configs" { + for_each = each.value.match.layer4_configs + content { + ip_protocol = layer4_configs.value.protocol + ports = layer4_configs.value.ports + } + } + dynamic "src_secure_tags" { + for_each = toset(coalesce(each.value.match.source_tags, [])) + content { + name = src_secure_tags.key + } + } + } + dynamic "target_secure_tags" { + for_each = toset( + each.value.target_tags == null ? [] : each.value.target_tags + ) + content { + name = target_secure_tags.value + } + } +} diff --git a/modules/net-vpc-firewall-policy/variables.tf b/modules/net-vpc-firewall-policy/variables.tf new file mode 100644 index 0000000000..cd8670630b --- /dev/null +++ b/modules/net-vpc-firewall-policy/variables.tf @@ -0,0 +1,121 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "description" { + description = "Policy description." + type = string + default = null +} + +variable "egress_rules" { + description = "List of egress rule definitions, action can be 'allow', 'deny', 'goto_next'. The match.layer4configs map is in protocol => optional [ports] format." + type = map(object({ + priority = number + action = optional(string, "deny") + description = optional(string) + disabled = optional(bool, false) + enable_logging = optional(bool) + target_service_accounts = optional(list(string)) + target_tags = optional(list(string)) + match = object({ + destination_ranges = optional(list(string)) + source_ranges = optional(list(string)) + source_tags = optional(list(string)) + layer4_configs = optional(list(object({ + protocol = optional(string, "all") + ports = optional(list(string)) + })), [{}]) + }) + })) + default = {} + nullable = false + validation { + condition = alltrue([ + for k, v in var.egress_rules : v.match.destination_ranges != null + ]) + error_message = "Engress rules need destination ranges." + } + validation { + condition = alltrue([ + for k, v in var.egress_rules : + contains(["allow", "deny", "goto_next"], v.action) + ]) + error_message = "Action can only be one of 'allow', 'deny', 'goto_next'." + } +} + +variable "ingress_rules" { + description = "List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next'." + type = map(object({ + priority = number + action = optional(string, "allow") + description = optional(string) + disabled = optional(bool, false) + enable_logging = optional(bool) + target_service_accounts = optional(list(string)) + target_tags = optional(list(string)) + match = object({ + destination_ranges = optional(list(string)) + source_ranges = optional(list(string)) + source_tags = optional(list(string)) + layer4_configs = optional(list(object({ + protocol = optional(string, "all") + ports = optional(list(string)) + })), [{}]) + }) + })) + default = {} + nullable = false + validation { + condition = alltrue([ + for k, v in var.ingress_rules : + v.match.source_ranges != null || v.match.source_tags != null + ]) + error_message = "Ingress rules need source ranges or tags." + } + validation { + condition = alltrue([ + for k, v in var.ingress_rules : + contains(["allow", "deny", "goto_next"], v.action) + ]) + error_message = "Action can only be one of 'allow', 'deny', 'goto_next'." + } +} + +variable "name" { + description = "Policy name." + type = string + nullable = false +} + +variable "project_id" { + description = "Project id of the project that holds the network." + type = string + nullable = false +} + +variable "region" { + description = "Policy region. Leave null for global policy." + type = string + default = null +} + +variable "target_vpcs" { + description = "VPC ids to which this policy will be attached." + type = list(string) + default = [] + nullable = false +} diff --git a/modules/net-vpc-firewall-policy/versions.tf b/modules/net-vpc-firewall-policy/versions.tf new file mode 100644 index 0000000000..cef924ea40 --- /dev/null +++ b/modules/net-vpc-firewall-policy/versions.tf @@ -0,0 +1,29 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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 { + required_version = ">= 1.3.1" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 4.55.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 4.55.0" # tftest + } + } +} + +