diff --git a/infrastructure/hub-and-spoke-vpns/README.md b/infrastructure/hub-and-spoke-vpns/README.md new file mode 100644 index 000000000..d135bc5f3 --- /dev/null +++ b/infrastructure/hub-and-spoke-vpns/README.md @@ -0,0 +1,69 @@ +# Hub and Spoke VPNs + +This sample creates a simple **Hub and Spoke VPNs** architecture, where network connects every location (VPC Network) through a single intermediary location called a hub. +The benefits of this topology include: +- Network/Security Admin manages Central Services Project (Hub). +- Central services and tools deployed in Central Services Project (Hub) for use by all Service Projects (Spokes). +- Network/Security Admin hands over spoke Projects to respective team who then have full autonomy. +- Network/Security Admin monitors spoke projects for organization security posture compliance using tools like [Forseti](https://forsetisecurity.org/), [CSCC](https://cloud.google.com/security-command-center/) etc deployed in Central Services Project (Hub). +- Spokes communicate with on-prem via VPN to transit hub and then over Interconnect or VPN to on-premises. +- (Optional) Spokes communicate in a full-mesh to each other via VPN transit routing in Central Services Project (Hub). +- This is a decentralized architecture where each spoke project has autonomy to manage all their GCP compute and network resources. + +The purpose of this sample is showing how to wire different [Cloud Foundation Fabric](https://github.com/search?q=topic%3Acft-fabric+org%3Aterraform-google-modules&type=Repositories) modules to create **Hub and Spoke VPNs** network architectures, and as such it is meant to be used for prototyping, or to experiment with networking configurations. Additional best practices and security considerations need to be taken into account for real world usage (eg removal of default service accounts, disabling of external IPs, firewall design, etc). + +![High-level diagram](diagram.png "High-level diagram") + +## Managed resources and services + +This sample creates several distinct groups of resources: + +- three VPC Networks (hub network and two ppoke networks) +- VPC-level resources (VPC, subnets, firewall rules, etc.) +- one Cloud DNS Private zone in the hub project +- one Cloud DNS Forwarding zone in the hub project +- four Cloud DNS Peering zones (two per each spoke project) +- four Cloud Routers (two in hub project and one per each spoke project) +- four Cloud VPNs (two in hub project and one per each spoke project) + +## Test resources + +A set of test resources are included for convenience, as they facilitate experimenting with different networking configurations (firewall rules, external connectivity via VPN, etc.). They are encapsulated in the `test-resources.tf` file, and can be safely removed as a single unit. + +- two virtual machine instances in hub project (one per each region) +- two virtual machine instances in spoke1 project (one per each region) +- two virtual machine instances in spoke2 project (one per each region) + +SSH access to instances is configured via [OS Login](https://cloud.google.com/compute/docs/oslogin/). External access is allowed via the default SSH rule created by the firewall module, and corresponding `ssh` tags on the instances. + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| forwarding\_dns\_zone\_domain | Forwarding DNS Zone Domain. | string | `"on-prem.local."` | no | +| forwarding\_dns\_zone\_name | Forwarding DNS Zone Name. | string | `"on-prem-local"` | no | +| forwarding\_zone\_server\_addresses | Forwarding DNS Zone Server Addresses | list | `` | no | +| hub\_bgp\_asn | Hub BGP ASN. | string | `"64515"` | no | +| hub\_project\_id | Hub Project id. | string | n/a | yes | +| hub\_subnets | Hub VPC subnets configuration. | list | `` | no | +| private\_dns\_zone\_domain | Private DNS Zone Domain. | string | `"gcp.local."` | no | +| private\_dns\_zone\_name | Private DNS Zone Name. | string | `"gcp-local"` | no | +| spoke\_1\_bgp\_asn | Spoke 1 BGP ASN. | string | `"64516"` | no | +| spoke\_1\_project\_id | Spoke 1 Project id. | string | n/a | yes | +| spoke\_1\_subnets | Spoke 1 VPC subnets configuration. | list | `` | no | +| spoke\_2\_bgp\_asn | Spoke 2 BGP ASN. | string | `"64517"` | no | +| spoke\_2\_project\_id | Spoke 2 Project id. | string | n/a | yes | +| spoke\_2\_subnets | Spoke 2 VPC subnets configuration. | list | `` | no | +| spoke\_to\_spoke\_route\_advertisement | Use custom route advertisement in hub routers to advertise all spoke subnets. | string | `"true"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| hub | Hub network resources. | +| spoke-1 | Spoke1 network resources. | +| spoke-2 | Spoke2 network resources. | +| test-instances | Test instance attributes. | + + \ No newline at end of file diff --git a/infrastructure/hub-and-spoke-vpns/diagram.png b/infrastructure/hub-and-spoke-vpns/diagram.png new file mode 100644 index 000000000..17d285118 Binary files /dev/null and b/infrastructure/hub-and-spoke-vpns/diagram.png differ diff --git a/infrastructure/hub-and-spoke-vpns/main.tf b/infrastructure/hub-and-spoke-vpns/main.tf index b56e0177c..21806722a 100644 --- a/infrastructure/hub-and-spoke-vpns/main.tf +++ b/infrastructure/hub-and-spoke-vpns/main.tf @@ -185,6 +185,7 @@ module "vpn-hub-to-spoke-1" { project_id = var.hub_project_id network = module.vpc-hub.network_name region = element(local.hub_subnet_regions, 0) + gateway_name = "hub-to-spoke-1-gtw" tunnel_name_prefix = "hub-to-spoke-1" peer_ips = [module.vpn-spoke-1-to-hub.gateway_ip] bgp_cr_session_range = ["169.254.0.1/30"] @@ -200,6 +201,7 @@ module "vpn-hub-to-spoke-2" { project_id = var.hub_project_id network = module.vpc-hub.network_name region = element(local.hub_subnet_regions, 1) + gateway_name = "hub-to-spoke-2-gtw" tunnel_name_prefix = "hub-to-spoke-2" peer_ips = [module.vpn-spoke-2-to-hub.gateway_ip] bgp_cr_session_range = ["169.254.1.1/30"] @@ -215,6 +217,7 @@ module "vpn-spoke-1-to-hub" { project_id = var.spoke_1_project_id network = module.vpc-spoke-1.network_name region = element(local.spoke_1_subnet_regions, 0) + gateway_name = "spoke-1-to-hub-gtw" tunnel_name_prefix = "spoke-1-to-hub" shared_secret = module.vpn-hub-to-spoke-1.ipsec_secret-dynamic[0] peer_ips = [module.vpn-hub-to-spoke-1.gateway_ip] @@ -231,6 +234,7 @@ module "vpn-spoke-2-to-hub" { project_id = var.spoke_2_project_id network = module.vpc-spoke-2.network_name region = element(local.spoke_2_subnet_regions, 1) + gateway_name = "spoke-2-to-hub-gtw" tunnel_name_prefix = "spoke-2-to-hub" shared_secret = module.vpn-hub-to-spoke-2.ipsec_secret-dynamic[0] peer_ips = [module.vpn-hub-to-spoke-2.gateway_ip] diff --git a/infrastructure/hub-and-spoke-vpns/outputs.tf b/infrastructure/hub-and-spoke-vpns/outputs.tf index 747c1c5cd..24476f8e2 100644 --- a/infrastructure/hub-and-spoke-vpns/outputs.tf +++ b/infrastructure/hub-and-spoke-vpns/outputs.tf @@ -13,6 +13,7 @@ # limitations under the License. output "hub" { + description = "Hub network resources." value = { network_name = module.vpc-hub.network_name subnets_ips = zipmap( @@ -35,6 +36,7 @@ output "hub" { } output "spoke-1" { + description = "Spoke1 network resources." value = { network_name = module.vpc-spoke-1.network_name subnets_ips = zipmap( @@ -45,14 +47,19 @@ output "spoke-1" { module.vpc-spoke-1.subnets_names, module.vpc-spoke-1.subnets_regions ) - peering_dns_zone = { - name = module.spoke-1-peering-zone.name - domain = module.spoke-1-peering-zone.domain + peering_to_hub_private_dns_zone = { + name = module.spoke-1-peering-zone-to-hub-private-zone.name + domain = module.spoke-1-peering-zone-to-hub-private-zone.domain + } + peering_to_hub_forwarding_dns_zone = { + name = module.spoke-1-peering-zone-to-hub-forwarding-zone.name + domain = module.spoke-1-peering-zone-to-hub-forwarding-zone.domain } } } output "spoke-2" { + description = "Spoke2 network resources." value = { network_name = module.vpc-spoke-2.network_name subnets_ips = zipmap( @@ -63,9 +70,13 @@ output "spoke-2" { module.vpc-spoke-2.subnets_names, module.vpc-spoke-2.subnets_regions ) - peering_dns_zone = { - name = module.spoke-2-peering-zone.name - domain = module.spoke-2-peering-zone.domain + peering_to_hub_private_dns_zone = { + name = module.spoke-2-peering-zone-to-hub-private-zone.name + domain = module.spoke-2-peering-zone-to-hub-private-zone.domain + } + peering_to_hub_forwarding_dns_zone = { + name = module.spoke-2-peering-zone-to-hub-forwarding-zone.name + domain = module.spoke-2-peering-zone-to-hub-forwarding-zone.domain } } } diff --git a/infrastructure/hub-and-spoke-vpns/test-resources.tf b/infrastructure/hub-and-spoke-vpns/test-resources.tf index 770cc3d38..a49a7bcea 100644 --- a/infrastructure/hub-and-spoke-vpns/test-resources.tf +++ b/infrastructure/hub-and-spoke-vpns/test-resources.tf @@ -122,6 +122,7 @@ resource "google_dns_record_set" "spoke-2" { ############################################################################### output "test-instances" { + description = "Test instance attributes." value = { hub = { instance_zones = zipmap( diff --git a/infrastructure/hub-and-spoke-vpns/variables.tf b/infrastructure/hub-and-spoke-vpns/variables.tf index 5fa27a851..5ba0ae5b3 100644 --- a/infrastructure/hub-and-spoke-vpns/variables.tf +++ b/infrastructure/hub-and-spoke-vpns/variables.tf @@ -62,12 +62,12 @@ variable "hub_subnets" { variable "spoke_1_subnets" { description = "Spoke 1 VPC subnets configuration." default = [{ - subnet_name = "subnet-a" + subnet_name = "spoke-1-subnet-a" subnet_ip = "10.20.10.0/24" subnet_region = "europe-west1" }, { - subnet_name = "subnet-b" + subnet_name = "spoke-1-subnet-b" subnet_ip = "10.20.20.0/24" subnet_region = "europe-west2" }, @@ -77,12 +77,12 @@ variable "spoke_1_subnets" { variable "spoke_2_subnets" { description = "Spoke 2 VPC subnets configuration." default = [{ - subnet_name = "subnet-a" + subnet_name = "spoke-2-subnet-a" subnet_ip = "10.30.10.0/24" subnet_region = "europe-west1" }, { - subnet_name = "subnet-b" + subnet_name = "spoke-2-subnet-b" subnet_ip = "10.30.20.0/24" subnet_region = "europe-west2" },