diff --git a/fast/stages/2-networking/README.md b/fast/stages/2-networking/README.md
index 4b2cd4e91..f16324d60 100644
--- a/fast/stages/2-networking/README.md
+++ b/fast/stages/2-networking/README.md
@@ -284,6 +284,19 @@ nat_config:
# [...]
```
+- **External IPs for NAT:** You can specify how many static IPs do you want, so they would be reserved and kept static. This is helpful when you need to connect to an external service that needs to whitelist the IPs. This is done by defining the number of static IP addresses in the `nat` section of a VPC's `.config.yaml` file.
+
+For example:
+
+```yaml
+# [...]
+nat_config:
+ nat-ew8:
+ num_nat_ips: 3
+ region: $locations:primary
+# [...]
+```
+
- **Cloud Routers:** The `factory-routers.tf` file manages Cloud Routers, which are used with Cloud VPN and Cloud Interconnect to exchange routes with on-premises networks. Routers are configured within each VPC's `.config.yaml` file.
```yaml
@@ -359,7 +372,7 @@ Internally created resources are mapped to context namespaces, and use specific
| name | description | modules | resources |
|---|---|---|---|
-| [factory-cloudnat.tf](./factory-cloudnat.tf) | Cloud NAT factory. | net-cloudnat | |
+| [factory-cloudnat.tf](./factory-cloudnat.tf) | Cloud NAT factory. | net-address · net-cloudnat | |
| [factory-dns.tf](./factory-dns.tf) | DNS zones and RPZ factory. | dns · dns-response-policy | |
| [factory-firewall-policies.tf](./factory-firewall-policies.tf) | Firewall policies factory. | net-firewall-policy | |
| [factory-ncc.tf](./factory-ncc.tf) | NCC Hubs and Groups factory | | google_network_connectivity_group · google_network_connectivity_hub · google_network_connectivity_spoke |
diff --git a/fast/stages/2-networking/factory-cloudnat.tf b/fast/stages/2-networking/factory-cloudnat.tf
index f76508db0..c7c3b5873 100644
--- a/fast/stages/2-networking/factory-cloudnat.tf
+++ b/fast/stages/2-networking/factory-cloudnat.tf
@@ -29,6 +29,8 @@ locals {
config_timeouts = try(nat_config.config_timeouts, {})
endpoint_types = try(nat_config.endpoint_types, null)
logging_filter = try(nat_config.logging_filter, null)
+ num_nat_ips = try(nat_config.num_nat_ips, 0)
+ region = try(nat_config.region, null)
router_asn = try(nat_config.router_asn, null)
router_create = try(nat_config.router_create, true)
router_network = module.vpc-factory.vpcs[vpc_key].id
@@ -40,12 +42,25 @@ locals {
])...)
}
+module "addresses" {
+ source = "../../../modules/net-address"
+ for_each = { for k, v in local.nat_configs : k => v if tonumber(v.num_nat_ips) > 0 }
+ project_id = each.value.project_id
+ external_addresses = {
+ for i in range(tonumber(each.value.num_nat_ips)) : "${each.value.name}-ip-${i}" => { region = each.value.region }
+ }
+ context = merge(local.ctx, {
+ project_ids = local.ctx_projects.project_ids
+ locations = local.ctx.locations
+ })
+}
+
module "nat" {
source = "../../../modules/net-cloudnat"
for_each = local.nat_configs
project_id = each.value.project_id
name = each.value.name
- addresses = each.value.addresses
+ addresses = concat(each.value.addresses, [for a in try(module.addresses[each.key].external_addresses, {}) : a.self_link])
config_port_allocation = each.value.config_port_allocation
config_source_subnetworks = each.value.config_source_subnetworks
config_timeouts = each.value.config_timeouts
@@ -58,8 +73,9 @@ module "nat" {
rules = each.value.rules
type = each.value.type
context = merge(local.ctx, {
- project_ids = local.ctx_projects.project_ids
- vpc_self_links = local.ctx_vpcs.self_links
- locations = local.ctx.locations
+ project_ids = local.ctx_projects.project_ids
+ networks = local.ctx_vpcs.self_links
+ locations = local.ctx.locations
+ subnets = local.ctx_vpcs.subnets_by_vpc
})
}
diff --git a/fast/stages/2-networking/schemas/vpc.schema.json b/fast/stages/2-networking/schemas/vpc.schema.json
index 59289339a..f881d53eb 100644
--- a/fast/stages/2-networking/schemas/vpc.schema.json
+++ b/fast/stages/2-networking/schemas/vpc.schema.json
@@ -197,6 +197,9 @@
"properties": {
"region": {
"type": "string"
+ },
+ "num_nat_ips": {
+ "type": "number"
}
}
}
diff --git a/fast/stages/2-networking/schemas/vpc.schema.md b/fast/stages/2-networking/schemas/vpc.schema.md
index 990e7fa8f..420b32718 100644
--- a/fast/stages/2-networking/schemas/vpc.schema.md
+++ b/fast/stages/2-networking/schemas/vpc.schema.md
@@ -67,6 +67,7 @@
- **nat_config**: *object*
- **`^[a-z0-9-]+$`**: *object*
- ⁺**region**: *string*
+ - **num_nat_ips**: *number*
- **ncc_config**: *object*
- ⁺**hub**: *string*
- **group**: *string*
diff --git a/tests/fast/stages/s2_networking/data-teststatic-ips-nat/defaults.yaml b/tests/fast/stages/s2_networking/data-teststatic-ips-nat/defaults.yaml
new file mode 100644
index 000000000..fdb490446
--- /dev/null
+++ b/tests/fast/stages/s2_networking/data-teststatic-ips-nat/defaults.yaml
@@ -0,0 +1,41 @@
+# 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.
+
+# yaml-language-server: $schema=../../schemas/defaults.schema.json
+
+context:
+ cidr_ranges_sets:
+ healthchecks:
+ - 35.191.0.0/16
+ - 130.211.0.0/22
+ - 209.85.152.0/22
+ - 209.85.204.0/22
+ rfc1918:
+ - 10.0.0.0/8
+ - 172.16.0.0/12
+ - 192.168.0.0/16
+ locations:
+ primary: europe-west1
+ secondary: europe-west3
+ iam_principals: {}
+projects:
+ defaults:
+ locations:
+ storage: eu
+vpcs:
+ auto_create_subnetworks: false
+ delete_default_route_on_create: true
+ mtu: 1500
+output_files:
+ storage_bucket: $storage_buckets:iac-0/iac-outputs
diff --git a/tests/fast/stages/s2_networking/data-teststatic-ips-nat/projects/net-core-0.yaml b/tests/fast/stages/s2_networking/data-teststatic-ips-nat/projects/net-core-0.yaml
new file mode 100644
index 000000000..3e8e97916
--- /dev/null
+++ b/tests/fast/stages/s2_networking/data-teststatic-ips-nat/projects/net-core-0.yaml
@@ -0,0 +1,20 @@
+# skip boilerplate check
+---
+# start of document (---) avoids errors if the file only contains comments
+
+# yaml-language-server: $schema=../../../schemas/project.schema.json
+name: prod-net-core-0
+parent: $folder_ids:networking
+services:
+ - compute.googleapis.com
+ - container.googleapis.com
+ - dns.googleapis.com
+ - iap.googleapis.com
+ - logging.googleapis.com
+ - monitoring.googleapis.com
+ - networkmanagement.googleapis.com
+ - networksecurity.googleapis.com
+ - servicenetworking.googleapis.com
+ - vpcaccess.googleapis.com
+shared_vpc_host_config:
+ enabled: true
diff --git a/tests/fast/stages/s2_networking/data-teststatic-ips-nat/vpcs/core/.config.yaml b/tests/fast/stages/s2_networking/data-teststatic-ips-nat/vpcs/core/.config.yaml
new file mode 100644
index 000000000..3fbea8268
--- /dev/null
+++ b/tests/fast/stages/s2_networking/data-teststatic-ips-nat/vpcs/core/.config.yaml
@@ -0,0 +1,17 @@
+# skip boilerplate check
+---
+# start of document (---) avoids errors if the file only contains comments
+
+# yaml-language-server: $schema=../../../../schemas/vpc.schema.json
+
+project_id: $project_ids:net-core-0
+name: core-0
+delete_default_routes_on_create: true
+nat_config:
+ nat-static:
+ region: $locations:primary
+ num_nat_ips: 3
+ config_source_subnetworks:
+ all: false
+ subnetworks:
+ - self_link: $subnets:core/europe-west1/core-default
diff --git a/tests/fast/stages/s2_networking/data-teststatic-ips-nat/vpcs/core/subnets/core-default.yaml b/tests/fast/stages/s2_networking/data-teststatic-ips-nat/vpcs/core/subnets/core-default.yaml
new file mode 100644
index 000000000..ebe56e9ff
--- /dev/null
+++ b/tests/fast/stages/s2_networking/data-teststatic-ips-nat/vpcs/core/subnets/core-default.yaml
@@ -0,0 +1,8 @@
+# skip boilerplate check
+
+# yaml-language-server: $schema=../../../../../schemas/subnet.schema.json
+
+name: core-default
+region: $locations:primary
+ip_cidr_range: 10.71.0.0/24
+description: Default primary-region subnet for core
diff --git a/tests/fast/stages/s2_networking/static_ips_nat.tfvars b/tests/fast/stages/s2_networking/static_ips_nat.tfvars
new file mode 100644
index 000000000..08e3b00a4
--- /dev/null
+++ b/tests/fast/stages/s2_networking/static_ips_nat.tfvars
@@ -0,0 +1,34 @@
+automation = {
+ outputs_bucket = "test"
+}
+billing_account = {
+ id = "000000-111111-222222"
+}
+factories_config = {
+ dataset = "./data-teststatic-ips-nat"
+ paths = {
+ defaults = "defaults.yaml"
+ }
+}
+folder_ids = {
+ "networking" = "folders/12345678"
+ "networking/prod" = "folders/23456789"
+ "networking/dev" = "folders/34567890"
+}
+organization = {
+ domain = "fast.example.com"
+ id = 123456789012
+ customer_id = "C00000000"
+}
+prefix = "fast"
+service_accounts = {
+ "iac-0/iac-pf-rw" = "iac-pf-rw@test.iam.gserviceaccount.com"
+ "iac-0/iac-pf-ro" = "iac-pf-ro@test.iam.gserviceaccount.com"
+}
+storage_buckets = {
+ "iac-0/iac-outputs" = "test"
+}
+tag_values = {
+ "environment/development" = "tagValues/12345"
+ "environment/production" = "tagValues/12346"
+}
diff --git a/tests/fast/stages/s2_networking/static_ips_nat.yaml b/tests/fast/stages/s2_networking/static_ips_nat.yaml
new file mode 100644
index 000000000..a5a597924
--- /dev/null
+++ b/tests/fast/stages/s2_networking/static_ips_nat.yaml
@@ -0,0 +1,78 @@
+# 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.
+
+counts:
+ google_compute_address: 3
+ google_compute_network: 1
+ google_compute_route: 3
+ google_compute_router: 1
+ google_compute_router_nat: 1
+ google_compute_shared_vpc_host_project: 1
+ google_compute_subnetwork: 1
+ google_logging_project_settings: 1
+ google_project: 1
+ google_project_iam_member: 8
+ google_project_service: 10
+ google_project_service_identity: 8
+ google_storage_bucket_object: 2
+ modules: 7
+ resources: 43
+ terraform_data: 2
+values:
+ module.addresses["core/nat-static"].google_compute_address.external["core-nat-static-ip-0"]:
+ address_type: EXTERNAL
+ description: 'Terraform managed.'
+ name: core-nat-static-ip-0
+ project: fast-prod-net-core-0
+ region: europe-west1
+ module.addresses["core/nat-static"].google_compute_address.external["core-nat-static-ip-1"]:
+ address_type: EXTERNAL
+ description: 'Terraform managed.'
+ name: core-nat-static-ip-1
+ project: fast-prod-net-core-0
+ region: europe-west1
+ module.addresses["core/nat-static"].google_compute_address.external["core-nat-static-ip-2"]:
+ address_type: EXTERNAL
+ description: 'Terraform managed.'
+ name: core-nat-static-ip-2
+ project: fast-prod-net-core-0
+ region: europe-west1
+ module.nat["core/nat-static"].google_compute_router.router[0]:
+ name: core-nat-static-nat
+ project: fast-prod-net-core-0
+ region: europe-west1
+ module.nat["core/nat-static"].google_compute_router_nat.nat:
+ enable_dynamic_port_allocation: False
+ enable_endpoint_independent_mapping: True
+ icmp_idle_timeout_sec: 30
+ log_config:
+ - enable: False
+ filter: ALL
+ max_ports_per_vm: 65536
+ name: core-nat-static
+ nat64_subnetwork: []
+ project: fast-prod-net-core-0
+ region: europe-west1
+ router: core-nat-static-nat
+ rules: []
+ source_subnetwork_ip_ranges_to_nat: LIST_OF_SUBNETWORKS
+ subnetwork:
+ - secondary_ip_range_names: []
+ source_ip_ranges_to_nat:
+ - 'ALL_IP_RANGES'
+ tcp_established_idle_timeout_sec: 1200
+ tcp_time_wait_timeout_sec: 120
+ tcp_transitory_idle_timeout_sec: 30
+ type: PUBLIC
+ udp_idle_timeout_sec: 30
diff --git a/tests/fast/stages/s2_networking/tftest.yaml b/tests/fast/stages/s2_networking/tftest.yaml
index 9b1a487cf..c4aea7a8a 100644
--- a/tests/fast/stages/s2_networking/tftest.yaml
+++ b/tests/fast/stages/s2_networking/tftest.yaml
@@ -22,6 +22,11 @@ tests:
- dns_delegations.yaml
extra_dirs:
- ../../../tests/fast/stages/s2_networking/data-testdns-delegation
+ static_ips_nat:
+ inventory:
+ - static_ips_nat.yaml
+ extra_dirs:
+ - ../../../tests/fast/stages/s2_networking/data-teststatic-ips-nat
ncc:
nva:
vlan_attachments: