Skip to content

Commit

Permalink
Refactor firewall policy module (#1576)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
ludoo and juliocc authored Aug 8, 2023
1 parent 8917333 commit 80ada0e
Show file tree
Hide file tree
Showing 12 changed files with 1,132 additions and 167 deletions.
202 changes: 180 additions & 22 deletions modules/net-vpc-firewall-policy/README.md
Original file line number Diff line number Diff line change
@@ -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" {
Expand All @@ -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 = {
Expand Down Expand Up @@ -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 -->
119 changes: 119 additions & 0 deletions modules/net-vpc-firewall-policy/factory.tf
Original file line number Diff line number Diff line change
@@ -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)
]
)
}
}
}
}
Loading

0 comments on commit 80ada0e

Please sign in to comment.