diff --git a/modules/net-vpc/README.md b/modules/net-vpc/README.md
index a2fdde4b9..d5b7b79a6 100644
--- a/modules/net-vpc/README.md
+++ b/modules/net-vpc/README.md
@@ -17,6 +17,7 @@ This module allows creation and management of VPC networks including subnetworks
- [DNS Policies](#dns-policies)
- [Subnet Factory](#subnet-factory)
- [Custom Routes](#custom-routes)
+ - [Policy Based Routes](#policy-based-routes)
- [Private Google Access routes](#private-google-access-routes)
- [Allow Firewall Policy to be evaluated before Firewall Rules](#allow-firewall-policy-to-be-evaluated-before-firewall-rules)
- [IPv6](#ipv6)
@@ -455,6 +456,40 @@ module "vpc" {
# tftest modules=5 resources=15 inventory=routes.yaml
```
+### Policy Based Routes
+
+Policy based routes can be configured through the `policy_based_routes` variable.
+
+```hcl
+module "vpc" {
+ source = "./fabric/modules/net-vpc"
+ project_id = var.project_id
+ name = "my-vpc"
+ policy_based_routes = {
+ skip-pbr-for-nva = {
+ use_default_routing = true
+ priority = 100
+ target = {
+ tags = ["nva"]
+ }
+ }
+ send-all-to-nva = {
+ next_hop_ilb_ip = "10.0.0.253"
+ priority = 101
+ filter = {
+ src_range = "10.0.0.0/8"
+ dest_range = "0.0.0.0/0"
+ }
+ target = {
+ interconnect_attachment = "europe-west8"
+ }
+ }
+ }
+ create_googleapis_routes = null
+}
+# tftest modules=1 resources=3 inventory=pbr.yaml
+```
+
### Private Google Access routes
By default the VPC module creates IPv4 routes for the [Private Google Access ranges](https://cloud.google.com/vpc/docs/configure-private-google-access#config-routing). This behavior can be controlled through the `create_googleapis_routes` variable:
@@ -541,7 +576,7 @@ module "vpc" {
| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [name](variables.tf#L95) | The name of the network being created. | string | ✓ | |
-| [project_id](variables.tf#L111) | The ID of the project where this VPC will be created. | string | ✓ | |
+| [project_id](variables.tf#L159) | The ID of the project where this VPC will be created. | string | ✓ | |
| [auto_create_subnetworks](variables.tf#L17) | Set to true to create an auto mode subnet, defaults to custom mode. | bool | | false |
| [create_googleapis_routes](variables.tf#L23) | Toggle creation of googleapis private/restricted routes. Disabled when vpc creation is turned off, or when set to null. | object({…}) | | {} |
| [delete_default_routes_on_create](variables.tf#L34) | Set to true to delete the default routes at creation time. | bool | | false |
@@ -552,15 +587,16 @@ module "vpc" {
| [ipv6_config](variables.tf#L79) | Optional IPv6 configuration for this network. | object({…}) | | {} |
| [mtu](variables.tf#L89) | Maximum Transmission Unit in bytes. The minimum value for this field is 1460 (the default) and the maximum value is 1500 bytes. | number | | null |
| [peering_config](variables.tf#L100) | VPC peering configuration. | object({…}) | | null |
-| [psa_config](variables.tf#L116) | The Private Service Access configuration for Service Networking. | object({…}) | | null |
-| [routes](variables.tf#L127) | Network routes, keyed by name. | map(object({…})) | | {} |
-| [routing_mode](variables.tf#L148) | The network routing mode (default 'GLOBAL'). | string | | "GLOBAL" |
-| [shared_vpc_host](variables.tf#L158) | Enable shared VPC for this project. | bool | | false |
-| [shared_vpc_service_projects](variables.tf#L164) | Shared VPC service projects to register with this host. | list(string) | | [] |
-| [subnets](variables.tf#L170) | Subnet configuration. | list(object({…})) | | [] |
-| [subnets_proxy_only](variables.tf#L217) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] |
-| [subnets_psc](variables.tf#L251) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] |
-| [vpc_create](variables.tf#L283) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true |
+| [policy_based_routes](variables.tf#L111) | Policy based routes, keyed by name. | map(object({…})) | | {} |
+| [psa_config](variables.tf#L164) | The Private Service Access configuration for Service Networking. | object({…}) | | null |
+| [routes](variables.tf#L175) | Network routes, keyed by name. | map(object({…})) | | {} |
+| [routing_mode](variables.tf#L196) | The network routing mode (default 'GLOBAL'). | string | | "GLOBAL" |
+| [shared_vpc_host](variables.tf#L206) | Enable shared VPC for this project. | bool | | false |
+| [shared_vpc_service_projects](variables.tf#L212) | Shared VPC service projects to register with this host. | list(string) | | [] |
+| [subnets](variables.tf#L218) | Subnet configuration. | list(object({…})) | | [] |
+| [subnets_proxy_only](variables.tf#L265) | List of proxy-only subnets for Regional HTTPS or Internal HTTPS load balancers. Note: Only one proxy-only subnet for each VPC network in each region can be active. | list(object({…})) | | [] |
+| [subnets_psc](variables.tf#L299) | List of subnets for Private Service Connect service producers. | list(object({…})) | | [] |
+| [vpc_create](variables.tf#L331) | Create VPC. When set to false, uses a data source to reference existing VPC. | bool | | true |
## Outputs
diff --git a/modules/net-vpc/routes.tf b/modules/net-vpc/routes.tf
index 065ea5fda..be16bb936 100644
--- a/modules/net-vpc/routes.tf
+++ b/modules/net-vpc/routes.tf
@@ -108,3 +108,32 @@ resource "google_compute_route" "vpn_tunnel" {
tags = each.value.tags
next_hop_vpn_tunnel = each.value.next_hop
}
+
+resource "google_network_connectivity_policy_based_route" "default" {
+ for_each = var.policy_based_routes
+ project = var.project_id
+ network = local.network.id
+ name = "${var.name}-${each.key}"
+ description = each.value.description
+ priority = each.value.priority
+ next_hop_other_routes = each.value.use_default_routing ? "DEFAULT_ROUTING" : null
+ next_hop_ilb_ip = each.value.use_default_routing ? null : each.value.next_hop_ilb_ip
+ filter {
+ protocol_version = "IPV4"
+ ip_protocol = each.value.filter.ip_protocol
+ dest_range = each.value.filter.dest_range
+ src_range = each.value.filter.src_range
+ }
+ dynamic "virtual_machine" {
+ for_each = each.value.target.tags != null ? [""] : []
+ content {
+ tags = each.value.target.tags
+ }
+ }
+ dynamic "interconnect_attachment" {
+ for_each = each.value.target.interconnect_attachment != null ? [""] : []
+ content {
+ region = each.value.target.interconnect_attachment
+ }
+ }
+}
diff --git a/modules/net-vpc/variables.tf b/modules/net-vpc/variables.tf
index f463470d1..06b95209c 100644
--- a/modules/net-vpc/variables.tf
+++ b/modules/net-vpc/variables.tf
@@ -108,6 +108,54 @@ variable "peering_config" {
default = null
}
+variable "policy_based_routes" {
+ description = "Policy based routes, keyed by name."
+ type = map(object({
+ description = optional(string, "Terraform-managed.")
+ labels = optional(map(string))
+ priority = optional(number)
+ next_hop_ilb_ip = optional(string)
+ use_default_routing = optional(bool, false)
+ filter = optional(object({
+ ip_protocol = optional(string)
+ dest_range = optional(string)
+ src_range = optional(string)
+ }), {})
+ target = optional(object({
+ interconnect_attachment = optional(string)
+ tags = optional(list(string))
+ }), {})
+ }))
+ default = {}
+ nullable = false
+ validation {
+ condition = alltrue([
+ for r in var.policy_based_routes :
+ contains(["TCP", "UDP", "ALL", null], r.filter.ip_protocol)
+ if r.filter.ip_protocol != null
+ ])
+ error_message = "Unsupported protocol for route."
+ }
+ validation {
+ condition = alltrue([
+ for r in var.policy_based_routes :
+ (
+ (r.use_default_routing == true ? 1 : 0)
+ +
+ (r.next_hop_ilb_ip != null ? 1 : 0)
+ ) == 1
+ ])
+ error_message = "Either set `use_default_routing = true` or specify an internal passthrough LB IP."
+ }
+ validation {
+ condition = alltrue([
+ for r in var.policy_based_routes :
+ r.target.tags == null || r.target.interconnect_attachment == null
+ ])
+ error_message = "Either use virtual machine tags or a vlan attachment region as a target."
+ }
+}
+
variable "project_id" {
description = "The ID of the project where this VPC will be created."
type = string
diff --git a/tests/modules/net_vpc/examples/pbr.yaml b/tests/modules/net_vpc/examples/pbr.yaml
new file mode 100644
index 000000000..4255e4e66
--- /dev/null
+++ b/tests/modules/net_vpc/examples/pbr.yaml
@@ -0,0 +1,68 @@
+# 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.vpc.google_compute_network.network[0]:
+ auto_create_subnetworks: false
+ delete_default_routes_on_create: false
+ description: Terraform-managed.
+ enable_ula_internal_ipv6: null
+ name: my-vpc
+ network_firewall_policy_enforcement_order: AFTER_CLASSIC_FIREWALL
+ project: project-id
+ routing_mode: GLOBAL
+ timeouts: null
+ ? module.vpc.google_network_connectivity_policy_based_route.default["send-all-to-nva"]
+ : description: Terraform-managed.
+ filter:
+ - dest_range: 0.0.0.0/0
+ ip_protocol: ALL
+ protocol_version: IPV4
+ src_range: 10.0.0.0/8
+ interconnect_attachment:
+ - region: europe-west8
+ labels: null
+ name: my-vpc-send-all-to-nva
+ next_hop_ilb_ip: 10.0.0.253
+ next_hop_other_routes: null
+ priority: 101
+ project: project-id
+ timeouts: null
+ virtual_machine: []
+ ? module.vpc.google_network_connectivity_policy_based_route.default["skip-pbr-for-nva"]
+ : description: Terraform-managed.
+ filter:
+ - dest_range: 0.0.0.0/0
+ ip_protocol: ALL
+ protocol_version: IPV4
+ src_range: 0.0.0.0/0
+ interconnect_attachment: []
+ labels: null
+ name: my-vpc-skip-pbr-for-nva
+ next_hop_ilb_ip: null
+ next_hop_other_routes: DEFAULT_ROUTING
+ priority: 100
+ project: project-id
+ timeouts: null
+ virtual_machine:
+ - tags:
+ - nva
+
+counts:
+ google_compute_network: 1
+ google_network_connectivity_policy_based_route: 2
+ modules: 1
+ resources: 3
+
+outputs: {}