-
Notifications
You must be signed in to change notification settings - Fork 921
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Network firewall policy module (#1232)
* validated, untested * tested * typo in README
- Loading branch information
Showing
6 changed files
with
374 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(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)) })), [{}]) }) }))">map(object({…}))</code> | | <code>{}</code> | | ||
| [ingress_rules](variables.tf#L60) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next'. | <code title="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)) })), [{}]) }) }))">map(object({…}))</code> | | <code>{}</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(string)</code> | | <code>[]</code> | | ||
|
||
<!-- END TFDOC --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} | ||
|
||
|