Refactor firewall policy module (#1576)

* refactor module interface

* hierarchical attachment and example

* hierarchical rules and TODO

* split rules resources

* additional fields

* keep using a single resource for rules

* factory

* factory test

* boilerplate

* Prefix ingress and egress rule ids

* Tests for other firewall policy types

* Fix rule id and names

---------

Co-authored-by: Julio Castillo <jccb@google.com>
This commit is contained in:
Ludovico Magnocavallo 2023-08-08 18:57:59 +02:00 committed by GitHub
parent 8917333bde
commit 80ada0e8dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1132 additions and 167 deletions

View File

@ -1,14 +1,63 @@
# Google Cloud Network Firewall Policies
# 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.
This module allows creation and management of two different firewall policy types:
The module interface deviates slightly from the [`net-vpc-firewall`](../net-vpc-firewall/) module since the underlying resources and API objects are different.
- a [hierarchical policy](https://cloud.google.com/firewall/docs/firewall-policies) in a folder or organization, or
- a [global](https://cloud.google.com/vpc/docs/network-firewall-policies) or [regional](https://cloud.google.com/vpc/docs/regional-firewall-policies) network policy
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.
The module also manages policy rules via code or a factory, and optional policy attachments. The interface deviates slightly from the [`net-vpc-firewall`](../net-vpc-firewall/) module since the underlying resources and API objects are different.
A factory implementation will be added in a subsequent release.
The module 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.
## Example
## Examples
### Hierarchical Policy
```hcl
module "firewall-policy" {
source = "./fabric/modules/net-vpc-firewall-policy"
name = "test-1"
parent_id = "folders/1234567890"
attachments = {
test = "folders/4567890123"
}
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 inventory=hierarchical.yaml
```
### Global Network policy
```hcl
module "vpc" {
@ -18,12 +67,10 @@ module "vpc" {
}
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 = {
source = "./fabric/modules/net-vpc-firewall-policy"
name = "test-1"
parent_id = "my-project"
attachments = {
my-vpc = module.vpc.self_link
}
egress_rules = {
@ -59,26 +106,137 @@ module "firewall-policy" {
}
}
}
# tftest modules=2 resources=9
# tftest modules=2 resources=9 inventory=global-net.yaml
```
<!-- BEGIN TFDOC -->
### Regional Network policy
```hcl
module "vpc" {
source = "./fabric/modules/net-vpc"
project_id = "my-project"
name = "my-network"
}
module "firewall-policy" {
source = "./fabric/modules/net-vpc-firewall-policy"
name = "test-1"
parent_id = "my-project"
region = "europe-west8"
attachments = {
my-vpc = module.vpc.self_link
}
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" }]
}
}
}
}
# tftest modules=2 resources=7 inventory=regional-net.yaml
```
### Factory
Similarly to other modules, a rules factory (see [Resource Factories](../../blueprints/factories/)) is also included here to allow route management via descriptive configuration files.
Factory configuration is via three optional attributes in the `rules_factory_config` variable:
- `cidr_file_path` specifying the path to a mapping of logical names to CIDR ranges, used for source and destination ranges in rules when available
- `egress_rules_file_path` specifying the path to the egress rules file
- `ingress_rules_file_path` specifying the path to the ingress rules file
Factory rules are merged with rules declared in code, with the latter taking precedence where both use the same key.
This is an example of a simple factory:
```hcl
module "firewall-policy" {
source = "./fabric/modules/net-vpc-firewall-policy"
name = "test-1"
parent_id = "folders/1234567890"
attachments = {
test = "folders/4567890123"
}
ingress_rules = {
ssh = {
priority = 1002
match = {
source_ranges = ["10.0.0.0/8"]
layer4_configs = [{ protocol = "tcp", ports = ["22"] }]
}
}
}
rules_factory_config = {
cidr_file_path = "configs/cidrs.yaml"
egress_rules_file_path = "configs/egress.yaml"
ingress_rules_file_path = "configs/ingress.yaml"
}
}
# tftest modules=1 resources=5 files=cidrs,egress,ingress inventory=factory.yaml
```
```yaml
# tftest-file id=cidrs path=configs/cidrs.yaml
rfc1918:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/24
```
```yaml
# tftest-file id=egress path=configs/egress.yaml
smtp:
priority: 900
match:
destination_ranges:
- rfc1918
layer4_configs:
- protocol: tcp
ports:
- 25
```
```yaml
# tftest-file id=ingress path=configs/ingress.yaml
icmp:
priority: 1000
match:
source_ranges:
- 10.0.0.0/8
layer4_configs:
- protocol: icmp
```
<!-- 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, in descriptive name => self link format. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [name](variables.tf#L113) | Policy name. | <code>string</code> | ✓ | |
| [parent_id](variables.tf#L119) | Parent node where the policy will be created, `folders/nnn` or `organizations/nnn` for hierarchical policy, project id for a network policy. | <code>string</code> | ✓ | |
| [attachments](variables.tf#L17) | Ids of the resources to which this policy will be attached, in descriptive name => self link format. Specify folders or organization for hierarchical policy, VPCs for network policy. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [description](variables.tf#L24) | Policy description. | <code>string</code> | | <code>null</code> |
| [egress_rules](variables.tf#L30) | 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; address_groups &#61; optional&#40;list&#40;string&#41;&#41;&#10; fqdns &#61; optional&#40;list&#40;string&#41;&#41;&#10; region_codes &#61; optional&#40;list&#40;string&#41;&#41;&#10; threat_intelligences &#61; optional&#40;list&#40;string&#41;&#41;&#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#L71) | 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; address_groups &#61; optional&#40;list&#40;string&#41;&#41;&#10; fqdns &#61; optional&#40;list&#40;string&#41;&#41;&#10; region_codes &#61; optional&#40;list&#40;string&#41;&#41;&#10; threat_intelligences &#61; optional&#40;list&#40;string&#41;&#41;&#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#L125) | Policy region. Leave null for hierarchical policy, or global network policy. | <code>string</code> | | <code>null</code> |
| [rules_factory_config](variables.tf#L131) | Configuration for the optional rules factory. | <code title="object&#40;&#123;&#10; cidr_file_path &#61; optional&#40;string&#41;&#10; egress_rules_file_path &#61; optional&#40;string&#41;&#10; ingress_rules_file_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [id](outputs.tf#L17) | Fully qualified firewall policy id. | |
<!-- END TFDOC -->

View File

@ -0,0 +1,119 @@
/**
* 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 {
_factory_egress_rules = (
var.rules_factory_config.egress_rules_file_path == null
? {}
: yamldecode(file(var.rules_factory_config.egress_rules_file_path))
)
_factory_ingress_rules = (
var.rules_factory_config.ingress_rules_file_path == null
? {}
: yamldecode(file(var.rules_factory_config.ingress_rules_file_path))
)
factory_cidrs = (
var.rules_factory_config.cidr_file_path == null
? {}
: yamldecode(file(var.rules_factory_config.cidr_file_path))
)
factory_egress_rules = {
for k, v in local._factory_egress_rules : "ingress/${k}" => {
action = "deny"
direction = "EGRESS"
priority = v.priority
description = lookup(v, "description", null)
disabled = lookup(v, "disabled", false)
enable_logging = lookup(v, "enable_logging", null)
target_service_accounts = lookup(v, "target_service_accounts", null)
target_tags = lookup(v, "target_tags", null)
match = {
address_groups = lookup(v.match, "address_groups", null)
fqdns = lookup(v.match, "fqdns", null)
region_codes = lookup(v.match, "region_codes", null)
threat_intelligences = lookup(v.match, "threat_intelligences", null)
destination_ranges = (
lookup(v.match, "destination_ranges", null) == null
? null
: flatten([
for r in v.match.destination_ranges :
try(local.factory_cidrs[r], r)
])
)
source_ranges = (
lookup(v.match, "source_ranges", null) == null
? null
: flatten([
for r in v.match.source_ranges :
try(local.factory_cidrs[r], r)
])
)
source_tags = lookup(v.match, "source_tags", null)
layer4_configs = (
lookup(v.match, "layer4_configs", null) == null
? [{ protocol = "all", ports = null }]
: [
for c in v.match.layer4_configs :
merge({ protocol = "all", ports = null }, c)
]
)
}
}
}
factory_ingress_rules = {
for k, v in local._factory_ingress_rules : "egress/${k}" => {
action = "allow"
direction = "INGRESS"
priority = v.priority
description = lookup(v, "description", null)
disabled = lookup(v, "disabled", false)
enable_logging = lookup(v, "enable_logging", null)
target_service_accounts = lookup(v, "target_service_accounts", null)
target_tags = lookup(v, "target_tags", null)
match = {
address_groups = lookup(v.match, "address_groups", null)
fqdns = lookup(v.match, "fqdns", null)
region_codes = lookup(v.match, "region_codes", null)
threat_intelligences = lookup(v.match, "threat_intelligences", null)
destination_ranges = (
lookup(v.match, "destination_ranges", null) == null
? null
: flatten([
for r in v.match.destination_ranges :
try(local.factory_cidrs[r], r)
])
)
source_ranges = (
lookup(v.match, "source_ranges", null) == null
? null
: flatten([
for r in v.match.source_ranges :
try(local.factory_cidrs[r], r)
])
)
source_tags = lookup(v.match, "source_tags", null)
layer4_configs = (
lookup(v.match, "layer4_configs", null) == null
? [{ protocol = "all", ports = null }]
: [
for c in v.match.layer4_configs :
merge({ protocol = "all", ports = null }, c)
]
)
}
}
}
}

View File

@ -0,0 +1,102 @@
/**
* 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.
*/
resource "google_compute_firewall_policy" "hierarchical" {
count = local.use_hierarchical ? 1 : 0
parent = var.parent_id
short_name = var.name
description = var.description
}
resource "google_compute_firewall_policy_association" "hierarchical" {
for_each = local.use_hierarchical ? var.attachments : {}
name = "${var.name}-${each.key}"
attachment_target = each.value
firewall_policy = google_compute_firewall_policy.hierarchical.0.name
}
output "foo" {
value = {
rules = local.rules
cidrs = local.factory_cidrs
}
}
resource "google_compute_firewall_policy_rule" "hierarchical" {
# Terraform's type system barfs in the condition if we use the locals map
for_each = toset(
local.use_hierarchical ? keys(local.rules) : []
)
firewall_policy = google_compute_firewall_policy.hierarchical.0.name
action = local.rules[each.key].action
description = local.rules[each.key].description
direction = local.rules[each.key].direction
disabled = local.rules[each.key].disabled
enable_logging = local.rules[each.key].enable_logging
priority = local.rules[each.key].priority
target_service_accounts = local.rules[each.key].target_service_accounts
match {
dest_ip_ranges = local.rules[each.key].match.destination_ranges
src_ip_ranges = local.rules[each.key].match.source_ranges
dest_address_groups = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.address_groups
: null
)
dest_fqdns = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.fqdns
: null
)
dest_region_codes = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.region_codes
: null
)
dest_threat_intelligences = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.threat_intelligences
: null
)
src_address_groups = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.address_groups
: null
)
src_fqdns = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.fqdns
: null
)
src_region_codes = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.region_codes
: null
)
src_threat_intelligences = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.threat_intelligences
: null
)
dynamic "layer4_configs" {
for_each = local.rules[each.key].match.layer4_configs
content {
ip_protocol = layer4_configs.value.protocol
ports = layer4_configs.value.ports
}
}
}
}

