From ae8b86c252613460eafa0cb827617f1e0cdc9a1b Mon Sep 17 00:00:00 2001 From: Ludo Date: Fri, 10 Mar 2023 01:32:24 +0100 Subject: [PATCH 1/3] validated, untested --- modules/net-vpc-firewall-policy/main.tf | 150 +++++++++++++++++++ modules/net-vpc-firewall-policy/variables.tf | 105 +++++++++++++ modules/net-vpc-firewall-policy/versions.tf | 29 ++++ 3 files changed, 284 insertions(+) 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/modules/net-vpc-firewall-policy/main.tf b/modules/net-vpc-firewall-policy/main.tf new file mode 100644 index 0000000000..e5d0fd20af --- /dev/null +++ b/modules/net-vpc-firewall-policy/main.tf @@ -0,0 +1,150 @@ +/** + * 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 + * + * 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}-${each.key}" + 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 = coalesce(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}-${each.key}" + 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_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 = coalesce(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..82527defe1 --- /dev/null +++ b/modules/net-vpc-firewall-policy/variables.tf @@ -0,0 +1,105 @@ +/** + * 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 + * + * 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({ + action = optional(string, "deny") + description = optional(string) + disabled = optional(bool, false) + enable_logging = optional(bool) + priority = optional(number, 1000) + target_service_accounts = optional(list(string)) + target_tags = optional(list(string)) + match = object({ + destination_ranges = optional(list(string)) + layer4_configs = optional(map(list(string))) + source_ranges = optional(list(string)) + source_tags = optional(list(string)) + }) + })) + default = {} + nullable = false + 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({ + action = optional(string, "allow") + description = optional(string) + disabled = optional(bool, false) + enable_logging = optional(bool) + priority = optional(number, 1000) + 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 = list(object({ + protocol = string + ports = optional(list(string)) + })) + }) + })) + default = {} + nullable = false + 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 = "Names of the VPCS 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 + } + } +} + + From 33cafcd1b4f1ec888e2117d8d5c07bd90542707e Mon Sep 17 00:00:00 2001 From: Ludo Date: Fri, 10 Mar 2023 08:49:05 +0100 Subject: [PATCH 2/3] tested --- README.md | 2 +- modules/README.md | 1 + modules/net-vpc-firewall-policy/README.md | 72 ++++++++++++++++++++ modules/net-vpc-firewall-policy/main.tf | 12 ++-- modules/net-vpc-firewall-policy/variables.tf | 32 ++++++--- 5 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 modules/net-vpc-firewall-policy/README.md 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..aa794d53eb --- /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 adder 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 index e5d0fd20af..d35d77ff73 100644 --- a/modules/net-vpc-firewall-policy/main.tf +++ b/modules/net-vpc-firewall-policy/main.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -42,7 +42,7 @@ resource "google_compute_network_firewall_policy" "default" { resource "google_compute_network_firewall_policy_association" "default" { for_each = toset(var.region == null ? var.target_vpcs : []) project = var.project_id - name = "${var.name}-${each.key}" + name = "${var.name}-${reverse(split("/", each.key))[0]}" attachment_target = each.key firewall_policy = google_compute_network_firewall_policy.default.0.name } @@ -64,7 +64,7 @@ resource "google_compute_network_firewall_policy_rule" "default" { dest_ip_ranges = each.value.match.destination_ranges src_ip_ranges = each.value.match.source_ranges dynamic "layer4_configs" { - for_each = coalesce(each.value.match.layer4_configs, {}) + for_each = each.value.match.layer4_configs content { ip_protocol = layer4_configs.value.protocol ports = layer4_configs.value.ports @@ -102,7 +102,7 @@ resource "google_compute_region_network_firewall_policy" "default" { 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}-${each.key}" + 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 @@ -113,7 +113,7 @@ resource "google_compute_region_network_firewall_policy_rule" "default" { for_each = var.region != null ? local.rules : {} project = var.project_id region = var.region - firewall_policy = google_compute_network_firewall_policy.default.0.name + firewall_policy = google_compute_region_network_firewall_policy.default.0.name rule_name = each.key action = each.value.action description = each.value.description @@ -126,7 +126,7 @@ resource "google_compute_region_network_firewall_policy_rule" "default" { dest_ip_ranges = each.value.match.destination_ranges src_ip_ranges = each.value.match.source_ranges dynamic "layer4_configs" { - for_each = coalesce(each.value.match.layer4_configs, {}) + for_each = each.value.match.layer4_configs content { ip_protocol = layer4_configs.value.protocol ports = layer4_configs.value.ports diff --git a/modules/net-vpc-firewall-policy/variables.tf b/modules/net-vpc-firewall-policy/variables.tf index 82527defe1..cd8670630b 100644 --- a/modules/net-vpc-firewall-policy/variables.tf +++ b/modules/net-vpc-firewall-policy/variables.tf @@ -1,5 +1,5 @@ /** - * Copyright 2022 Google LLC + * 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. @@ -23,22 +23,31 @@ variable "description" { 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) - priority = optional(number, 1000) target_service_accounts = optional(list(string)) target_tags = optional(list(string)) match = object({ destination_ranges = optional(list(string)) - layer4_configs = optional(map(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 : @@ -51,25 +60,32 @@ variable "egress_rules" { 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) - priority = optional(number, 1000) 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 = list(object({ - protocol = 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 : @@ -98,7 +114,7 @@ variable "region" { } variable "target_vpcs" { - description = "Names of the VPCS to which this policy will be attached." + description = "VPC ids to which this policy will be attached." type = list(string) default = [] nullable = false From 98bf1c7b1f30cec4cbf87325e7abcf17f00e3e17 Mon Sep 17 00:00:00 2001 From: Ludo Date: Fri, 10 Mar 2023 08:57:24 +0100 Subject: [PATCH 3/3] typo in README --- modules/net-vpc-firewall-policy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/net-vpc-firewall-policy/README.md b/modules/net-vpc-firewall-policy/README.md index aa794d53eb..2151dd1ba4 100644 --- a/modules/net-vpc-firewall-policy/README.md +++ b/modules/net-vpc-firewall-policy/README.md @@ -6,7 +6,7 @@ The module interface deviates slightly from the [`net-vpc-firewall`](../net-vpc- 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 adder in a subsequent release. +A factory implementation will be added in a subsequent release. ## Example