Skip to content

Commit

Permalink
Network firewall policy module (#1232)
Browse files Browse the repository at this point in the history
* validated, untested

* tested

* typo in README
  • Loading branch information
ludoo authored and lcaggio committed May 5, 2023
1 parent 90b32a8 commit edb44a7
Show file tree
Hide file tree
Showing 6 changed files with 374 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions modules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
72 changes: 72 additions & 0 deletions modules/net-vpc-firewall-policy/README.md
Original file line number Diff line number Diff line change
@@ -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
```
<!-- BEGIN TFDOC -->

## Variables

| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [name](variables.tf#L98) | Policy name. | <code>string</code> || |
| [project_id](variables.tf#L104) | Project id of the project that holds the network. | <code>string</code> || |
| [description](variables.tf#L17) | Policy description. | <code>string</code> | | <code>null</code> |
| [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. | <code title="map&#40;object&#40;&#123;&#10; priority &#61; number&#10; action &#61; optional&#40;string, &#34;deny&#34;&#41;&#10; description &#61; optional&#40;string&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; enable_logging &#61; optional&#40;bool&#41;&#10; target_service_accounts &#61; optional&#40;list&#40;string&#41;&#41;&#10; target_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; match &#61; object&#40;&#123;&#10; destination_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; layer4_configs &#61; optional&#40;list&#40;object&#40;&#123;&#10; protocol &#61; optional&#40;string, &#34;all&#34;&#41;&#10; ports &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#91;&#123;&#125;&#93;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [ingress_rules](variables.tf#L60) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next'. | <code title="map&#40;object&#40;&#123;&#10; priority &#61; number&#10; action &#61; optional&#40;string, &#34;allow&#34;&#41;&#10; description &#61; optional&#40;string&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; enable_logging &#61; optional&#40;bool&#41;&#10; target_service_accounts &#61; optional&#40;list&#40;string&#41;&#41;&#10; target_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; match &#61; object&#40;&#123;&#10; destination_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_ranges &#61; optional&#40;list&#40;string&#41;&#41;&#10; source_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; layer4_configs &#61; optional&#40;list&#40;object&#40;&#123;&#10; protocol &#61; optional&#40;string, &#34;all&#34;&#41;&#10; ports &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;&#41;, &#91;&#123;&#125;&#93;&#41;&#10; &#125;&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [region](variables.tf#L110) | Policy region. Leave null for global policy. | <code>string</code> | | <code>null</code> |
| [target_vpcs](variables.tf#L116) | VPC ids to which this policy will be attached. | <code>list&#40;string&#41;</code> | | <code>&#91;&#93;</code> |

<!-- END TFDOC -->
150 changes: 150 additions & 0 deletions modules/net-vpc-firewall-policy/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
}
}
121 changes: 121 additions & 0 deletions modules/net-vpc-firewall-policy/variables.tf
Original file line number Diff line number Diff line change
@@ -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
}
29 changes: 29 additions & 0 deletions modules/net-vpc-firewall-policy/versions.tf
Original file line number Diff line number Diff line change
@@ -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
}
}
}


0 comments on commit edb44a7

Please sign in to comment.