View File

@ -15,136 +15,18 @@
*/
locals {
rules = merge(
local._rules_egress, local._rules_ingress
)
_rules_egress = {
for name, rule in merge(var.egress_rules) :
name => merge(rule, { direction = "EGRESS" })
"egress/${name}" => merge(rule, { name = name, 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 = var.region == null ? var.target_vpcs : {}
project = var.project_id
name = "${var.name}-${each.key}"
attachment_target = each.value
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 = var.region != null ? var.target_vpcs : {}
project = var.project_id
region = var.region
name = "${var.name}-${each.key}"
attachment_target = each.value
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
}
"ingress/${name}" => merge(rule, { name = name, direction = "INGRESS" })
}
rules = merge(
local.factory_egress_rules, local.factory_ingress_rules,
local._rules_egress, local._rules_ingress
)
use_hierarchical = strcontains(var.parent_id, "/") ? true : false
use_regional = !local.use_hierarchical && var.region != null
}

View File

@ -0,0 +1,118 @@
/**
* 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.
*/
resource "google_compute_network_firewall_policy" "net-global" {
count = !local.use_hierarchical && !local.use_regional ? 1 : 0
project = var.parent_id
name = var.name
description = var.description
}
resource "google_compute_network_firewall_policy_association" "net-global" {
for_each = (
!local.use_hierarchical && !local.use_regional ? var.attachments : {}
)
project = var.parent_id
name = "${var.name}-${each.key}"
attachment_target = each.value
firewall_policy = google_compute_network_firewall_policy.net-global.0.name
}
resource "google_compute_network_firewall_policy_rule" "net-global" {
# Terraform's type system barfs in the condition if we use the locals map
for_each = toset(
!local.use_hierarchical && !local.use_regional
? keys(local.rules)
: []
)
project = var.parent_id
firewall_policy = google_compute_network_firewall_policy.net-global.0.name
rule_name = local.rules[each.key].name
action = local.rules[each.key].action
description = local.rules[each.key].description
direction = local.rules[each.key].direction
disabled = local.rules[each.key].disabled
enable_logging = local.rules[each.key].enable_logging
priority = local.rules[each.key].priority
target_service_accounts = local.rules[each.key].target_service_accounts
match {
dest_ip_ranges = local.rules[each.key].match.destination_ranges
src_ip_ranges = local.rules[each.key].match.source_ranges
dest_address_groups = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.address_groups
: null
)
dest_fqdns = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.fqdns
: null
)
dest_region_codes = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.region_codes
: null
)
dest_threat_intelligences = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.threat_intelligences
: null
)
src_address_groups = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.address_groups
: null
)
src_fqdns = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.fqdns
: null
)
src_region_codes = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.region_codes
: null
)
src_threat_intelligences = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.threat_intelligences
: null
)
dynamic "layer4_configs" {
for_each = local.rules[each.key].match.layer4_configs
content {
ip_protocol = layer4_configs.value.protocol
ports = layer4_configs.value.ports
}
}
dynamic "src_secure_tags" {
for_each = toset(coalesce(local.rules[each.key].match.source_tags, []))
content {
name = src_secure_tags.key
}
}
}
dynamic "target_secure_tags" {
for_each = toset(
local.rules[each.key].target_tags == null
? []
: local.rules[each.key].target_tags
)
content {
name = target_secure_tags.value
}
}
}

