Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Session.vim
**/.terraform.d/*
.terraform.lock
.terraform.lock.hcl
.terraform.lock

# .tfstate files
*.tfstate
Expand Down
16 changes: 12 additions & 4 deletions examples/network_connectivity_center/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ module "network_connectivity_center_star" {
# VPC Spoke #
################################
module "vpc_spoke_vpc" {
source = "terraform-google-modules/network/google"
source = "terraform-google-modules/network/google"
version = "~> 13.0"

project_id = var.project_id
network_name = var.vpc_spoke_vpc_name
routing_mode = "GLOBAL"
Expand Down Expand Up @@ -136,7 +138,9 @@ module "vpc_spoke_vpc" {
################################
# Simulates an on-prem network that will be connected over VPN
module "vpn_spoke_remote_vpc" {
source = "terraform-google-modules/network/google"
source = "terraform-google-modules/network/google"
version = "~> 13.0"

project_id = var.project_id
network_name = var.vpn_spoke_remote_vpc_name
routing_mode = "GLOBAL"
Expand All @@ -161,7 +165,9 @@ module "vpn_spoke_remote_vpc" {
}

module "vpn_spoke_local_vpc" {
source = "terraform-google-modules/network/google"
source = "terraform-google-modules/network/google"
version = "~> 13.0"

project_id = var.project_id
network_name = var.vpn_spoke_local_vpc_name
routing_mode = "GLOBAL"
Expand Down Expand Up @@ -259,7 +265,9 @@ resource "random_shuffle" "zone" {
}

module "router_appliance_spoke_vpc" {
source = "terraform-google-modules/network/google"
source = "terraform-google-modules/network/google"
version = "~> 13.0"

project_id = var.project_id
network_name = var.router_appliance_vpc_name
routing_mode = "GLOBAL"
Expand Down
2 changes: 2 additions & 0 deletions metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ spec:
location: modules/firewall-rules
- name: hierarchical-firewall-policy
location: modules/hierarchical-firewall-policy
- name: network
location: modules/foundation/network
- name: network-connectivity-center
location: modules/network-connectivity-center
- name: network-firewall-policy
Expand Down
180 changes: 180 additions & 0 deletions modules/foundation/network/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Google Cloud Shared VPC Foundation Module

This Terraform module deploys a **Shared VPC Host** network on Google Cloud Platform. It serves as a foundational networking component, integrating with **Network Connectivity Center (NCC)** for Mesh or Star topologies, **Global Network Firewall Policies**, and DNS configurations.

## Prerequisites

### 1. Required APIs
The project where this module is deployed must have the following APIs enabled:

* `compute.googleapis.com` (Compute Engine)
* `dns.googleapis.com` (Cloud DNS)
* `servicenetworking.googleapis.com` (Service Networking / PSA)
* `networkconnectivity.googleapis.com` (Network Connectivity Center)

### 2. IAM Roles
The Service Account running Terraform requires the following roles at the **Project** level:

* **Compute Network Admin** (`roles/compute.networkAdmin`): For VPC, Subnets, Routes.
* **Compute Security Admin** (`roles/compute.securityAdmin`): **Required** for Global Network Firewall Policies.
* **DNS Administrator** (`roles/dns.admin`): For DNS Policies and Zones.
* **Network Connectivity Center Admin** (`roles/networkconnectivity.hubAdmin`): For Hub/Spoke management.
* **Project IAM Admin** (Conditional): If auto-accepting projects into NCC groups.


## Features

* **Shared VPC:** Automatically configures the network as a Shared VPC Host (`shared_vpc_host = "true"`).
* **Network Connectivity Center (NCC):**
* Supports **Mesh** (full-mesh connectivity) or **Star** (Hub & Spoke) topologies.
* Granular control over spoke export filters and producer route propagation.
* **Advanced DNS:**
* **Spoke Logic:** Automatically creates DNS Peering to a central DNS Hub.
* **Hub/Standalone Logic:** Creates DNS Forwarding zones to on-premise target name servers.
* Default DNS Policy with logging enabled.
* **Security:**
* **Global Network Firewall Policies:** Uses Next-Gen Firewall policies (hierarchical-style) instead of legacy VPC rules.
* **Default Deny:** Priority 65530 rule denies all egress traffic.
* **Google APIs:** Priority 1000 rule allows TCP 443 to Restricted Google APIs Virtual IP (VIP).
* **Connectivity:**
* **Cloud NAT:** Optional regional Cloud Router and NAT configuration.
* **Private Service Connect (PSC):** Configures endpoints for internal Google API access.
* **Private Services Access (PSA):** Configures VPC Peering for Google Managed Services (SQL, Redis, etc.).

## Usage

```hcl
module "shared_vpc_foundation" {
source = "terraform-google-modules/network/google//modules/foundation/network"
version = "~> 13.0"

project_id = "my-project-id"
vpc_name = "core-net" # Final VPC Name: vpc-p-core-net

# Naming convention codes
resource_codes = {
short = "p"
long = "production"
}

# Subnet Configuration
subnets = [
{
subnet_name = "sb-prod-us-central1"
subnet_ip = "10.0.0.0/24"
subnet_region = "us-central1"
subnet_private_access = "true"
subnet_flow_logs = "true"
description = "Production workload subnet"
}
]

# DNS Configuration (Example: Spoke peering to a DNS Hub)
dns_config = {
onprem_forwarding = true
type = "spoke" # Triggers DNS Peering
dns_hub_project_id = "my-hub-project"
dns_hub_network_name = "vpc-dns-hub"
domain = "example.com."
}

# Network Connectivity Center (Mesh Topology Example)
ncc_hub_config = {
create_hub = true
name = "global-mesh-hub"
description = "Global VPC Mesh"
preset_topology = "MESH"
hub_labels = { env = "prod" }

# Spoke Configuration
spoke_group = "default" # Use "default" for MESH, "center"/"edge" for STAR
spoke_description = "Core Network Spoke"
auto_accept_projects_default = ["my-project-id"]
}

# Private Service Connect IP (Must be a valid internal IP)
private_service_connect_ip = "10.1.0.5"

# Private Service Access (e.g., for Cloud SQL)
private_service_cidr = "10.2.0.0/16"
}
```

## Architecture Details

### DNS Architecture
The module dynamically creates DNS resources based on `var.dns_config`:

| Configuration | Resulting Resource | Note |
|---------------|--------------------|------|
| `type = "spoke"` | **DNS Peering Zone** | Peers `domain` to `dns_hub_network_name`. |
| `type != "spoke"` | **DNS Forwarding Zone** | Forwards `domain` to `target_name_server_addresses`. |

### Firewall Strategy
This module creates a **Network Firewall Policy** attached to the VPC. It does **not** create standard VPC firewall rules.
1. **Priority 65530 (Egress):** Deny all traffic (Logging enabled).
2. **Priority 1000 (Egress):** Allow TCP 443 to Restricted Google APIs VIP.
3. **Optional:** Allow full internal VPC traffic (Ingress/Egress) if `enable_all_vpc_internal_traffic` is true.

### Network Connectivity Center (NCC)
* **Mesh:** Use `preset_topology = "MESH"` and `spoke_group = "default"`. All attached VPCs can talk to each other.
* **Star:** Use `preset_topology = "STAR"` and `spoke_group = "center"` (Hub) or `"edge"` (Spoke).
* **Route Export:** You can filter which subnets are advertised to the Hub using `spoke_exclude_export_ranges`.

## Requirements

* **Terraform:** `>= 0.13`
* **Provider:** `google` `>= 3.50` (Excluding `6.26.0`, `6.27.0`)
* **Provider:** `google-beta` `>= 3.50`

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| bgp\_always\_compare\_med | If set to true, the Cloud Router will use MED values from the peer even if the AS paths differ. Default is false. | `bool` | `false` | no |
| bgp\_best\_path\_selection\_mode | Specifies the BGP best path selection mode. Valid values are `STANDARD` or `LEGACY`. Default is `LEGACY`. | `string` | `"LEGACY"` | no |
| bgp\_inter\_region\_cost | Specifies the BGP inter-region cost mode. Valid values are `DEFAULT` or `ADD_COST_TO_MED`. | `string` | `null` | no |
| description | An optional description of this network. The resource must be recreated to modify this field. | `string` | `""` | no |
| dns\_config | DNS configuration. | <pre>object({<br> enable_logging = optional(bool, true)<br> onprem_forwarding = optional(bool, false)<br> enable_inbound_forwarding = optional(bool, true)<br> dns_hub_project_id = optional(string, "")<br> dns_hub_network_name = optional(string, "")<br> domain = optional(string, "")<br> type = optional(string, "")<br> target_name_server_addresses = optional(list(map(any)), [])<br> })</pre> | `{}` | no |
| enable\_all\_vpc\_internal\_traffic | Enable firewall policy rule to allow internal traffic (ingress and egress). | `bool` | `false` | no |
| enable\_ipv6\_ula | Enabled IPv6 ULA, this is a permanent change and cannot be undone! (default 'false') | `bool` | `false` | no |
| firewall\_enable\_logging | Toggle firewall logging for VPC Firewalls. | `bool` | `true` | no |
| internal\_ipv6\_range | When enabling IPv6 ULA, optionally, specify a /48 from fd20::/20 (default null) | `string` | `null` | no |
| mtu | The network MTU (If set to 0, meaning MTU is unset - defaults to '1460'). Recommended values: 1460 (default for historic reasons), 1500 (Internet default), or 8896 (for Jumbo packets). Allowed are all values in the range 1300 to 8896, inclusively. | `number` | `0` | no |
| nat\_config | Configuration of NAT cloud router. | <pre>object({<br> enabled = optional(bool, false)<br> bgp_asn = optional(number, 64512)<br> regions = optional(list(object({<br> name = string<br> num_addresses = optional(number, 2)<br> })))<br> })</pre> | `{}` | no |
| ncc\_hub\_config | Network Connectivity Center configuration. | <pre>object({<br> create_hub = optional(bool, true)<br> uri = optional(string)<br> name = optional(string)<br> description = optional(string)<br> hub_labels = optional(map(string))<br> policy_mode = optional(string, "PRESET")<br> preset_topology = optional(string, "MESH")<br> export_psc = optional(bool, false)<br> spoke_labels = optional(map(string))<br> spoke_exclude_export_ranges = optional(set(string), [])<br> spoke_include_export_ranges = optional(set(string), [])<br> spoke_description = optional(string)<br> spoke_group = optional(string, "default")<br> producer_labels = optional(map(string))<br> producer_exclude_export_ranges = optional(set(string), [])<br> producer_include_export_ranges = optional(set(string), [])<br> producer_description = optional(string)<br> auto_accept_projects_center = optional(list(string), [])<br> auto_accept_projects_edge = optional(list(string), [])<br> auto_accept_projects_default = optional(list(string), [])<br> })</pre> | `{}` | no |
| network\_firewall\_policy\_enforcement\_order | Set the order that Firewall Rules and Firewall Policies are evaluated. Valid values are `BEFORE_CLASSIC_FIREWALL` and `AFTER_CLASSIC_FIREWALL`. (default null or equivalent to `AFTER_CLASSIC_FIREWALL`) | `string` | `null` | no |
| network\_profile | "A full or partial URL of the network profile to apply to this network.<br>This field can be set only at resource creation time. For example, the<br>following are valid URLs:<br> * https://www.googleapis.com/compute/beta/projects/{projectId}/global/networkProfiles/{network_profile_name}<br> * projects/{projectId}/global/networkProfiles/{network\_profile\_name} | `string` | `null` | no |
| private\_service\_cidr | CIDR range for private service networking. Used for Cloud SQL and other managed services. | `string` | `null` | no |
| private\_service\_connect\_ip | The subnet internal IP to be used as the private service connect endpoint in the Shared VPC | `string` | n/a | yes |
| project\_id | Project ID for Shared VPC. | `string` | n/a | yes |
| resource\_codes | codes for resources created | <pre>object({<br> short = optional(string, "p")<br> long = optional(string, "production")<br> })</pre> | n/a | yes |
| routing\_mode | The network routing mode (default 'GLOBAL') | `string` | `"GLOBAL"` | no |
| secondary\_ranges | Secondary ranges that will be used in some of the subnets | `map(list(object({ range_name = string, ip_cidr_range = string })))` | `{}` | no |
| subnets | The list of subnets being created | <pre>list(object({<br> subnet_name = string<br> subnet_ip = string<br> subnet_region = string<br> subnet_private_access = optional(string, "false")<br> subnet_private_ipv6_access = optional(string)<br> subnet_flow_logs = optional(string, "false")<br> subnet_flow_logs_interval = optional(string, "INTERVAL_5_SEC")<br> subnet_flow_logs_sampling = optional(string, "0.5")<br> subnet_flow_logs_metadata = optional(string, "INCLUDE_ALL_METADATA")<br> subnet_flow_logs_filter = optional(string, "true")<br> subnet_flow_logs_metadata_fields = optional(list(string), [])<br> description = optional(string)<br> purpose = optional(string)<br> role = optional(string)<br> stack_type = optional(string)<br> ipv6_access_type = optional(string)<br> }))</pre> | `[]` | no |
| vpc\_name | The name of the network being created. Complete name will be `vpc-{vpc_name}` | `string` | n/a | yes |
| windows\_activation\_enabled | Enable Windows license activation for Windows workloads. See https://docs.cloud.google.com/compute/docs/instances/windows/creating-managing-windows-instances . | `bool` | `false` | no |

## Outputs

| Name | Description |
|------|-------------|
| dns\_policy | The name of the DNS policy being created |
| firewall\_policy | Policy created for firewall policy rules. |
| ncc\_hub\_uri | The NCC Hub ID |
| network | The created network |
| network\_name | The name of the VPC being created |
| network\_self\_link | The URI of the VPC being created |
| project\_id | VPC project id |
| route\_names | The route names associated with this VPC |
| subnets | A map with keys of form subnet\_region/subnet\_name and values being the outputs of the google\_compute\_subnetwork resources used to create corresponding subnets. |
| subnets\_flow\_logs | Whether the subnets will have VPC flow logs enabled |
| subnets\_ips | The IPs and CIDRs of the subnets being created |
| subnets\_names | The names of the subnets being created |
| subnets\_private\_access | Whether the subnets will have access to Google API's without a public IP |
| subnets\_regions | The region where the subnets will be created |
| subnets\_secondary\_ranges | The secondary ranges associated with these subnets |
| subnets\_self\_links | The self-links of subnets being created |

<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
78 changes: 78 additions & 0 deletions modules/foundation/network/dns.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/******************************************
Default DNS Policy
*****************************************/

