Add support for mirroring rules to modules/net-firewall-policy (#3636)

* Add support for mirroring rules to net-firewall-policy

* Split mirroring rules

* Add schema

* Sort variables
This commit is contained in:
Julio Castillo
2026-01-12 12:10:43 +01:00
committed by GitHub
parent c1248d328a
commit 6febcfe136
8 changed files with 622 additions and 26 deletions

View File

@@ -14,6 +14,8 @@ The module also makes fewer assumptions about implicit defaults, only using one
- [Hierarchical Policy](#hierarchical-policy)
- [Global Network policy](#global-network-policy)
- [Regional Network policy](#regional-network-policy)
- [Packet Mirroring Rules](#packet-mirroring-rules)
- [Packet Mirroring Rules](#packet-mirroring-rules)
- [Factory](#factory)
- [Firewall Rule Factory Schema](#firewall-rule-factory-schema)
- [Dynamic Rule Matching](#dynamic-rule-matching)
@@ -168,6 +170,60 @@ module "firewall-policy" {
# tftest modules=2 resources=8 inventory=regional-net.yaml
```
### Packet Mirroring Rules
### Packet Mirroring Rules
Packet mirroring rules can be defined using the `ingress_mirroring_rules` and `egress_mirroring_rules` variables. This is supported only for Global Network Policies.
```hcl
resource "google_network_security_security_profile" "default" {
provider = google-beta
name = "sec-profile"
parent = var.organization_id
type = "CUSTOM_MIRRORING"
custom_mirroring_profile {
mirroring_endpoint_group = "xxx"
}
}
resource "google_network_security_security_profile_group" "default" {
provider = google-beta
name = "sec-profile-group"
parent = var.organization_id
custom_mirroring_profile = google_network_security_security_profile.default.id
}
module "firewall-policy" {
source = "./fabric/modules/net-firewall-policy"
name = "test-mirroring"
parent_id = "my-project"
region = "global"
attachments = {
my-vpc = var.vpc.self_link
}
security_profile_group_ids = {
my-spg = "//networksecurity.googleapis.com/${google_network_security_security_profile_group.default.id}"
}
ingress_mirroring_rules = {
rule-1 = {
priority = 1000
action = "mirror"
description = "Mirror all traffic"
match = {
source_ranges = ["0.0.0.0/0"]
layer4_configs = [
{ protocol = "tcp", ports = ["80", "443"] }
]
}
security_profile_group = "//networksecurity.googleapis.com/${google_network_security_security_profile_group.default.id}"
}
}
}
# tftest inventory=mirroring.yaml
```
### Factory
Similarly to other modules, a rules factory is also included here to allow route management via descriptive configuration files.
@@ -177,6 +233,8 @@ Factory configuration is via three optional attributes in the `rules_factory_con
- `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
- `ingress_mirroring_rules_file_path` specifying the path to the mirroring rules file
- `egress_mirroring_rules_file_path` specifying the path to the mirroring rules file
Factory rules are merged with rules declared in code, with the latter taking precedence where both use the same key.
Also, the factory applies implicit defaults: `action` defaults to `deny` for egress and `allow` for ingress, while omitting `layer4_configs` makes the rule match all protocols.
@@ -260,6 +318,35 @@ issue-1995:
# tftest-file id=ingress path=configs/ingress.yaml schema=firewall-policy-rules.schema.json
```
```yaml
icmp:
priority: 1000
match:
source_ranges:
- 10.0.0.0/8
layer4_configs:
- protocol: icmp
issue-1995:
priority: 10020
description: Allow intra-cluster communication required by k8s networking model
enable_logging: true
target_service_accounts:
- sa-gke-cluster@burner-project.iam.gserviceaccount.com
match:
source_ranges:
- gke-nodes-range
layer4_configs:
- protocol: tcp
ports:
- 1-65535
- protocol: udp
ports:
- 1-65535
- protocol: icmp
# tftest-file id=ingress path=configs/ingress.yaml schema=firewall-policy-rules.schema.json
```
You might need to reference external security profile groups in your firewall rules, using their Terraform ids. For example, `//networksecurity.googleapis.com/${google_network_security_security_profile_group.security_profile_group.id}`. To do so, list your security profile groups in the `security_profile_group_ids` map variable. Then reference them by key from your factories.
```hcl
@@ -272,33 +359,54 @@ module "vpc" {
resource "google_network_security_security_profile" "security_profile" {
name = "security-profile"
type = "THREAT_PREVENTION"
parent = "organizations/0123456789"
parent = var.organization_id
location = "global"
}
resource "google_network_security_security_profile_group" "security_profile_group" {
name = "security-profile-group"
parent = "organizations/0123456789"
parent = var.organization_id
location = "global"
description = "Sample security profile group."
threat_prevention_profile = google_network_security_security_profile.security_profile.id
}
resource "google_network_security_security_profile" "mirror_profile" {
provider = google-beta
name = "sec-profile"
parent = var.organization_id
type = "CUSTOM_MIRRORING"
custom_mirroring_profile {
mirroring_endpoint_group = "xxx"
}
}
resource "google_network_security_security_profile_group" "mirror_profile_group" {
provider = google-beta
name = "sec-profile-group"
parent = var.organization_id
custom_mirroring_profile = google_network_security_security_profile.mirror_profile.id
}
module "firewall-policy" {
source = "./fabric/modules/net-firewall-policy"
name = "fw-policy"
parent_id = "my-project"
security_profile_group_ids = {
http-sg = "//networksecurity.googleapis.com/${google_network_security_security_profile_group.security_profile_group.id}"
http-sg = "//networksecurity.googleapis.com/${google_network_security_security_profile_group.security_profile_group.id}"
mirror-sg = "//networksecurity.googleapis.com/${google_network_security_security_profile_group.mirror_profile_group.id}"
}
attachments = {
my-vpc = module.vpc.self_link
}
factories_config = {
ingress_rules_file_path = "configs/ingress-spg.yaml"
ingress_rules_file_path = "configs/ingress-spg.yaml"
ingress_mirroring_rules_file_path = "configs/mirror-spg.yaml"
}
}
# tftest modules=2 resources=9 files=ingress-spg inventory=factory-spg.yaml
# tftest modules=2 resources=11 files=ingress-spg,mirror-spg inventory=factory-spg.yaml
```
```yaml
@@ -316,6 +424,21 @@ http:
- 80
```
```yaml
# tftest-file id=mirror-spg path=configs/mirror-spg.yaml schema=firewall-policy-mirroring-rules.schema.json
mirror-ssh:
priority: 1000
action: mirror
security_profile_group: mirror-sg
match:
source_ranges:
- 10.0.0.0/8
layer4_configs:
- protocol: tcp
ports:
- 22
```
#### Firewall Rule Factory Schema
The following schema outlines all available fields for defining a rule within a factory YAML file. Use this as a reference, and note the inline comments for fields that apply only to specific policy types.
@@ -386,16 +509,18 @@ The following variable is defined at the top level of the rule (not within the `
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [name](variables.tf#L133) | Policy name. | <code>string</code> | ✓ | |
| [parent_id](variables.tf#L139) | 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> | ✓ | |
| [name](variables.tf#L197) | Policy name. | <code>string</code> | ✓ | |
| [parent_id](variables.tf#L203) | 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> |
| [context](variables.tf#L24) | Context-specific interpolations. | <code title="object&#40;&#123;&#10; cidr_ranges &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; cidr_ranges_sets &#61; optional&#40;map&#40;list&#40;string&#41;&#41;, &#123;&#125;&#41;&#10; folder_ids &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; iam_principals &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; locations &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; networks &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; project_ids &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10; tag_values &#61; optional&#40;map&#40;string&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [description](variables.tf#L40) | Policy description. | <code>string</code> | | <code>null</code> |
| [egress_rules](variables.tf#L46) | List of egress rule definitions, action can be 'allow', 'deny', 'goto_next' or 'apply_security_profile_group'. 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; security_profile_group &#61; optional&#40;string&#41;&#10; target_resources &#61; optional&#40;list&#40;string&#41;&#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; tls_inspect &#61; optional&#40;bool, null&#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> |
| [factories_config](variables.tf#L84) | Paths to folders for the optional factories. | <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> |
| [ingress_rules](variables.tf#L95) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next' or 'apply_security_profile_group'. | <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; security_profile_group &#61; optional&#40;string&#41;&#10; target_resources &#61; optional&#40;list&#40;string&#41;&#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; tls_inspect &#61; optional&#40;bool, null&#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#L145) | Policy region. Leave null for hierarchical policy, set to 'global' for a global network policy. | <code>string</code> | | <code>null</code> |
| [security_profile_group_ids](variables.tf#L151) | The optional security groups ids to be referenced in factories. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [egress_mirroring_rules](variables.tf#L46) | List of egress packet mirroring rule definitions, action can be 'mirror', 'do_not_mirror', or 'goto_next'. | <code title="map&#40;object&#40;&#123;&#10; priority &#61; number&#10; action &#61; optional&#40;string, &#34;mirror&#34;&#41;&#10; description &#61; optional&#40;string&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; security_profile_group &#61; optional&#40;string&#41;&#10; target_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; tls_inspect &#61; optional&#40;bool, null&#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> |
| [egress_rules](variables.tf#L77) | List of egress rule definitions, action can be 'allow', 'deny', 'goto_next' or 'apply_security_profile_group'. 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; security_profile_group &#61; optional&#40;string&#41;&#10; target_resources &#61; optional&#40;list&#40;string&#41;&#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; tls_inspect &#61; optional&#40;bool, null&#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> |
| [factories_config](variables.tf#L115) | Paths to folders for the optional factories. | <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; ingress_mirroring_rules_file_path &#61; optional&#40;string&#41;&#10; egress_mirroring_rules_file_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [ingress_mirroring_rules](variables.tf#L128) | List of ingress packet mirroring rule definitions, action can be 'mirror', 'do_not_mirror', or 'goto_next'. | <code title="map&#40;object&#40;&#123;&#10; priority &#61; number&#10; action &#61; optional&#40;string, &#34;mirror&#34;&#41;&#10; description &#61; optional&#40;string&#41;&#10; disabled &#61; optional&#40;bool, false&#41;&#10; security_profile_group &#61; optional&#40;string&#41;&#10; target_tags &#61; optional&#40;list&#40;string&#41;&#41;&#10; tls_inspect &#61; optional&#40;bool, null&#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#L159) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next' or 'apply_security_profile_group'. | <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; security_profile_group &#61; optional&#40;string&#41;&#10; target_resources &#61; optional&#40;list&#40;string&#41;&#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; tls_inspect &#61; optional&#40;bool, null&#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#L209) | Policy region. Leave null for hierarchical policy, set to 'global' for a global network policy. | <code>string</code> | | <code>null</code> |
| [security_profile_group_ids](variables.tf#L215) | The optional security groups ids to be referenced in factories. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
## Outputs

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2024 Google LLC
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,16 @@ locals {
yamldecode(file(pathexpand(var.factories_config.ingress_rules_file_path))),
{}), tomap({})
)
_factory_mirroring_rules_egress = coalesce(
try(
yamldecode(file(pathexpand(var.factories_config.egress_mirroring_rules_file_path))),
{}), tomap({})
)
_factory_mirroring_rules_ingress = coalesce(
try(
yamldecode(file(pathexpand(var.factories_config.ingress_mirroring_rules_file_path))),
{}), tomap({})
)
factory_cidrs = coalesce(
try(
yamldecode(file(pathexpand(var.factories_config.cidr_file_path))),
@@ -124,4 +134,84 @@ locals {
}
}
}
factory_mirroring_ingress_rules = {
for k, v in local._factory_mirroring_rules_ingress : "mirror/ingress/${k}" => {
direction = "INGRESS"
name = k
priority = v.priority
action = lookup(v, "action", "mirror")
description = lookup(v, "description", null)
disabled = lookup(v, "disabled", false)
security_profile_group = lookup(v, "security_profile_group", null)
target_tags = lookup(v, "target_tags", null)
tls_inspect = lookup(v, "tls_inspect", null)
match = {
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 = [] }, c)
]
)
}
}
}
factory_mirroring_egress_rules = {
for k, v in local._factory_mirroring_rules_egress : "mirror/egress/${k}" => {
direction = "EGRESS"
name = k
priority = v.priority
action = lookup(v, "action", "mirror")
description = lookup(v, "description", null)
disabled = lookup(v, "disabled", false)
security_profile_group = lookup(v, "security_profile_group", null)
target_tags = lookup(v, "target_tags", null)
tls_inspect = lookup(v, "tls_inspect", null)
match = {
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 = [] }, c)
]
)
}
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2023 Google LLC
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,14 @@ locals {
for name, rule in merge(var.ingress_rules) :
"ingress/${name}" => merge(rule, { name = name, direction = "INGRESS" })
}
_mirroring_rules_egress = {
for name, rule in merge(var.egress_mirroring_rules) :
"mirror/egress/${name}" => merge(rule, { name = name, direction = "EGRESS" })
}
_mirroring_rules_ingress = {
for name, rule in merge(var.ingress_mirroring_rules) :
"mirror/ingress/${name}" => merge(rule, { name = name, direction = "INGRESS" })
}
ctx = {
for k, v in var.context : k => {
for kk, vv in v : "${local.ctx_p}${k}:${kk}" => vv
@@ -33,6 +41,10 @@ locals {
local.factory_egress_rules, local.factory_ingress_rules,
local._rules_egress, local._rules_ingress
)
mirroring_rules = merge(
local.factory_mirroring_egress_rules, local.factory_mirroring_ingress_rules,
local._mirroring_rules_egress, local._mirroring_rules_ingress
)
# do not depend on the parent id as that might be dynamic and prevent count
use_hierarchical = var.region == null
use_regional = !local.use_hierarchical && var.region != "global"

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2024 Google LLC
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -146,3 +146,71 @@ resource "google_compute_network_firewall_policy_rule" "net-global" {
}
}
}
resource "google_compute_network_firewall_policy_packet_mirroring_rule" "net-global" {
provider = google-beta
for_each = toset(
!local.use_hierarchical && !local.use_regional
? keys(local.mirroring_rules)
: []
)
project = lookup(local.ctx.project_ids, var.parent_id, var.parent_id)
firewall_policy = google_compute_network_firewall_policy.net-global[0].name
rule_name = local.mirroring_rules[each.key].name
action = local.mirroring_rules[each.key].action
description = local.mirroring_rules[each.key].description
direction = local.mirroring_rules[each.key].direction
disabled = local.mirroring_rules[each.key].disabled
priority = local.mirroring_rules[each.key].priority
tls_inspect = local.mirroring_rules[each.key].tls_inspect
security_profile_group = try(
var.security_profile_group_ids[local.mirroring_rules[each.key].security_profile_group],
local.mirroring_rules[each.key].security_profile_group
)
match {
dest_ip_ranges = (
local.mirroring_rules[each.key].match.destination_ranges == null
? null
: distinct(flatten([
for r in local.mirroring_rules[each.key].match.destination_ranges : try(
local.ctx.cidr_ranges_sets[r],
local.ctx.cidr_ranges[r],
r
)
]))
)
src_ip_ranges = (
local.mirroring_rules[each.key].match.source_ranges == null
? null
: distinct(flatten([
for r in local.mirroring_rules[each.key].match.source_ranges : try(
local.ctx.cidr_ranges_sets[r],
local.ctx.cidr_ranges[r],
r
)
]))
)
dynamic "layer4_configs" {
for_each = local.mirroring_rules[each.key].match.layer4_configs
content {
ip_protocol = layer4_configs.value.protocol
ports = layer4_configs.value.ports
}
}
}
dynamic "target_secure_tags" {
for_each = toset(
local.mirroring_rules[each.key].target_tags == null
? []
: local.mirroring_rules[each.key].target_tags
)
content {
name = lookup(
local.ctx.tag_values, target_secure_tags.value, target_secure_tags.value
)
}
}
}

View File

@@ -0,0 +1,90 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Firewall Policy Mirroring Rules",
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-z0-9_-]+$": {
"$ref": "#/$defs/rule"
}
},
"$defs": {
"rule": {
"type": "object",
"additionalProperties": false,
"required": [
"priority"
],
"properties": {
"priority": {
"type": "number"
},
"action": {
"type": "string",
"enum": [
"mirror",
"do_not_mirror",
"goto_next"
]
},
"description": {
"type": "string"
},
"disabled": {
"type": "boolean"
},
"security_profile_group": {
"type": "string"
},
"target_tags": {
"type": "array",
"items": {
"type": "string"
}
},
"tls_inspect": {
"type": "boolean"
},
"match": {
"type": "object",
"additionalProperties": false,
"properties": {
"destination_ranges": {
"type": "array",
"items": {
"type": "string"
}
},
"source_ranges": {
"type": "array",
"items": {
"type": "string"
}
},
"source_tags": {
"type": "array",
"items": {
"type": "string"
}
},
"layer4_configs": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"protocol": {
"type": "string"
},
"ports": {
"type": "array"
}
}
}
}
}
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2024 Google LLC
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,6 +43,37 @@ variable "description" {
default = null
}
variable "egress_mirroring_rules" {
description = "List of egress packet mirroring rule definitions, action can be 'mirror', 'do_not_mirror', or 'goto_next'."
type = map(object({
priority = number
action = optional(string, "mirror")
description = optional(string)
disabled = optional(bool, false)
security_profile_group = optional(string)
target_tags = optional(list(string))
tls_inspect = optional(bool, null)
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_mirroring_rules :
contains(["mirror", "do_not_mirror", "goto_next"], v.action)
])
error_message = "Action can only be one of 'mirror', 'do_not_mirror' or 'goto_next'."
}
}
variable "egress_rules" {
description = "List of egress rule definitions, action can be 'allow', 'deny', 'goto_next' or 'apply_security_profile_group'. The match.layer4configs map is in protocol => optional [ports] format."
type = map(object({
@@ -84,14 +115,47 @@ variable "egress_rules" {
variable "factories_config" {
description = "Paths to folders for the optional factories."
type = object({
cidr_file_path = optional(string)
egress_rules_file_path = optional(string)
ingress_rules_file_path = optional(string)
cidr_file_path = optional(string)
egress_rules_file_path = optional(string)
ingress_rules_file_path = optional(string)
ingress_mirroring_rules_file_path = optional(string)
egress_mirroring_rules_file_path = optional(string)
})
nullable = false
default = {}
}
variable "ingress_mirroring_rules" {
description = "List of ingress packet mirroring rule definitions, action can be 'mirror', 'do_not_mirror', or 'goto_next'."
type = map(object({
priority = number
action = optional(string, "mirror")
description = optional(string)
disabled = optional(bool, false)
security_profile_group = optional(string)
target_tags = optional(list(string))
tls_inspect = optional(bool, null)
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_mirroring_rules :
contains(["mirror", "do_not_mirror", "goto_next"], v.action)
])
error_message = "Action can only be one of 'mirror', 'do_not_mirror' or 'goto_next'."
}
}
variable "ingress_rules" {
description = "List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next' or 'apply_security_profile_group'."
type = map(object({

View File

@@ -1,4 +1,4 @@
# Copyright 2024 Google LLC
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,26 +13,74 @@
# limitations under the License.
values:
google_network_security_security_profile.security_profile:
google_network_security_security_profile.mirror_profile:
custom_intercept_profile: []
custom_mirroring_profile:
- mirroring_deployment_groups: null
mirroring_endpoint_group: xxx
description: null
effective_labels:
goog-terraform-provisioned: 'true'
labels: null
location: global
name: sec-profile
parent: organizations/1122334455
terraform_labels:
goog-terraform-provisioned: 'true'
threat_prevention_profile: []
timeouts: null
type: CUSTOM_MIRRORING
url_filtering_profile: []
google_network_security_security_profile.security_profile:
custom_intercept_profile: []
custom_mirroring_profile: []
description: null
effective_labels:
goog-terraform-provisioned: 'true'
labels: null
location: global
name: security-profile
parent: organizations/0123456789
parent: organizations/1122334455
terraform_labels:
goog-terraform-provisioned: 'true'
threat_prevention_profile: []
timeouts: null
type: THREAT_PREVENTION
google_network_security_security_profile_group.mirror_profile_group:
custom_intercept_profile: null
description: null
effective_labels:
goog-terraform-provisioned: 'true'
labels: null
location: global
name: sec-profile-group
parent: organizations/1122334455
terraform_labels:
goog-terraform-provisioned: 'true'
threat_prevention_profile: null
timeouts: null
url_filtering_profile: null
google_network_security_security_profile_group.security_profile_group:
custom_intercept_profile: null
custom_mirroring_profile: null
description: Sample security profile group.
effective_labels:
goog-terraform-provisioned: 'true'
labels: null
location: global
name: security-profile-group
parent: organizations/0123456789
parent: organizations/1122334455
terraform_labels:
goog-terraform-provisioned: 'true'
timeouts: null
module.firewall-policy.google_compute_firewall_policy.hierarchical[0]:
description: null
parent: my-project
short_name: fw-policy
timeouts: null
module.firewall-policy.google_compute_firewall_policy_association.hierarchical["my-vpc"]:
name: fw-policy-my-vpc
timeouts: null
module.firewall-policy.google_compute_firewall_policy_rule.hierarchical["ingress/http"]:
action: apply_security_profile_group
description: null
@@ -54,20 +102,27 @@ values:
src_ip_ranges:
- 10.0.0.0/8
src_region_codes: null
src_secure_tags: []
src_threat_intelligences: null
priority: 1000
target_resources: null
target_secure_tags: []
target_service_accounts: null
timeouts: null
tls_inspect: null
module.vpc.google_compute_network.network[0]:
auto_create_subnetworks: false
delete_bgp_always_compare_med: false
delete_default_routes_on_create: false
description: Terraform-managed.
enable_ula_internal_ipv6: null
name: my-network
network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL
network_profile: null
params: []
project: my-project
routing_mode: GLOBAL
timeouts: null
module.vpc.google_compute_route.gateway["private-googleapis"]:
description: Terraform-managed.
dest_range: 199.36.153.8/30
@@ -77,9 +132,11 @@ values:
next_hop_ilb: null
next_hop_instance: null
next_hop_vpn_tunnel: null
params: []
priority: 1000
project: my-project
tags: null
timeouts: null
module.vpc.google_compute_route.gateway["restricted-googleapis"]:
description: Terraform-managed.
dest_range: 199.36.153.4/30
@@ -89,9 +146,11 @@ values:
next_hop_ilb: null
next_hop_instance: null
next_hop_vpn_tunnel: null
params: []
priority: 1000
project: my-project
tags: null
timeouts: null
counts:
google_compute_firewall_policy: 1
@@ -99,7 +158,7 @@ counts:
google_compute_firewall_policy_rule: 1
google_compute_network: 1
google_compute_route: 3
google_network_security_security_profile: 1
google_network_security_security_profile_group: 1
google_network_security_security_profile: 2
google_network_security_security_profile_group: 2
modules: 2
resources: 9
resources: 11

View File

@@ -0,0 +1,88 @@
# Copyright 2026 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:
google_network_security_security_profile.default:
custom_intercept_profile: []
custom_mirroring_profile:
- mirroring_deployment_groups: null
mirroring_endpoint_group: xxx
description: null
effective_labels:
goog-terraform-provisioned: 'true'
labels: null
location: global
name: sec-profile
parent: organizations/1122334455
terraform_labels:
goog-terraform-provisioned: 'true'
threat_prevention_profile: []
timeouts: null
type: CUSTOM_MIRRORING
url_filtering_profile: []
google_network_security_security_profile_group.default:
custom_intercept_profile: null
description: null
effective_labels:
goog-terraform-provisioned: 'true'
labels: null
location: global
name: sec-profile-group
parent: organizations/1122334455
terraform_labels:
goog-terraform-provisioned: 'true'
threat_prevention_profile: null
timeouts: null
url_filtering_profile: null
module.firewall-policy.google_compute_network_firewall_policy.net-global[0]:
description: null
name: test-mirroring
project: my-project
timeouts: null
module.firewall-policy.google_compute_network_firewall_policy_association.net-global["my-vpc"]:
attachment_target: https://www.googleapis.com/compute/v1/projects/xxx/global/networks/aaa
firewall_policy: test-mirroring
name: test-mirroring-my-vpc
project: my-project
timeouts: null
module.firewall-policy.google_compute_network_firewall_policy_packet_mirroring_rule.net-global["mirror/ingress/rule-1"]:
action: mirror
description: Mirror all traffic
direction: INGRESS
disabled: false
firewall_policy: test-mirroring
match:
- dest_ip_ranges: null
layer4_configs:
- ip_protocol: tcp
ports:
- '80'
- '443'
src_ip_ranges:
- 0.0.0.0/0
priority: 1000
project: my-project
rule_name: rule-1
target_secure_tags: []
timeouts: null
tls_inspect: null
counts:
google_compute_network_firewall_policy: 1
google_compute_network_firewall_policy_association: 1
google_compute_network_firewall_policy_packet_mirroring_rule: 1
google_network_security_security_profile: 1
google_network_security_security_profile_group: 1
modules: 1
resources: 5