View File

@ -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.
*/
resource "google_compute_region_network_firewall_policy" "net-regional" {
count = !local.use_hierarchical && local.use_regional ? 1 : 0
project = var.parent_id
name = var.name
description = var.description
region = var.region
}
resource "google_compute_region_network_firewall_policy_association" "net-regional" {
for_each = (
!local.use_hierarchical && local.use_regional ? var.attachments : {}
)
project = var.parent_id
region = var.region
name = "${var.name}-${each.key}"
attachment_target = each.value
firewall_policy = google_compute_region_network_firewall_policy.net-regional.0.name
}
resource "google_compute_region_network_firewall_policy_rule" "net-regional" {
# Terraform's type system barfs in the condition if we use the locals map
for_each = toset(
!local.use_hierarchical && local.use_regional
? keys(local.rules)
: []
)
project = var.parent_id
region = var.region
firewall_policy = google_compute_region_network_firewall_policy.net-regional.0.name
rule_name = local.rules[each.key].name
action = local.rules[each.key].action
description = local.rules[each.key].description
direction = local.rules[each.key].direction
disabled = local.rules[each.key].disabled
enable_logging = local.rules[each.key].enable_logging
priority = local.rules[each.key].priority
target_service_accounts = local.rules[each.key].target_service_accounts
match {
dest_ip_ranges = local.rules[each.key].match.destination_ranges
src_ip_ranges = local.rules[each.key].match.source_ranges
dest_address_groups = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.address_groups
: null
)
dest_fqdns = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.fqdns
: null
)
dest_region_codes = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.region_codes
: null
)
dest_threat_intelligences = (
local.rules[each.key].direction == "EGRESS"
? local.rules[each.key].match.threat_intelligences
: null
)
src_address_groups = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.address_groups
: null
)
src_fqdns = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.fqdns
: null
)
src_region_codes = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.region_codes
: null
)
src_threat_intelligences = (
local.rules[each.key].direction == "INGRESS"
? local.rules[each.key].match.threat_intelligences
: null
)
dynamic "layer4_configs" {
for_each = local.rules[each.key].match.layer4_configs
content {
ip_protocol = layer4_configs.value.protocol
ports = layer4_configs.value.ports
}
}
dynamic "src_secure_tags" {
for_each = toset(coalesce(local.rules[each.key].match.source_tags, []))
content {
name = src_secure_tags.key
}
}
}
dynamic "target_secure_tags" {
for_each = toset(
local.rules[each.key].target_tags == null
? []
: local.rules[each.key].target_tags
)
content {
name = target_secure_tags.value
}
}
}

