diff --git a/fast/stages/02-networking-nva/README.md b/fast/stages/02-networking-nva/README.md new file mode 100644 index 000000000..fb6286e49 --- /dev/null +++ b/fast/stages/02-networking-nva/README.md @@ -0,0 +1,350 @@ +# Networking with Network Virtual Appliance + +This stage sets up the shared network infrastructure for the whole organization. +It is an alternative to the [02-networking stage](../02-networking/README.md). + +It is designed for those who would like to leverage Network Virtual Appliances (NVAs) between trusted and untrusted areas of the network, for example for Intrusion Prevention System (IPS) purposes. + +It adopts the common “hub and spoke” reference design, which is well suited for multiple scenarios, and it offers several advantages versus other designs: + +- the "trusted hub" VPC centralizes the external connectivity towards trusted network resources (e.g. on-prem, other cloud environments and the spokes), and it is ready to host cross-environment services like CI/CD, code repositories, and monitoring probes +- the "spoke" VPCs allow partitioning workloads (e.g. by environment like in this setup), while still retaining controlled access to central connectivity and services +- Shared VPCs -both in hub and spokes- split the management of the network resources into specific (host) projects, while still allowing them to be consumed from the workload (service) projects +- the design facilitates DNS centralization + +Connectivity between the hub and the spokes is established via [VPC network peerings](https://cloud.google.com/vpc/docs/vpc-peering), which offer uncapped bandwidth, lower latencies, at no additional costs and with a very low management overhead. Different ways of implementing connectivity, and related some pros and cons, are discussed below. + +The diagram shows the high-level design and it should be used as a reference throughout the following sections. + +The final number of subnets, and their IP addressing will depend on the user-specific requirements. It can be easily changed via variables or external data files, without any need to edit the code. + +
+
+
dns | |
+| [dns-landing.tf](./dns-landing.tf) | Landing DNS zones and peerings setup. | dns | |
+| [dns-prod.tf](./dns-prod.tf) | Production spoke DNS zones and peerings setup. | dns | |
+| [main.tf](./main.tf) | Networking folder and hierarchical policy. | folder | |
+| [monitoring.tf](./monitoring.tf) | Network monitoring dashboards. | | google_monitoring_dashboard |
+| [nva.tf](./nva.tf) | None | compute-mig · compute-vm · net-ilb | |
+| [outputs.tf](./outputs.tf) | Module outputs. | | local_file |
+| [test-resources.tf](./test-resources.tf) | temporary instances for testing | compute-vm | |
+| [variables.tf](./variables.tf) | Module variables. | | |
+| [vpc-landing.tf](./vpc-landing.tf) | Landing VPC and related resources. | net-cloudnat · net-vpc · net-vpc-firewall · project | |
+| [vpc-spoke-dev.tf](./vpc-spoke-dev.tf) | Dev spoke VPC and related resources. | net-address · net-vpc · net-vpc-firewall · net-vpc-peering · project | |
+| [vpc-spoke-prod.tf](./vpc-spoke-prod.tf) | Production spoke VPC and related resources. | net-address · net-vpc · net-vpc-firewall · net-vpc-peering · project | |
+| [vpn-onprem.tf](./vpn-onprem.tf) | VPN between landing and onprem. | net-vpn-ha | |
+
+## Variables
+
+| name | description | type | required | default | producer |
+|---|---|:---:|:---:|:---:|:---:|
+| [billing_account_id](variables.tf#L17) | Billing account id. | string | ✓ | | 00-bootstrap |
+| [organization](variables.tf#L99) | Organization details. | object({…}) | ✓ | | 00-bootstrap |
+| [prefix](variables.tf#L115) | Prefix used for resources that need unique names. | string | ✓ | | 00-bootstrap |
+| [custom_adv](variables.tf#L23) | Custom advertisement definitions in name => range format. | map(string) | | {…} | |
+| [data_dir](variables.tf#L45) | Relative path for the folder storing configuration data for network resources. | string | | "data" | |
+| [dns](variables.tf#L51) | Onprem DNS resolvers | map(list(string)) | | {…} | |
+| [folder_id](variables.tf#L59) | Folder to be used for the networking resources in folders/nnnnnnnnnnn format. If null, folder will be created. | string | | null | 01-resman |
+| [l7ilb_subnets](variables.tf#L73) | Subnets used for L7 ILBs. | map(list(object({…}))) | | {…} | |
+| [onprem_cidr](variables.tf#L91) | Onprem addresses in name => range format. | map(string) | | {…} | |
+| [outputs_location](variables.tf#L109) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string | | null | |
+| [project_factory_sa](variables.tf#L121) | IAM emails for project factory service accounts | map(string) | | {} | 01-resman |
+| [psa_ranges](variables.tf#L128) | IP ranges used for Private Service Access (e.g. CloudSQL). | map(map(string)) | | {…} | |
+| [router_configs](variables.tf#L143) | Configurations for CRs and onprem routers. | map(object({…})) | | {…} | |
+| [vpn_onprem_configs](variables.tf#L166) | VPN gateway configuration for onprem interconnection. | map(object({…})) | | {…} | |
+
+## Outputs
+
+| name | description | sensitive | consumers |
+|---|---|:---:|---|
+| [project_ids](outputs.tf#L42) | Network project ids. | | |
+| [project_numbers](outputs.tf#L51) | Network project numbers. | | |
+| [shared_vpc_host_projects](outputs.tf#L60) | Shared VPC host projects. | | |
+| [shared_vpc_self_links](outputs.tf#L69) | Shared VPC host projects. | | |
+| [tfvars](outputs.tf#L93) | Network-related variables used in other stages. | ✓ | |
+| [vpn_gateway_endpoints](outputs.tf#L79) | External IP Addresses for the GCP VPN gateways. | | |
+
+
diff --git a/fast/stages/02-networking-nva/data/cidrs.yaml b/fast/stages/02-networking-nva/data/cidrs.yaml
new file mode 100644
index 000000000..b6c25e21a
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/cidrs.yaml
@@ -0,0 +1,15 @@
+# skip boilerplate check
+
+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
+
+onprem_probes:
+ - 10.255.255.254/32
diff --git a/fast/stages/02-networking-nva/data/dashboards/firewall_insights.json b/fast/stages/02-networking-nva/data/dashboards/firewall_insights.json
new file mode 100644
index 000000000..4c0ebe287
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/dashboards/firewall_insights.json
@@ -0,0 +1,68 @@
+{
+ "displayName": "Firewall Insights Monitoring",
+ "gridLayout": {
+ "columns": "2",
+ "widgets": [
+ {
+ "title": "Subnet Firewall Hit Counts",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"firewallinsights.googleapis.com/subnet/firewall_hit_count\" resource.type=\"gce_subnetwork\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "VM Firewall Hit Counts",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"firewallinsights.googleapis.com/vm/firewall_hit_count\" resource.type=\"gce_instance\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/fast/stages/02-networking-nva/data/dashboards/vpn.json b/fast/stages/02-networking-nva/data/dashboards/vpn.json
new file mode 100644
index 000000000..1aec3e45b
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/dashboards/vpn.json
@@ -0,0 +1,248 @@
+{
+ "displayName": "VPN Monitoring",
+ "gridLayout": {
+ "columns": "2",
+ "widgets": [
+ {
+ "title": "Number of connections",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_MEAN"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/gateway/connections\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Tunnel established",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_MEAN"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/tunnel_established\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Cloud VPN Gateway - Received bytes",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/received_bytes_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "By"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Cloud VPN Gateway - Sent bytes",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/sent_bytes_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "By"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Cloud VPN Gateway - Received packets",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/received_packets_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "{packets}"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Cloud VPN Gateway - Sent packets",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/sent_packets_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "{packets}"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Incoming packets dropped",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/dropped_received_packets_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ },
+ {
+ "title": "Outgoing packets dropped",
+ "xyChart": {
+ "chartOptions": {
+ "mode": "COLOR"
+ },
+ "dataSets": [
+ {
+ "minAlignmentPeriod": "60s",
+ "plotType": "LINE",
+ "targetAxis": "Y1",
+ "timeSeriesQuery": {
+ "timeSeriesFilter": {
+ "aggregation": {
+ "perSeriesAligner": "ALIGN_RATE"
+ },
+ "filter": "metric.type=\"vpn.googleapis.com/network/dropped_sent_packets_count\" resource.type=\"vpn_gateway\"",
+ "secondaryAggregation": {}
+ },
+ "unitOverride": "1"
+ }
+ }
+ ],
+ "timeshiftDuration": "0s",
+ "yAxis": {
+ "label": "y1Axis",
+ "scale": "LINEAR"
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/fast/stages/02-networking-nva/data/firewall-rules/landing-trusted/rules.yaml b/fast/stages/02-networking-nva/data/firewall-rules/landing-trusted/rules.yaml
new file mode 100644
index 000000000..672af07f7
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/firewall-rules/landing-trusted/rules.yaml
@@ -0,0 +1,29 @@
+# skip boilerplate check
+
+allow-hc-nva-ssh-trusted:
+ description: "Allow traffic from Google healthchecks to NVA appliances"
+ direction: INGRESS
+ action: allow
+ sources: []
+ ranges:
+ - $healthchecks
+ targets: []
+ use_service_accounts: false
+ rules:
+ - protocol: tcp
+ ports:
+ - 22
+
+allow-onprem-probes-trusted-example:
+ description: "Allow traffic from onprem probes"
+ direction: INGRESS
+ action: allow
+ sources: []
+ ranges:
+ - $onprem_probes
+ targets: []
+ use_service_accounts: false
+ rules:
+ - protocol: tcp
+ ports:
+ - 12345
diff --git a/fast/stages/02-networking-nva/data/firewall-rules/landing-untrusted/rules.yaml b/fast/stages/02-networking-nva/data/firewall-rules/landing-untrusted/rules.yaml
new file mode 100644
index 000000000..15db503ba
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/firewall-rules/landing-untrusted/rules.yaml
@@ -0,0 +1,15 @@
+# skip boilerplate check
+
+allow-hc-nva-ssh-untrusted:
+ description: "Allow traffic from Google healthchecks to NVA appliances"
+ direction: INGRESS
+ action: allow
+ sources: []
+ ranges:
+ - $healthchecks
+ targets: []
+ use_service_accounts: false
+ rules:
+ - protocol: tcp
+ ports:
+ - 22
diff --git a/fast/stages/02-networking-nva/data/hierarchical-policy-rules.yaml b/fast/stages/02-networking-nva/data/hierarchical-policy-rules.yaml
new file mode 100644
index 000000000..0172a3091
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/hierarchical-policy-rules.yaml
@@ -0,0 +1,49 @@
+# skip boilerplate check
+
+allow-admins:
+ description: Access from the admin subnet to all subnets
+ direction: INGRESS
+ action: allow
+ priority: 1000
+ ranges:
+ - $rfc1918
+ ports:
+ all: []
+ target_resources: null
+ enable_logging: false
+
+allow-healthchecks:
+ description: Enable HTTP and HTTPS healthchecks
+ direction: INGRESS
+ action: allow
+ priority: 1001
+ ranges:
+ - $healthchecks
+ ports:
+ tcp: ["80", "443"]
+ target_resources: null
+ enable_logging: false
+
+allow-ssh-from-iap:
+ description: Enable SSH from IAP
+ direction: INGRESS
+ action: allow
+ priority: 1002
+ ranges:
+ - 35.235.240.0/20
+ ports:
+ tcp: ["22"]
+ target_resources: null
+ enable_logging: false
+
+allow-icmp:
+ description: Enable ICMP
+ direction: INGRESS
+ action: allow
+ priority: 1003
+ ranges:
+ - 0.0.0.0/0
+ ports:
+ icmp: []
+ target_resources: null
+ enable_logging: false
diff --git a/fast/stages/02-networking-nva/data/nva-startup-script.tftpl b/fast/stages/02-networking-nva/data/nva-startup-script.tftpl
new file mode 100644
index 000000000..353c6fa19
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/nva-startup-script.tftpl
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+echo 'Enabling IP forwarding'
+sed '/net.ipv4.ip_forward=1/s/^#//g' -i /etc/sysctl.conf &&
+sysctl -p /etc/sysctl.conf &&
+/etc/init.d/procps restart
+
+echo 'Setting Routes'
+ip route add ${landing-untrusted-other-region} via ${gateway-untrusted} dev ens4
+ip route add ${landing-trusted-other-region} via ${gateway-trusted} dev ens5
+ip route add ${dev-default-ew1-cidr} via ${gateway-trusted} dev ens5
+ip route add ${dev-default-ew4-cidr} via ${gateway-trusted} dev ens5
+ip route add ${prod-default-ew1-cidr} via ${gateway-trusted} dev ens5
+ip route add ${prod-default-ew4-cidr} via ${gateway-trusted} dev ens5
+ip route add ${onprem-main-cidr} via ${gateway-trusted} dev ens5
+
+echo 'Adding PBR rules to answer HCs also from the secondary nic'
+grep -qxF '200 hc' /etc/iproute2/rt_tables || echo '200 hc' >> /etc/iproute2/rt_tables
+ip_addr_ens5=$(ip route ls table local | awk '/ens5 proto 66 scope host/ {print $2}')
+while [ -z $ip_addr_ens5 ]; do
+ echo 'Waiting for networking stack to be ready'
+ sleep 2
+ ip_addr_ens5=$(ip route ls table local | awk '/ens5 proto 66 scope host/ {print $2}')
+done
+ip rule add from $ip_addr_ens5 lookup hc
+ip route add default via ${gateway-trusted} dev ens5 table hc
+
+echo 'Setting NAT masquerade (for Internet connectivity)'
+iptables --append FORWARD --in-interface ens5 -j ACCEPT
+iptables --table nat --append POSTROUTING --out-interface ens4 -j MASQUERADE
diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew1.yaml b/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew1.yaml
new file mode 100644
index 000000000..3baaf1482
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew1.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west1
+ip_cidr_range: 10.128.128.0/19
+description: Default europe-west1 subnet for dev
diff --git a/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew4.yaml b/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew4.yaml
new file mode 100644
index 000000000..387694555
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/subnets/dev/dev-default-ew4.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west4
+ip_cidr_range: 10.128.160.0/19
+description: Default europe-west4 subnet for dev
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml b/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml
new file mode 100644
index 000000000..474045234
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew1.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west1
+ip_cidr_range: 10.128.64.0/19
+description: Default europe-west1 subnet for landing trusted
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml b/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml
new file mode 100644
index 000000000..463066fb4
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/subnets/landing-trusted/landing-trusted-default-ew4.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west4
+ip_cidr_range: 10.128.96.0/19
+description: Default europe-west4 subnet for landing trusted
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml b/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml
new file mode 100644
index 000000000..2758da5f9
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew1.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west1
+ip_cidr_range: 10.128.0.0/19
+description: Default europe-west1 subnet for landing untrusted
diff --git a/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml b/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml
new file mode 100644
index 000000000..25bad9db6
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/subnets/landing-untrusted/landing-untrusted-default-ew4.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west4
+ip_cidr_range: 10.128.32.0/19
+description: Default europe-west4 subnet for landing untrusted
diff --git a/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew1.yaml b/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew1.yaml
new file mode 100644
index 000000000..b829cb94d
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew1.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west1
+ip_cidr_range: 10.128.192.0/19
+description: Default europe-west1 subnet for prod
diff --git a/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew4.yaml b/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew4.yaml
new file mode 100644
index 000000000..dbd716cdb
--- /dev/null
+++ b/fast/stages/02-networking-nva/data/subnets/prod/prod-default-ew4.yaml
@@ -0,0 +1,5 @@
+# skip boilerplate check
+
+region: europe-west4
+ip_cidr_range: 10.128.224.0/19
+description: Default europe-west4 subnet for prod
diff --git a/fast/stages/02-networking-nva/diagram.png b/fast/stages/02-networking-nva/diagram.png
new file mode 100644
index 000000000..5e789ed79
Binary files /dev/null and b/fast/stages/02-networking-nva/diagram.png differ
diff --git a/fast/stages/02-networking-nva/diagram.svg b/fast/stages/02-networking-nva/diagram.svg
new file mode 100644
index 000000000..b5ccea7e0
--- /dev/null
+++ b/fast/stages/02-networking-nva/diagram.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/fast/stages/02-networking-nva/dns-dev.tf b/fast/stages/02-networking-nva/dns-dev.tf
new file mode 100644
index 000000000..08de34cce
--- /dev/null
+++ b/fast/stages/02-networking-nva/dns-dev.tf
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Development spoke DNS zones and peerings setup.
+
+# GCP-specific environment zone
+
+module "dev-dns-private-zone" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "private"
+ name = "dev-gcp-example-com"
+ domain = "dev.gcp.example.com."
+ client_networks = [module.dev-spoke-vpc.self_link]
+ recordsets = {
+ "A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
+ }
+}
+
+# root zone peering to landing to centralize configuration; remove if unneeded
+
+module "dev-landing-root-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.dev-spoke-project.project_id
+ type = "peering"
+ name = "dev-root-dns-peering"
+ domain = "."
+ client_networks = [module.dev-spoke-vpc.self_link]
+ peer_network = module.landing-trusted-vpc.self_link
+}
+
+module "dev-reverse-10-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.dev-spoke-project.project_id
+ type = "peering"
+ name = "dev-reverse-10-dns-peering"
+ domain = "10.in-addr.arpa."
+ client_networks = [module.dev-spoke-vpc.self_link]
+ peer_network = module.landing-trusted-vpc.self_link
+}
diff --git a/fast/stages/02-networking-nva/dns-landing.tf b/fast/stages/02-networking-nva/dns-landing.tf
new file mode 100644
index 000000000..f13ef9960
--- /dev/null
+++ b/fast/stages/02-networking-nva/dns-landing.tf
@@ -0,0 +1,111 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Landing DNS zones and peerings setup.
+
+# forwarding to on-prem DNS resolvers
+
+module "onprem-example-dns-forwarding" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "forwarding"
+ name = "example-com"
+ domain = "onprem.example.com."
+ client_networks = [
+ module.landing-untrusted-vpc.self_link,
+ module.landing-trusted-vpc.self_link
+ ]
+ forwarders = { for ip in var.dns.onprem : ip => null }
+}
+
+module "reverse-10-dns-forwarding" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "forwarding"
+ name = "root-reverse-10"
+ domain = "10.in-addr.arpa."
+ client_networks = [
+ module.landing-untrusted-vpc.self_link,
+ module.landing-trusted-vpc.self_link
+ ]
+ forwarders = { for ip in var.dns.onprem : ip => null }
+}
+
+module "gcp-example-dns-private-zone" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "private"
+ name = "gcp-example-com"
+ domain = "gcp.example.com."
+ client_networks = [
+ module.landing-untrusted-vpc.self_link,
+ module.landing-trusted-vpc.self_link
+ ]
+ recordsets = {
+ "A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
+ }
+}
+
+# GCP-specific DNS zones peered to the environment spoke that holds the config
+
+module "prod-gcp-example-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "peering"
+ name = "prod-root-dns-peering"
+ domain = "prod.gcp.example.com."
+ client_networks = [
+ module.landing-untrusted-vpc.self_link,
+ module.landing-trusted-vpc.self_link
+ ]
+ peer_network = module.prod-spoke-vpc.self_link
+}
+
+module "dev-gcp-example-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "peering"
+ name = "dev-root-dns-peering"
+ domain = "dev.gcp.example.com."
+ client_networks = [
+ module.landing-untrusted-vpc.self_link,
+ module.landing-trusted-vpc.self_link
+ ]
+ peer_network = module.dev-spoke-vpc.self_link
+}
+
+# Google API zone to trigger Private Access
+
+module "googleapis-private-zone" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "private"
+ name = "googleapis-com"
+ domain = "googleapis.com."
+ client_networks = [
+ module.landing-untrusted-vpc.self_link,
+ module.landing-trusted-vpc.self_link
+ ]
+ recordsets = {
+ "A private" = { type = "A", ttl = 300, records = [
+ "199.36.153.8", "199.36.153.9", "199.36.153.10", "199.36.153.11"
+ ] }
+ "A restricted" = { type = "A", ttl = 300, records = [
+ "199.36.153.4", "199.36.153.5", "199.36.153.6", "199.36.153.7"
+ ] }
+ "CNAME *" = { type = "CNAME", ttl = 300, records = ["private.googleapis.com."] }
+ }
+}
diff --git a/fast/stages/02-networking-nva/dns-prod.tf b/fast/stages/02-networking-nva/dns-prod.tf
new file mode 100644
index 000000000..d92157e82
--- /dev/null
+++ b/fast/stages/02-networking-nva/dns-prod.tf
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Production spoke DNS zones and peerings setup.
+
+# GCP-specific environment zone
+
+module "prod-dns-private-zone" {
+ source = "../../../modules/dns"
+ project_id = module.landing-project.project_id
+ type = "private"
+ name = "prod-gcp-example-com"
+ domain = "prod.gcp.example.com."
+ client_networks = [module.prod-spoke-vpc.self_link]
+ recordsets = {
+ "A localhost" = { type = "A", ttl = 300, records = ["127.0.0.1"] }
+ }
+}
+
+# root zone peering to landing to centralize configuration; remove if unneeded
+
+module "prod-landing-root-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.prod-spoke-project.project_id
+ type = "peering"
+ name = "prod-root-dns-peering"
+ domain = "."
+ client_networks = [module.prod-spoke-vpc.self_link]
+ peer_network = module.landing-trusted-vpc.self_link
+}
+
+module "prod-reverse-10-dns-peering" {
+ source = "../../../modules/dns"
+ project_id = module.prod-spoke-project.project_id
+ type = "peering"
+ name = "prod-reverse-10-dns-peering"
+ domain = "10.in-addr.arpa."
+ client_networks = [module.prod-spoke-vpc.self_link]
+ peer_network = module.landing-trusted-vpc.self_link
+}
diff --git a/fast/stages/02-networking-nva/main.tf b/fast/stages/02-networking-nva/main.tf
new file mode 100644
index 000000000..db03c69a1
--- /dev/null
+++ b/fast/stages/02-networking-nva/main.tf
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Networking folder and hierarchical policy.
+
+locals {
+ l7ilb_subnets = { for env, v in var.l7ilb_subnets : env => [
+ for s in v : merge(s, {
+ active = true
+ name = "${env}-l7ilb-${s.region}"
+ })]
+ }
+}
+
+module "folder" {
+ source = "../../../modules/folder"
+ parent = "organizations/${var.organization.id}"
+ name = "Networking"
+ folder_create = var.folder_id == null
+ id = var.folder_id
+ firewall_policy_factory = {
+ cidr_file = "${var.data_dir}/cidrs.yaml"
+ policy_name = null
+ rules_file = "${var.data_dir}/hierarchical-policy-rules.yaml"
+ }
+ firewall_policy_association = {
+ factory-policy = "factory"
+ }
+}
diff --git a/fast/stages/02-networking-nva/monitoring.tf b/fast/stages/02-networking-nva/monitoring.tf
new file mode 100644
index 000000000..7b8b70c51
--- /dev/null
+++ b/fast/stages/02-networking-nva/monitoring.tf
@@ -0,0 +1,32 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description Network monitoring dashboards.
+
+locals {
+ dashboard_path = "${var.data_dir}/dashboards"
+ dashboard_files = fileset(local.dashboard_path, "*.json")
+ dashboards = {
+ for filename in local.dashboard_files :
+ filename => "${local.dashboard_path}/${filename}"
+ }
+}
+
+resource "google_monitoring_dashboard" "dashboard" {
+ for_each = local.dashboards
+ project = module.landing-project.project_id
+ dashboard_json = file(each.value)
+}
diff --git a/fast/stages/02-networking-nva/nva.tf b/fast/stages/02-networking-nva/nva.tf
new file mode 100644
index 000000000..aaf4c6de8
--- /dev/null
+++ b/fast/stages/02-networking-nva/nva.tf
@@ -0,0 +1,222 @@
+/**
+ * Copyright 2022 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 {
+ _subnets = var.data_dir == null ? tomap({}) : {
+ for f in fileset("${var.data_dir}/subnets", "**/*.yaml") :
+ trimsuffix(basename(f), ".yaml") => yamldecode(file("${var.data_dir}/subnets/${f}"))
+ }
+ subnets = merge(
+ { for k, v in local._subnets : "${k}-cidr" => v.ip_cidr_range },
+ { for k, v in local._subnets : "${k}-gw" => cidrhost(v.ip_cidr_range, 1) }
+ )
+}
+
+# europe-west1
+
+module "nva-template-ew1" {
+ source = "../../../modules/compute-vm"
+ project_id = module.landing-project.project_id
+ name = "nva-template"
+ zone = "europe-west1-b"
+ tags = ["nva"]
+ can_ip_forward = true
+ network_interfaces = [
+ {
+ network = module.landing-untrusted-vpc.self_link
+ subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west1/landing-untrusted-default-ew1"]
+ nat = false
+ addresses = null
+ },
+ {
+ network = module.landing-trusted-vpc.self_link
+ subnetwork = module.landing-trusted-vpc.subnet_self_links["europe-west1/landing-trusted-default-ew1"]
+ nat = false
+ addresses = null
+ }
+ ]
+ boot_disk = {
+ image = "projects/debian-cloud/global/images/family/debian-10"
+ type = "pd-balanced"
+ size = 10
+ }
+ create_template = true
+ metadata = {
+ startup-script = templatefile(
+ "${path.module}/data/nva-startup-script.tftpl",
+ {
+ dev-default-ew1-cidr = local.subnets.dev-default-ew1-cidr
+ dev-default-ew4-cidr = local.subnets.dev-default-ew4-cidr
+ gateway-trusted = local.subnets.landing-trusted-default-ew1-gw
+ gateway-untrusted = local.subnets.landing-untrusted-default-ew1-gw
+ landing-trusted-other-region = local.subnets.landing-trusted-default-ew4-cidr
+ landing-untrusted-other-region = local.subnets.landing-untrusted-default-ew4-cidr
+ onprem-main-cidr = var.onprem_cidr.main
+ prod-default-ew1-cidr = local.subnets.prod-default-ew1-cidr
+ prod-default-ew4-cidr = local.subnets.prod-default-ew4-cidr
+ }
+ )
+ }
+}
+
+module "nva-mig-ew1" {
+ source = "../../../modules/compute-mig"
+ project_id = module.landing-project.project_id
+ regional = true
+ location = "europe-west1"
+ name = "nva"
+ target_size = 2
+ default_version = {
+ instance_template = module.nva-template-ew1.template.self_link
+ name = "default"
+ }
+}
+
+module "ilb-nva-untrusted-ew1" {
+ source = "../../../modules/net-ilb"
+ project_id = module.landing-project.project_id
+ region = "europe-west1"
+ name = "ilb-nva-untrusted-ew1"
+ service_label = var.prefix
+ global_access = true
+ network = module.landing-untrusted-vpc.self_link
+ subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west1/landing-untrusted-default-ew1"]
+ backends = [{
+ failover = false
+ group = module.nva-mig-ew1.group_manager.instance_group
+ balancing_mode = "CONNECTION"
+ }]
+ health_check_config = {
+ type = "tcp", check = { port = 22 }, config = {}, logging = false
+ }
+}
+
+module "ilb-nva-trusted-ew1" {
+ source = "../../../modules/net-ilb"
+ project_id = module.landing-project.project_id
+ region = "europe-west1"
+ name = "ilb-nva-trusted-ew1"
+ service_label = var.prefix
+ global_access = true
+ network = module.landing-trusted-vpc.self_link
+ subnetwork = module.landing-trusted-vpc.subnet_self_links["europe-west1/landing-trusted-default-ew1"]
+ backends = [{
+ failover = false
+ group = module.nva-mig-ew1.group_manager.instance_group
+ balancing_mode = "CONNECTION"
+ }]
+ health_check_config = {
+ type = "tcp", check = { port = 22 }, config = {}, logging = false
+ }
+}
+
+# europe-west4
+
+module "nva-template-ew4" {
+ source = "../../../modules/compute-vm"
+ project_id = module.landing-project.project_id
+ name = "nva-template"
+ zone = "europe-west4-a"
+ tags = ["nva"]
+ can_ip_forward = true
+ network_interfaces = [
+ {
+ network = module.landing-untrusted-vpc.self_link
+ subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west4/landing-untrusted-default-ew4"]
+ nat = false
+ addresses = null
+ },
+ {
+ network = module.landing-trusted-vpc.self_link
+ subnetwork = module.landing-trusted-vpc.subnet_self_links["europe-west4/landing-trusted-default-ew4"]
+ nat = false
+ addresses = null
+ }
+ ]
+ boot_disk = {
+ image = "projects/debian-cloud/global/images/family/debian-10"
+ type = "pd-balanced"
+ size = 10
+ }
+ create_template = true
+ metadata = {
+ startup-script = templatefile(
+ "${path.module}/data/nva-startup-script.tftpl",
+ {
+ dev-default-ew1-cidr = local.subnets.dev-default-ew1-cidr
+ dev-default-ew4-cidr = local.subnets.dev-default-ew4-cidr
+ gateway-trusted = local.subnets.landing-trusted-default-ew4-gw
+ gateway-untrusted = local.subnets.landing-untrusted-default-ew4-gw
+ landing-trusted-other-region = local.subnets.landing-trusted-default-ew1-cidr
+ landing-untrusted-other-region = local.subnets.landing-untrusted-default-ew1-cidr
+ onprem-main-cidr = var.onprem_cidr.main
+ prod-default-ew1-cidr = local.subnets.prod-default-ew1-cidr
+ prod-default-ew4-cidr = local.subnets.prod-default-ew4-cidr
+ }
+ )
+ }
+}
+
+module "nva-mig-ew4" {
+ source = "../../../modules/compute-mig"
+ project_id = module.landing-project.project_id
+ regional = true
+ location = "europe-west4"
+ name = "nva"
+ target_size = 2
+ default_version = {
+ instance_template = module.nva-template-ew4.template.self_link
+ name = "default"
+ }
+}
+
+module "ilb-nva-untrusted-ew4" {
+ source = "../../../modules/net-ilb"
+ project_id = module.landing-project.project_id
+ region = "europe-west4"
+ name = "ilb-nva-untrusted-ew4"
+ service_label = var.prefix
+ global_access = true
+ network = module.landing-untrusted-vpc.self_link
+ subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west4/landing-untrusted-default-ew4"]
+ backends = [{
+ failover = false
+ group = module.nva-mig-ew4.group_manager.instance_group
+ balancing_mode = "CONNECTION"
+ }]
+ health_check_config = {
+ type = "tcp", check = { port = 22 }, config = {}, logging = false
+ }
+}
+
+module "ilb-nva-trusted-ew4" {
+ source = "../../../modules/net-ilb"
+ project_id = module.landing-project.project_id
+ region = "europe-west4"
+ name = "ilb-nva-trusted-ew4"
+ service_label = var.prefix
+ global_access = true
+ network = module.landing-trusted-vpc.self_link
+ subnetwork = module.landing-trusted-vpc.subnet_self_links["europe-west4/landing-trusted-default-ew4"]
+ backends = [{
+ failover = false
+ group = module.nva-mig-ew4.group_manager.instance_group
+ balancing_mode = "CONNECTION"
+ }]
+ health_check_config = {
+ type = "tcp", check = { port = 22 }, config = {}, logging = false
+ }
+}
diff --git a/fast/stages/02-networking-nva/outputs.tf b/fast/stages/02-networking-nva/outputs.tf
new file mode 100644
index 000000000..39c5d2ef3
--- /dev/null
+++ b/fast/stages/02-networking-nva/outputs.tf
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# Optionally, generate providers and tfvars files for subsequent stages
+
+locals {
+ tfvars = {
+ "03-project-factory-dev" = jsonencode({
+ environment_dns_zone = module.dev-dns-private-zone.domain
+ shared_vpc_self_link = module.dev-spoke-vpc.self_link
+ vpc_host_project = module.dev-spoke-project.project_id
+ })
+ "03-project-factory-prod" = jsonencode({
+ environment_dns_zone = module.prod-dns-private-zone.domain
+ shared_vpc_self_link = module.prod-spoke-vpc.self_link
+ vpc_host_project = module.prod-spoke-project.project_id
+ })
+ }
+}
+
+resource "local_file" "tfvars" {
+ for_each = var.outputs_location == null ? {} : local.tfvars
+ filename = "${var.outputs_location}/${each.key}/terraform-networking.auto.tfvars.json"
+ content = each.value
+}
+
+# Outputs
+
+output "project_ids" {
+ description = "Network project ids."
+ value = {
+ dev = module.dev-spoke-project.project_id
+ landing = module.landing-project.project_id
+ prod = module.prod-spoke-project.project_id
+ }
+}
+
+output "project_numbers" {
+ description = "Network project numbers."
+ value = {
+ dev = "projects/${module.dev-spoke-project.number}"
+ landing = "projects/${module.landing-project.number}"
+ prod = "projects/${module.prod-spoke-project.number}"
+ }
+}
+
+output "shared_vpc_host_projects" {
+ description = "Shared VPC host projects."
+ value = {
+ dev = module.dev-spoke-project.project_id
+ landing = module.landing-project.project_id
+ prod = module.prod-spoke-project.project_id
+ }
+}
+
+output "shared_vpc_self_links" {
+ description = "Shared VPC host projects."
+ value = {
+ dev = module.dev-spoke-vpc.self_link
+ landing-trusted = module.landing-trusted-vpc.self_link
+ landing-untrusted = module.landing-untrusted-vpc.self_link
+ prod = module.prod-spoke-vpc.self_link
+ }
+}
+
+output "vpn_gateway_endpoints" {
+ description = "External IP Addresses for the GCP VPN gateways."
+ value = {
+ onprem-ew1 = {
+ for v in module.landing-to-onprem-ew1-vpn.gateway.vpn_interfaces :
+ v.id => v.ip_address
+ }
+ onprem-ew4 = {
+ for v in module.landing-to-onprem-ew4-vpn.gateway.vpn_interfaces :
+ v.id => v.ip_address
+ }
+ }
+}
+
+output "tfvars" {
+ description = "Network-related variables used in other stages."
+ sensitive = true
+ value = local.tfvars
+}
diff --git a/fast/stages/02-networking-nva/test-resources.tf b/fast/stages/02-networking-nva/test-resources.tf
new file mode 100644
index 000000000..cd9ae9b0a
--- /dev/null
+++ b/fast/stages/02-networking-nva/test-resources.tf
@@ -0,0 +1,245 @@
+/**
+ * Copyright 2022 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.
+ */
+
+# tfdoc:file:description temporary instances for testing
+
+# Untrusted (Landing)
+
+module "test-vm-landing-untrusted-ew1-0" {
+ source = "../../../modules/compute-vm"
+ project_id = module.landing-project.project_id
+ zone = "europe-west1-b"
+ name = "test-vm-lnd-unt-ew1-0"
+ network_interfaces = [{
+ network = module.landing-untrusted-vpc.self_link
+ subnetwork = module.landing-untrusted-vpc.subnet_self_links["europe-west1/landing-untrusted-default-ew1"]
+ alias_ips = {}
+ nat = false
+ addresses = null
+ }]
+ tags = ["ew1", "ssh"]
+ service_account_create = true
+ boot_disk = {
+ image = "projects/debian-cloud/global/images/family/debian-10"
+ type = "pd-balanced"
+ size = 10
+ }
+ metadata = {
+ startup-script = <