diff --git a/fast/stages/00-bootstrap/README.md b/fast/stages/00-bootstrap/README.md
index c9b0fa71..994d67a9 100644
--- a/fast/stages/00-bootstrap/README.md
+++ b/fast/stages/00-bootstrap/README.md
@@ -301,9 +301,9 @@ Names used in internal references (e.g. `module.foo-prod.id`) are only used by T
| name | description | sensitive | consumers |
|---|---|:---:|---|
-| [billing_dataset](outputs.tf#L84) | BigQuery dataset prepared for billing export. | | |
-| [project_ids](outputs.tf#L89) | Projects created by this stage. | | |
-| [providers](outputs.tf#L100) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01
|
-| [tfvars](outputs.tf#L109) | Terraform variable files for the following stages. | ✓ | |
+| [billing_dataset](outputs.tf#L85) | BigQuery dataset prepared for billing export. | | |
+| [project_ids](outputs.tf#L90) | Projects created by this stage. | | |
+| [providers](outputs.tf#L101) | Terraform provider files for this stage and dependent stages. | ✓ | stage-01
|
+| [tfvars](outputs.tf#L110) | Terraform variable files for the following stages. | ✓ | |
diff --git a/fast/stages/00-bootstrap/organization.tf b/fast/stages/00-bootstrap/organization.tf
index 52747ce4..f4f2cc3d 100644
--- a/fast/stages/00-bootstrap/organization.tf
+++ b/fast/stages/00-bootstrap/organization.tf
@@ -147,7 +147,7 @@ module "organization" {
"resourcemanager.organizations.getIamPolicy",
"resourcemanager.organizations.setIamPolicy"
]
- "xpnServiceAdmin" = [
+ "serviceProjectNetworkAdmin" = [
"compute.globalOperations.get",
"compute.organizations.disableXpnResource",
"compute.organizations.enableXpnResource",
diff --git a/fast/stages/00-bootstrap/outputs.tf b/fast/stages/00-bootstrap/outputs.tf
index c25b9013..8912fb87 100644
--- a/fast/stages/00-bootstrap/outputs.tf
+++ b/fast/stages/00-bootstrap/outputs.tf
@@ -38,6 +38,7 @@ locals {
})
"02-networking" = jsonencode({
billing_account_id = var.billing_account.id
+ custom_roles = module.organization.custom_role_id
organization = var.organization
prefix = var.prefix
})
diff --git a/fast/stages/01-resman/organization.tf b/fast/stages/01-resman/organization.tf
index 10fbb6fd..5e3b072d 100644
--- a/fast/stages/01-resman/organization.tf
+++ b/fast/stages/01-resman/organization.tf
@@ -49,9 +49,6 @@ module "organization" {
# role assigned in stage 00; they need to be additive to avoid conflicts
iam_additive = merge(
{
- (var.custom_roles.xpnServiceAdmin) = concat(
- local.branch_teams_pf_sa_iam_emails
- )
"roles/accesscontextmanager.policyAdmin" = [
module.branch-security-sa.iam_email
]
diff --git a/fast/stages/02-networking-nva/README.md b/fast/stages/02-networking-nva/README.md
new file mode 100644
index 00000000..fb6286e4
--- /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 00000000..b6c25e21
--- /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 00000000..4c0ebe28
--- /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 00000000..1aec3e45
--- /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 00000000..672af07f
--- /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 00000000..15db503b
--- /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 00000000..0172a309
--- /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 00000000..353c6fa1
--- /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 00000000..3baaf148
--- /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 00000000..38769455
--- /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 00000000..47404523
--- /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 00000000..463066fb
--- /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 00000000..2758da5f
--- /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 00000000..25bad9db
--- /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 00000000..b829cb94
--- /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 00000000..dbd716cd
--- /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 00000000..5e789ed7
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 00000000..b5ccea7e
--- /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 00000000..08de34cc
--- /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 00000000..f13ef996
--- /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 00000000..d92157e8
--- /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 00000000..db03c69a
--- /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 00000000..7b8b70c5
--- /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 00000000..aaf4c6de
--- /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 00000000..39c5d2ef
--- /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 00000000..cd9ae9b0
--- /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 = <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-cloudnat
· net-vpc
· net-vpc-firewall
· project
| |
-| [vpc-spoke-prod.tf](./vpc-spoke-prod.tf) | Production spoke VPC and related resources. | net-address
· 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-cloudnat
· net-vpc
· net-vpc-firewall
· project
| google_project_iam_binding
|
+| [vpc-spoke-prod.tf](./vpc-spoke-prod.tf) | Production spoke VPC and related resources. | net-address
· net-cloudnat
· net-vpc
· net-vpc-firewall
· project
| google_project_iam_binding
|
| [vpn-onprem.tf](./vpn-onprem.tf) | VPN between landing and onprem. | net-vpn-ha
| |
| [vpn-spoke-dev.tf](./vpn-spoke-dev.tf) | VPN between landing and development spoke. | net-vpn-ha
| |
| [vpn-spoke-prod.tf](./vpn-spoke-prod.tf) | VPN between landing and production spoke. | net-vpn-ha
| |
@@ -309,19 +309,20 @@ DNS configurations are centralised in the `dns.tf` file. Spokes delegate DNS res
| name | description | type | required | default | producer |
|---|---|:---:|:---:|:---:|:---:|
| [billing_account_id](variables.tf#L17) | Billing account id. | string
| ✓ | | 00-bootstrap
|
-| [organization](variables.tf#L86) | Organization details. | object({…})
| ✓ | | 00-bootstrap
|
-| [prefix](variables.tf#L102) | Prefix used for resources that need unique names. | string
| ✓ | | 00-bootstrap
|
+| [organization](variables.tf#L93) | Organization details. | object({…})
| ✓ | | 00-bootstrap
|
+| [prefix](variables.tf#L109) | 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#L40) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
-| [dns](variables.tf#L46) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
-| [folder_id](variables.tf#L54) | 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#L68) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
-| [outputs_location](variables.tf#L96) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
-| [project_factory_sa](variables.tf#L108) | IAM emails for project factory service accounts. | map(string)
| | {}
| 01-resman
|
-| [psa_ranges](variables.tf#L115) | IP ranges used for Private Service Access (e.g. CloudSQL). | map(map(string))
| | {…}
| |
-| [router_configs](variables.tf#L130) | Configurations for CRs and onprem routers. | map(object({…}))
| | {…}
| |
-| [vpn_onprem_configs](variables.tf#L154) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
-| [vpn_spoke_configs](variables.tf#L210) | VPN gateway configuration for spokes. | map(object({…}))
| | {…}
| |
+| [custom_roles](variables.tf#L40) | Custom roles defined at the org level, in key => id format. | map(string)
| | {}
| 00-bootstrap
|
+| [data_dir](variables.tf#L47) | Relative path for the folder storing configuration data for network resources. | string
| | "data"
| |
+| [dns](variables.tf#L53) | Onprem DNS resolvers. | map(list(string))
| | {…}
| |
+| [folder_id](variables.tf#L61) | 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#L75) | Subnets used for L7 ILBs. | map(list(object({…})))
| | {…}
| |
+| [outputs_location](variables.tf#L103) | Path where providers and tfvars files for the following stages are written. Leave empty to disable. | string
| | null
| |
+| [project_factory_sa](variables.tf#L115) | IAM emails for project factory service accounts. | map(string)
| | {}
| 01-resman
|
+| [psa_ranges](variables.tf#L122) | IP ranges used for Private Service Access (e.g. CloudSQL). | map(map(string))
| | {…}
| |
+| [router_configs](variables.tf#L137) | Configurations for CRs and onprem routers. | map(object({…}))
| | {…}
| |
+| [vpn_onprem_configs](variables.tf#L161) | VPN gateway configuration for onprem interconnection. | map(object({…}))
| | {…}
| |
+| [vpn_spoke_configs](variables.tf#L217) | VPN gateway configuration for spokes. | map(object({…}))
| | {…}
| |
## Outputs
diff --git a/fast/stages/02-networking/main.tf b/fast/stages/02-networking/main.tf
index edf6ec53..4a3f4748 100644
--- a/fast/stages/02-networking/main.tf
+++ b/fast/stages/02-networking/main.tf
@@ -41,6 +41,12 @@ locals {
europe-west1 = "ew1"
europe-west3 = "ew3"
}
+ stage3_sas_delegated_grants = [
+ "roles/composer.sharedVpcAgent",
+ "roles/compute.networkUser",
+ "roles/container.hostServiceAgentUser",
+ "roles/vpcaccess.user",
+ ]
}
module "folder" {
diff --git a/fast/stages/02-networking/variables.tf b/fast/stages/02-networking/variables.tf
index abf45adb..4c134e2f 100644
--- a/fast/stages/02-networking/variables.tf
+++ b/fast/stages/02-networking/variables.tf
@@ -37,6 +37,13 @@ variable "custom_adv" {
}
}
+variable "custom_roles" {
+ # tfdoc:variable:source 00-bootstrap
+ description = "Custom roles defined at the org level, in key => id format."
+ type = map(string)
+ default = {}
+}
+
variable "data_dir" {
description = "Relative path for the folder storing configuration data for network resources."
type = string
diff --git a/fast/stages/02-networking/vpc-spoke-dev.tf b/fast/stages/02-networking/vpc-spoke-dev.tf
index 8021f0c5..90d11f16 100644
--- a/fast/stages/02-networking/vpc-spoke-dev.tf
+++ b/fast/stages/02-networking/vpc-spoke-dev.tf
@@ -40,6 +40,9 @@ module "dev-spoke-project" {
metric_scopes = [module.landing-project.project_id]
iam = {
"roles/dns.admin" = [var.project_factory_sa.dev]
+ (var.custom_roles.serviceProjectNetworkAdmin) = [
+ var.project_factory_sa.prod
+ ]
}
}
@@ -103,3 +106,20 @@ module "dev-spoke-psa-addresses" {
}
}
}
+
+# Create delegated grants for stage3 service accounts
+resource "google_project_iam_binding" "dev_spoke_project_iam_delegated" {
+ project = module.dev-spoke-project.project_id
+ role = "roles/resourcemanager.projectIamAdmin"
+ members = [
+ var.project_factory_sa.dev
+ ]
+ condition {
+ title = "dev_stage3_sa_delegated_grants"
+ description = "Development host project delegated grants."
+ expression = format(
+ "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
+ join(",", formatlist("'%s'", local.stage3_sas_delegated_grants))
+ )
+ }
+}
diff --git a/fast/stages/02-networking/vpc-spoke-prod.tf b/fast/stages/02-networking/vpc-spoke-prod.tf
index 574af757..0132d8fd 100644
--- a/fast/stages/02-networking/vpc-spoke-prod.tf
+++ b/fast/stages/02-networking/vpc-spoke-prod.tf
@@ -40,6 +40,9 @@ module "prod-spoke-project" {
metric_scopes = [module.landing-project.project_id]
iam = {
"roles/dns.admin" = [var.project_factory_sa.prod]
+ (var.custom_roles.serviceProjectNetworkAdmin) = [
+ var.project_factory_sa.prod
+ ]
}
}
@@ -103,3 +106,20 @@ module "prod-spoke-psa-addresses" {
}
}
}
+
+# Create delegated grants for stage3 service accounts
+resource "google_project_iam_binding" "prod_spoke_project_iam_delegated" {
+ project = module.prod-spoke-project.project_id
+ role = "roles/resourcemanager.projectIamAdmin"
+ members = [
+ var.project_factory_sa.prod
+ ]
+ condition {
+ title = "prod_stage3_sa_delegated_grants"
+ description = "Production host project delegated grants."
+ expression = format(
+ "api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([%s])",
+ join(",", formatlist("'%s'", local.stage3_sas_delegated_grants))
+ )
+ }
+}
diff --git a/modules/net-vpc/main.tf b/modules/net-vpc/main.tf
index 7c25e039..730e17f2 100644
--- a/modules/net-vpc/main.tf
+++ b/modules/net-vpc/main.tf
@@ -49,7 +49,7 @@ locals {
ip_cidr_range = v.ip_cidr_range
name = k
region = v.region
- secondary_ip_range = try(v.secondary_ip_range, [])
+ secondary_ip_range = try(v.secondary_ip_range, {})
}
}
_iam = var.iam == null ? {} : var.iam
diff --git a/tests/fast/stages/s02_networking_nva/__init__.py b/tests/fast/stages/s02_networking_nva/__init__.py
new file mode 100644
index 00000000..6d6d1266
--- /dev/null
+++ b/tests/fast/stages/s02_networking_nva/__init__.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/tests/fast/stages/s02_networking_nva/fixture/main.tf b/tests/fast/stages/s02_networking_nva/fixture/main.tf
new file mode 100644
index 00000000..d3638250
--- /dev/null
+++ b/tests/fast/stages/s02_networking_nva/fixture/main.tf
@@ -0,0 +1,31 @@
+/**
+ * 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.
+ */
+
+module "stage" {
+ source = "../../../../../fast/stages/02-networking-nva"
+ billing_account_id = "000000-111111-222222"
+ organization = {
+ domain = "gcp-pso-italy.net"
+ id = 856933387836
+ customer_id = "C01lmug8b"
+ }
+ prefix = "fast"
+ project_factory_sa = {
+ dev = "foo@iam"
+ prod = "bar@iam"
+ }
+ data_dir = "../../../../../fast/stages/02-networking-nva/data/"
+}
diff --git a/tests/fast/stages/s02_networking_nva/test_plan.py b/tests/fast/stages/s02_networking_nva/test_plan.py
new file mode 100644
index 00000000..6189f62e
--- /dev/null
+++ b/tests/fast/stages/s02_networking_nva/test_plan.py
@@ -0,0 +1,20 @@
+# 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.
+
+
+def test_counts(fast_e2e_plan_runner):
+ "Test stage."
+ num_modules, num_resources, _ = fast_e2e_plan_runner()
+ # TODO: to re-enable per-module resource count check print _, then test
+ assert num_modules > 0 and num_resources > 0