View File

@ -16,9 +16,9 @@
output "id" {
description = "Fully qualified firewall policy id."
value = (
var.region == null
? try(google_compute_network_firewall_policy.default.0.id, null)
: try(google_compute_region_network_firewall_policy.default.0.id, null)
)
value = coalesce([
try(google_compute_firewall_policy.hierarchical.0.id, null),
try(google_compute_network_firewall_policy.net-global.0.id, null),
try(google_compute_region_network_firewall_policy.net-regional.0.id, null)
])
}

View File

@ -14,6 +14,13 @@
* limitations under the License.
*/
variable "attachments" {
description = "Ids of the resources to which this policy will be attached, in descriptive name => self link format. Specify folders or organization for hierarchical policy, VPCs for network policy."
type = map(string)
default = {}
nullable = false
}
variable "description" {
description = "Policy description."
type = string
@ -31,9 +38,13 @@ variable "egress_rules" {
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))
address_groups = optional(list(string))
fqdns = optional(list(string))
region_codes = optional(list(string))
threat_intelligences = optional(list(string))
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))
@ -68,9 +79,13 @@ variable "ingress_rules" {
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))
address_groups = optional(list(string))
fqdns = optional(list(string))
region_codes = optional(list(string))
threat_intelligences = optional(list(string))
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))
@ -101,21 +116,25 @@ variable "name" {
nullable = false
}
variable "project_id" {
description = "Project id of the project that holds the network."
variable "parent_id" {
description = "Parent node where the policy will be created, `folders/nnn` or `organizations/nnn` for hierarchical policy, project id for a network policy."
type = string
nullable = false
}
variable "region" {
description = "Policy region. Leave null for global policy."
description = "Policy region. Leave null for hierarchical policy, or global network policy."
type = string
default = null
}
variable "target_vpcs" {
description = "VPC ids to which this policy will be attached, in descriptive name => self link format."
type = map(string)
default = {}
nullable = false
variable "rules_factory_config" {
description = "Configuration for the optional rules factory."
type = object({
cidr_file_path = optional(string)
egress_rules_file_path = optional(string)
ingress_rules_file_path = optional(string)
})
nullable = false
default = {}
}

View File

@ -0,0 +1,98 @@
# 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.
values:
module.firewall-policy.google_compute_firewall_policy.hierarchical[0]:
parent: folders/1234567890
short_name: test-1
module.firewall-policy.google_compute_firewall_policy_association.hierarchical["test"]:
attachment_target: folders/4567890123
name: test-1-test
module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["egress/icmp"]:
action: allow
direction: INGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: icmp
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 10.0.0.0/8
src_region_codes: null
src_threat_intelligences: null
priority: 1000
target_resources: null
target_service_accounts: null
module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["ingress/smtp"]:
action: deny
direction: EGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/24
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: tcp
ports:
- '25'
src_address_groups: null
src_fqdns: null
src_ip_ranges: null
src_region_codes: null
src_threat_intelligences: null
priority: 900
target_resources: null
target_service_accounts: null
module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["ingress/ssh"]:
action: allow
direction: INGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: tcp
ports:
- '22'
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 10.0.0.0/8
src_region_codes: null
src_threat_intelligences: null
priority: 1002
target_resources: null
target_service_accounts: null
counts:
google_compute_firewall_policy: 1
google_compute_firewall_policy_association: 1
google_compute_firewall_policy_rule: 3

View File

@ -0,0 +1,137 @@
# 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.
values:
module.firewall-policy.google_compute_network_firewall_policy.net-global[0]:
name: test-1
project: my-project
module.firewall-policy.google_compute_network_firewall_policy_association.net-global["my-vpc"]:
firewall_policy: test-1
name: test-1-my-vpc
project: my-project
module.firewall-policy.google_compute_network_firewall_policy_rule.net-global["egress/smtp"]:
action: deny
direction: EGRESS
disabled: false
enable_logging: null
firewall_policy: test-1
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges:
- 0.0.0.0/0
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: tcp
ports:
- '25'
src_address_groups: null
src_fqdns: null
src_ip_ranges: null
src_region_codes: null
src_secure_tags: []
src_threat_intelligences: null
priority: 900
project: my-project
rule_name: smtp
target_secure_tags: []
target_service_accounts: null
module.firewall-policy.google_compute_network_firewall_policy_rule.net-global["ingress/icmp"]:
action: allow
direction: INGRESS
disabled: false
enable_logging: null
firewall_policy: test-1
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: icmp
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 0.0.0.0/0
src_region_codes: null
src_secure_tags: []
src_threat_intelligences: null
priority: 1000
project: my-project
rule_name: icmp
target_secure_tags: []
target_service_accounts: null
module.firewall-policy.google_compute_network_firewall_policy_rule.net-global["ingress/mgmt"]:
action: allow
direction: INGRESS
disabled: false
enable_logging: null
firewall_policy: test-1
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: all
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 10.1.1.0/24
src_region_codes: null
src_secure_tags: []
src_threat_intelligences: null
priority: 1001
project: my-project
rule_name: mgmt
target_secure_tags: []
target_service_accounts: null
module.firewall-policy.google_compute_network_firewall_policy_rule.net-global["ingress/ssh"]:
action: allow
direction: INGRESS
disabled: false
enable_logging: null
firewall_policy: test-1
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: tcp
ports:
- '22'
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 10.0.0.0/8
src_region_codes: null
src_secure_tags: []
src_threat_intelligences: null
priority: 1002
project: my-project
rule_name: ssh
target_secure_tags: []
target_service_accounts: null
counts:
google_compute_network_firewall_policy: 1
google_compute_network_firewall_policy_association: 1
google_compute_network_firewall_policy_rule: 4

View File

@ -0,0 +1,125 @@
# 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.
values:
module.firewall-policy.google_compute_firewall_policy.hierarchical[0]:
description: null
parent: folders/1234567890
short_name: test-1
module.firewall-policy.google_compute_firewall_policy_association.hierarchical["test"]:
attachment_target: folders/4567890123
name: test-1-test
module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["egress/smtp"]:
action: deny
description: null
direction: EGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges:
- 0.0.0.0/0
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: tcp
ports:
- '25'
src_address_groups: null
src_fqdns: null
src_ip_ranges: null
src_region_codes: null
src_threat_intelligences: null
priority: 900
target_resources: null
target_service_accounts: null
module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["ingress/icmp"]:
action: allow
description: null
direction: INGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: icmp
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 0.0.0.0/0
src_region_codes: null
src_threat_intelligences: null
priority: 1000
target_resources: null
target_service_accounts: null
module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["ingress/mgmt"]:
action: allow
description: null
direction: INGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: all
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 10.1.1.0/24
src_region_codes: null
src_threat_intelligences: null
priority: 1001
target_resources: null
target_service_accounts: null
module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["ingress/ssh"]:
action: allow
description: null
direction: INGRESS
disabled: false
enable_logging: null
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: tcp
ports:
- '22'
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 10.0.0.0/8
src_region_codes: null
src_threat_intelligences: null
priority: 1002
target_resources: null
target_service_accounts: null
counts:
google_compute_firewall_policy: 1
google_compute_firewall_policy_association: 1
google_compute_firewall_policy_rule: 4

View File

@ -0,0 +1,86 @@
# 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.
values:
module.firewall-policy.google_compute_region_network_firewall_policy.net-regional[0]:
name: test-1
project: my-project
region: europe-west8
module.firewall-policy.google_compute_region_network_firewall_policy_association.net-regional["my-vpc"]:
firewall_policy: test-1
name: test-1-my-vpc
project: my-project
region: europe-west8
module.firewall-policy.google_compute_region_network_firewall_policy_rule.net-regional["egress/smtp"]:
action: deny
direction: EGRESS
disabled: false
enable_logging: null
firewall_policy: test-1
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges:
- 0.0.0.0/0
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: tcp
ports:
- '25'
src_address_groups: null
src_fqdns: null
src_ip_ranges: null
src_region_codes: null
src_secure_tags: []
src_threat_intelligences: null
priority: 900
project: my-project
region: europe-west8
rule_name: smtp
target_secure_tags: []
target_service_accounts: null
module.firewall-policy.google_compute_region_network_firewall_policy_rule.net-regional["ingress/icmp"]:
action: allow
direction: INGRESS
disabled: false
enable_logging: null
firewall_policy: test-1
match:
- dest_address_groups: null
dest_fqdns: null
dest_ip_ranges: null
dest_region_codes: null
dest_threat_intelligences: null
layer4_configs:
- ip_protocol: icmp
ports: null
src_address_groups: null
src_fqdns: null
src_ip_ranges:
- 0.0.0.0/0
src_region_codes: null
src_secure_tags: []
src_threat_intelligences: null
priority: 1000
project: my-project
region: europe-west8
rule_name: icmp
target_secure_tags: []
target_service_accounts: null
counts:
google_compute_region_network_firewall_policy: 1
google_compute_region_network_firewall_policy_association: 1
google_compute_region_network_firewall_policy_rule: 2