resource "google_dns_policy" "default_policy" {
project = var.project_id
name = "dp-${var.resource_codes.short}-svpc-default-policy"
enable_logging = var.dns_config.enable_logging
enable_inbound_forwarding = var.dns_config.onprem_forwarding ? var.dns_config.enable_inbound_forwarding : false
networks {
network_url = module.main.network_self_link
}
}

/******************************************
Creates DNS Peering to DNS HUB
*****************************************/

data "google_compute_network" "vpc_dns_hub" {
count = var.dns_config.onprem_forwarding && var.dns_config.type == "spoke" ? 1 : 0

name = var.dns_config.dns_hub_network_name
project = var.dns_config.dns_hub_project_id
}

module "peering_zone" {
source = "terraform-google-modules/cloud-dns/google"
version = "~> 6.1"

count = var.dns_config.onprem_forwarding && var.dns_config.type == "spoke" ? 1 : 0

project_id = var.project_id
type = "peering"
name = "dz-${var.resource_codes.short}-svpc-to-dns-hub"
domain = var.dns_config.domain
description = "Private DNS peering zone."

private_visibility_config_networks = [
module.main.network_self_link
]
target_network = data.google_compute_network.vpc_dns_hub[0].self_link
}

/******************************************
DNS Forwarding
*****************************************/
module "dns_forwarding_zone" {
source = "terraform-google-modules/cloud-dns/google"
version = "~> 6.1"

count = var.dns_config.onprem_forwarding && var.dns_config.type != "spoke" ? 1 : 0

project_id = var.project_id
type = "forwarding"
name = "fz-${var.resource_codes.short}-dns-hub"
domain = var.dns_config.domain

private_visibility_config_networks = [
module.main.network_self_link
]
target_name_server_addresses = var.dns_config.target_name_server_addresses
}
Loading