diff --git a/modules/net-vpc-firewall-policy/README.md b/modules/net-vpc-firewall-policy/README.md
index a24561386f..dd2b206418 100644
--- a/modules/net-vpc-firewall-policy/README.md
+++ b/modules/net-vpc-firewall-policy/README.md
@@ -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
```
-
+### 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
+```
+
+
## Variables
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
-| [name](variables.tf#L98) | Policy name. | string
| ✓ | |
-| [project_id](variables.tf#L104) | Project id of the project that holds the network. | string
| ✓ | |
-| [description](variables.tf#L17) | Policy description. | string
| | null
|
-| [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. | map(object({…}))
| | {}
|
-| [ingress_rules](variables.tf#L60) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next'. | map(object({…}))
| | {}
|
-| [region](variables.tf#L110) | Policy region. Leave null for global policy. | string
| | null
|
-| [target_vpcs](variables.tf#L116) | VPC ids to which this policy will be attached, in descriptive name => self link format. | map(string)
| | {}
|
+| [name](variables.tf#L113) | Policy name. | string
| ✓ | |
+| [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. | string
| ✓ | |
+| [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. | map(string)
| | {}
|
+| [description](variables.tf#L24) | Policy description. | string
| | null
|
+| [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. | map(object({…}))
| | {}
|
+| [ingress_rules](variables.tf#L71) | List of ingress rule definitions, action can be 'allow', 'deny', 'goto_next'. | map(object({…}))
| | {}
|
+| [region](variables.tf#L125) | Policy region. Leave null for hierarchical policy, or global network policy. | string
| | null
|
+| [rules_factory_config](variables.tf#L131) | Configuration for the optional rules factory. | object({…})
| | {}
|
## Outputs
| name | description | sensitive |
|---|---|:---:|
| [id](outputs.tf#L17) | Fully qualified firewall policy id. | |
-
diff --git a/modules/net-vpc-firewall-policy/factory.tf b/modules/net-vpc-firewall-policy/factory.tf
new file mode 100644
index 0000000000..a0e655c75b
--- /dev/null
+++ b/modules/net-vpc-firewall-policy/factory.tf
@@ -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)
+ ]
+ )
+ }
+ }
+ }
+}
diff --git a/modules/net-vpc-firewall-policy/hierarchical.tf b/modules/net-vpc-firewall-policy/hierarchical.tf
new file mode 100644
index 0000000000..6c1decdcf5
--- /dev/null
+++ b/modules/net-vpc-firewall-policy/hierarchical.tf
@@ -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
+ }
+ }
+ }
+}
diff --git a/modules/net-vpc-firewall-policy/main.tf b/modules/net-vpc-firewall-policy/main.tf
index 190b209316..f253e2219b 100644
--- a/modules/net-vpc-firewall-policy/main.tf
+++ b/modules/net-vpc-firewall-policy/main.tf
@@ -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
}
diff --git a/modules/net-vpc-firewall-policy/net-global.tf b/modules/net-vpc-firewall-policy/net-global.tf
new file mode 100644
index 0000000000..685c860514
--- /dev/null
+++ b/modules/net-vpc-firewall-policy/net-global.tf
@@ -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
+ }
+ }
+}
diff --git a/modules/net-vpc-firewall-policy/net-regional.tf b/modules/net-vpc-firewall-policy/net-regional.tf
new file mode 100644
index 0000000000..a77b30f3e1
--- /dev/null
+++ b/modules/net-vpc-firewall-policy/net-regional.tf
@@ -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
+ }
+ }
+}
diff --git a/modules/net-vpc-firewall-policy/outputs.tf b/modules/net-vpc-firewall-policy/outputs.tf
index 284dc96314..44c0a7a6a0 100644
--- a/modules/net-vpc-firewall-policy/outputs.tf
+++ b/modules/net-vpc-firewall-policy/outputs.tf
@@ -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)
+ ])
}
diff --git a/modules/net-vpc-firewall-policy/variables.tf b/modules/net-vpc-firewall-policy/variables.tf
index 8465f9444c..a1694d50c9 100644
--- a/modules/net-vpc-firewall-policy/variables.tf
+++ b/modules/net-vpc-firewall-policy/variables.tf
@@ -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 = {}
}
diff --git a/tests/modules/net_vpc_firewall_policy/examples/factory.yaml b/tests/modules/net_vpc_firewall_policy/examples/factory.yaml
new file mode 100644
index 0000000000..b3709fe631
--- /dev/null
+++ b/tests/modules/net_vpc_firewall_policy/examples/factory.yaml
@@ -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
diff --git a/tests/modules/net_vpc_firewall_policy/examples/global-net.yaml b/tests/modules/net_vpc_firewall_policy/examples/global-net.yaml
new file mode 100644
index 0000000000..35b7e4eda5
--- /dev/null
+++ b/tests/modules/net_vpc_firewall_policy/examples/global-net.yaml
@@ -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
diff --git a/tests/modules/net_vpc_firewall_policy/examples/hierarchical.yaml b/tests/modules/net_vpc_firewall_policy/examples/hierarchical.yaml
new file mode 100644
index 0000000000..2b0c4e3146
--- /dev/null
+++ b/tests/modules/net_vpc_firewall_policy/examples/hierarchical.yaml
@@ -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
diff --git a/tests/modules/net_vpc_firewall_policy/examples/regional-net.yaml b/tests/modules/net_vpc_firewall_policy/examples/regional-net.yaml
new file mode 100644
index 0000000000..326973ba8c
--- /dev/null
+++ b/tests/modules/net_vpc_firewall_policy/examples/regional-net.yaml
@@ -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