From c427fd47a48c2904bdf7e1c7b24572e1d097de72 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Mon, 7 Oct 2019 08:58:24 +0200 Subject: [PATCH 01/16] Hub and Spoke example, initial commit --- infrastructure/net-hub-and-spoke/instances.tf | 65 ++++++++++ infrastructure/net-hub-and-spoke/locals.tf | 32 +++++ infrastructure/net-hub-and-spoke/main.tf | 111 ++++++++++++++++++ infrastructure/net-hub-and-spoke/outputs.tf | 52 ++++++++ infrastructure/net-hub-and-spoke/routers.tf | 91 ++++++++++++++ infrastructure/net-hub-and-spoke/variables.tf | 72 ++++++++++++ 6 files changed, 423 insertions(+) create mode 100644 infrastructure/net-hub-and-spoke/instances.tf create mode 100644 infrastructure/net-hub-and-spoke/locals.tf create mode 100644 infrastructure/net-hub-and-spoke/main.tf create mode 100644 infrastructure/net-hub-and-spoke/outputs.tf create mode 100644 infrastructure/net-hub-and-spoke/routers.tf create mode 100644 infrastructure/net-hub-and-spoke/variables.tf diff --git a/infrastructure/net-hub-and-spoke/instances.tf b/infrastructure/net-hub-and-spoke/instances.tf new file mode 100644 index 000000000..199286d31 --- /dev/null +++ b/infrastructure/net-hub-and-spoke/instances.tf @@ -0,0 +1,65 @@ +# Copyright 2019 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 +# +# https://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. + +resource "google_compute_instance" "hub" { + count = "${length(var.hub_subnet_names)}" + project = "${var.project_id}" + name = "${var.prefix}-hub-${element(var.hub_subnet_names, count.index)}" + machine_type = "f1-micro" + zone = "${element(var.hub_subnet_regions, count.index)}-b" + tags = ["ssh"] + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + network_interface { + subnetwork = "${lookup(module.vpc-hub.subnet_self_links, element(var.hub_subnet_names, count.index))}" + access_config = {} + } +} +resource "google_compute_instance" "spoke-1" { + count = "${length(var.spoke_1_subnet_names)}" + project = "${var.project_id}" + name = "${var.prefix}-spoke-1-${element(var.spoke_1_subnet_names, count.index)}" + machine_type = "f1-micro" + zone = "${element(var.spoke_1_subnet_regions, count.index)}-b" + tags = ["ssh"] + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + network_interface { + subnetwork = "${lookup(module.vpc-spoke-1.subnet_self_links, element(var.spoke_1_subnet_names, count.index))}" + access_config = {} + } +} +resource "google_compute_instance" "spoke-2" { + count = "${length(var.spoke_2_subnet_names)}" + project = "${var.project_id}" + name = "${var.prefix}-spoke-2-${element(var.spoke_2_subnet_names, count.index)}" + machine_type = "f1-micro" + zone = "${element(var.spoke_2_subnet_regions, count.index)}-b" + tags = ["ssh"] + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + } + } + network_interface { + subnetwork = "${lookup(module.vpc-spoke-2.subnet_self_links, element(var.spoke_2_subnet_names, count.index))}" + access_config = {} + } +} \ No newline at end of file diff --git a/infrastructure/net-hub-and-spoke/locals.tf b/infrastructure/net-hub-and-spoke/locals.tf new file mode 100644 index 000000000..2d5494468 --- /dev/null +++ b/infrastructure/net-hub-and-spoke/locals.tf @@ -0,0 +1,32 @@ + +# Copyright 2019 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 +# +# https://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 { + all_subnets = ["${concat( + var.hub_subnet_cidr_ranges, + var.spoke_1_subnet_cidr_ranges, + var.spoke_2_subnet_cidr_ranges + )}"] + hub_to_spoke_1_router = "${ + var.hub_custom_route_advertisement + ? element(concat(google_compute_router.hub-to-spoke-1-custom.*.name, list("")), 0) + : element(concat(google_compute_router.hub-to-spoke-1-default.*.name, list("")), 0) + }" + hub_to_spoke_2_router = "${ + var.hub_custom_route_advertisement + ? element(concat(google_compute_router.hub-to-spoke-2-custom.*.name, list("")), 0) + : element(concat(google_compute_router.hub-to-spoke-2-default.*.name, list("")), 0) + }" +} \ No newline at end of file diff --git a/infrastructure/net-hub-and-spoke/main.tf b/infrastructure/net-hub-and-spoke/main.tf new file mode 100644 index 000000000..df27a8c90 --- /dev/null +++ b/infrastructure/net-hub-and-spoke/main.tf @@ -0,0 +1,111 @@ +# Copyright 2019 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 +# +# https://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 "vpc-hub" { + source = "../../modules/net-vpc-simple" + prefix = "${var.prefix}-hub" + project_id = "${var.project_id}" + subnet_names = ["${var.hub_subnet_names}"] + subnet_regions = ["${var.hub_subnet_regions}"] + subnet_ip_cidr_ranges = ["${var.hub_subnet_cidr_ranges}"] + routing_mode = "GLOBAL" +} +module "vpc-spoke-1" { + source = "../../modules/net-vpc-simple" + prefix = "${var.prefix}-spoke-1" + project_id = "${var.project_id}" + subnet_names = ["${var.spoke_1_subnet_names}"] + subnet_regions = ["${var.spoke_1_subnet_regions}"] + subnet_ip_cidr_ranges = ["${var.spoke_1_subnet_cidr_ranges}"] + routing_mode = "GLOBAL" +} +module "vpc-spoke-2" { + source = "../../modules/net-vpc-simple" + prefix = "${var.prefix}-spoke-2" + project_id = "${var.project_id}" + subnet_names = ["${var.spoke_2_subnet_names}"] + subnet_regions = ["${var.spoke_2_subnet_regions}"] + subnet_ip_cidr_ranges = ["${var.spoke_2_subnet_cidr_ranges}"] + routing_mode = "GLOBAL" +} +module "firewall-hub" { + source = "../../modules/net-firewall" + project_id = "${var.project_id}" + network = "${module.vpc-hub.name}" + admin_ranges_enabled = true + admin_ranges = ["${local.all_subnets}"] +} +module "firewall-spoke-1" { + source = "../../modules/net-firewall" + project_id = "${var.project_id}" + network = "${module.vpc-spoke-1.name}" + admin_ranges_enabled = true + admin_ranges = ["${local.all_subnets}"] +} +module "firewall-spoke-2" { + source = "../../modules/net-firewall" + project_id = "${var.project_id}" + network = "${module.vpc-spoke-2.name}" + admin_ranges_enabled = true + admin_ranges = ["${local.all_subnets}"] +} +module "vpn-hub-to-spoke-1" { + source = "../../modules/net-vpn-dynamic" + project_id = "${var.project_id}" + network = "${module.vpc-hub.name}" + region = "${element(var.hub_subnet_regions, 0)}" + prefix = "hub-to-spoke-1" + peer_ip = "${module.vpn-spoke-1-to-hub.gateway_address}" + bgp_cr_session_range = "169.254.0.1/30" + bgp_remote_session_range = "169.254.0.2" + peer_asn = "${var.spoke_1_bgp_asn}" + router = "${local.hub_to_spoke_1_router}" +} +module "vpn-hub-to-spoke-2" { + source = "../../modules/net-vpn-dynamic" + project_id = "${var.project_id}" + network = "${module.vpc-hub.name}" + region = "${element(var.hub_subnet_regions, 1)}" + prefix = "hub-to-spoke-2" + peer_ip = "${module.vpn-spoke-2-to-hub.gateway_address}" + bgp_cr_session_range = "169.254.1.1/30" + bgp_remote_session_range = "169.254.1.2" + peer_asn = "${var.spoke_2_bgp_asn}" + router = "${local.hub_to_spoke_2_router}" +} +module "vpn-spoke-1-to-hub" { + source = "../../modules/net-vpn-dynamic" + project_id = "${var.project_id}" + network = "${module.vpc-spoke-1.name}" + region = "${element(var.spoke_1_subnet_regions, 0)}" + prefix = "spoke-1-to-hub" + shared_secret = "${module.vpn-hub-to-spoke-1.shared_secret}" + peer_ip = "${module.vpn-hub-to-spoke-1.gateway_address}" + bgp_cr_session_range = "169.254.0.2/30" + bgp_remote_session_range = "169.254.0.1" + peer_asn = "${var.hub_bgp_asn}" + router = "${google_compute_router.spoke-1.name}" +} +module "vpn-spoke-2-to-hub" { + source = "../../modules/net-vpn-dynamic" + project_id = "${var.project_id}" + network = "${module.vpc-spoke-2.name}" + region = "${element(var.spoke_2_subnet_regions, 0)}" + prefix = "spoke-2-to-hub" + shared_secret = "${module.vpn-hub-to-spoke-2.shared_secret}" + peer_ip = "${module.vpn-hub-to-spoke-2.gateway_address}" + bgp_cr_session_range = "169.254.1.2/30" + bgp_remote_session_range = "169.254.1.1" + peer_asn = "${var.hub_bgp_asn}" + router = "${google_compute_router.spoke-2.name}" +} \ No newline at end of file diff --git a/infrastructure/net-hub-and-spoke/outputs.tf b/infrastructure/net-hub-and-spoke/outputs.tf new file mode 100644 index 000000000..bbb25ef14 --- /dev/null +++ b/infrastructure/net-hub-and-spoke/outputs.tf @@ -0,0 +1,52 @@ +# Copyright 2019 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 +# +# https://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. +output "hub" { + value = { + name = "${module.vpc-hub.name}" + subnets = "${zipmap( + values(module.vpc-hub.subnet_names), + values(module.vpc-hub.subnet_ranges) + )}" + instances = "${zipmap( + google_compute_instance.hub.*.name, + google_compute_instance.hub.*.zone + )}" + } +} +output "spoke-1" { + value = { + name = "${module.vpc-spoke-1.name}" + subnets = "${zipmap( + values(module.vpc-spoke-1.subnet_names), + values(module.vpc-spoke-1.subnet_ranges) + )}" + instances = "${zipmap( + google_compute_instance.spoke-1.*.name, + google_compute_instance.spoke-1.*.zone + )}" + } +} +output "spoke-2" { + value = { + name = "${module.vpc-spoke-2.name}" + subnets = "${zipmap( + values(module.vpc-spoke-2.subnet_names), + values(module.vpc-spoke-2.subnet_ranges) + )}" + instances = "${zipmap( + google_compute_instance.spoke-2.*.name, + google_compute_instance.spoke-2.*.zone + )}" + } +} \ No newline at end of file diff --git a/infrastructure/net-hub-and-spoke/routers.tf b/infrastructure/net-hub-and-spoke/routers.tf new file mode 100644 index 000000000..cce21062f --- /dev/null +++ b/infrastructure/net-hub-and-spoke/routers.tf @@ -0,0 +1,91 @@ +# Copyright 2019 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 +# +# https://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. + +resource "null_resource" "spoke-1-ranges-to-advertise" { + count = "${length(var.spoke_1_subnet_names)}" + triggers = { + range = "${element(var.spoke_1_subnet_cidr_ranges, count.index)}" + } +} + +resource "null_resource" "spoke-2-ranges-to-advertise" { + count = "${length(var.spoke_2_subnet_names)}" + triggers = { + range = "${element(var.spoke_2_subnet_cidr_ranges, count.index)}" + } +} +resource "google_compute_router" "hub-to-spoke-1-custom" { + count = "${var.hub_custom_route_advertisement ? 1 : 0}" + name = "hub-to-spoke-1-custom" + region = "${element(var.hub_subnet_regions, 0)}" + network = "${module.vpc-hub.name}" + project = "${var.project_id}" + bgp { + asn = "${var.hub_bgp_asn}" + advertise_mode = "CUSTOM" + advertised_groups = ["ALL_SUBNETS"] + advertised_ip_ranges = ["${null_resource.spoke-2-ranges-to-advertise.*.triggers}"] + } +} +resource "google_compute_router" "hub-to-spoke-2-custom" { + count = "${var.hub_custom_route_advertisement ? 1 : 0}" + name = "hub-to-spoke-2-custom" + region = "${element(var.hub_subnet_regions, 1)}" + network = "${module.vpc-hub.name}" + project = "${var.project_id}" + bgp { + asn = "${var.hub_bgp_asn}" + advertise_mode = "CUSTOM" + advertised_groups = ["ALL_SUBNETS"] + advertised_ip_ranges = ["${null_resource.spoke-1-ranges-to-advertise.*.triggers}"] + } +} +resource "google_compute_router" "hub-to-spoke-1-default" { + count = "${var.hub_custom_route_advertisement ? 0 : 1}" + name = "hub-to-spoke-1-default" + region = "${element(var.hub_subnet_regions, 0)}" + network = "${module.vpc-hub.name}" + project = "${var.project_id}" + bgp { + asn = "${var.hub_bgp_asn}" + } +} +resource "google_compute_router" "hub-to-spoke-2-default" { + count = "${var.hub_custom_route_advertisement ? 0 : 1}" + name = "hub-to-spoke-2-default" + region = "${element(var.hub_subnet_regions, 1)}" + network = "${module.vpc-hub.name}" + project = "${var.project_id}" + bgp { + asn = "${var.hub_bgp_asn}" + } +} +resource "google_compute_router" "spoke-1" { + name = "spoke-1" + region = "${element(var.spoke_1_subnet_regions, 0)}" + network = "${module.vpc-spoke-1.name}" + project = "${var.project_id}" + bgp { + asn = "${var.spoke_1_bgp_asn}" + } +} +resource "google_compute_router" "spoke-2" { + name = "spoke-2" + region = "${element(var.spoke_2_subnet_regions, 0)}" + network = "${module.vpc-spoke-2.name}" + project = "${var.project_id}" + bgp { + asn = "${var.spoke_2_bgp_asn}" + } +} \ No newline at end of file diff --git a/infrastructure/net-hub-and-spoke/variables.tf b/infrastructure/net-hub-and-spoke/variables.tf new file mode 100644 index 000000000..37856b593 --- /dev/null +++ b/infrastructure/net-hub-and-spoke/variables.tf @@ -0,0 +1,72 @@ +# Copyright 2019 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 +# +# https://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. +variable "project_id" { + description = "Project id to use for resources." +} +variable "prefix" { + description = "Prefix for VPC names." +} +variable "hub_subnet_names" { + description = "Hub VPC subnet names." + default = ["a", "b"] +} +variable "hub_subnet_regions" { + description = "Hub subnet regions." + default = ["europe-west1", "europe-west2"] +} +variable "hub_subnet_cidr_ranges" { + description = "Hub subnet IP CIDR ranges." + default = ["10.10.10.0/24", "10.10.20.0/24"] +} +variable "hub_bgp_asn" { + description = "Hub BGP ASN." + default = 64515 +} +variable "hub_custom_route_advertisement" { + description = "Use custom route advertisement in hub routers to advertise all spoke subnets." + default = true +} +variable "spoke_1_subnet_names" { + description = "Spoke 1 VPC subnet names." + default = ["a", "b"] +} +variable "spoke_1_subnet_regions" { + description = "Spoke 1 subnet regions." + default = ["asia-east1", "asia-northeast1"] +} +variable "spoke_1_subnet_cidr_ranges" { + description = "Spoke 1 subnet IP CIDR ranges." + default = ["10.20.10.0/24", "10.20.20.0/24"] +} +variable "spoke_1_bgp_asn" { + description = "Spoke 1 BGP ASN." + default = 64516 +} +variable "spoke_2_subnet_names" { + description = "Spoke 2 VPC subnet names." + default = ["a", "b"] +} +variable "spoke_2_subnet_regions" { + description = "Spoke 2 subnet regions." + default = ["us-west1", "us-west2"] +} +variable "spoke_2_subnet_cidr_ranges" { + description = "Spoke 2 subnet IP CIDR ranges." + default = ["10.30.10.0/24", "10.30.20.0/24"] +} + +variable "spoke_2_bgp_asn" { + description = "Spoke 2 BGP ASN." + default = 64517 +} \ No newline at end of file From e6e7fa8840db2a922c5feca2be815cce5dc31841 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Mon, 7 Oct 2019 18:06:07 +0200 Subject: [PATCH 02/16] Update hub-and-spoke example to use only external cft modules, update the code to HCL2 --- infrastructure/net-hub-and-spoke/instances.tf | 40 ++-- infrastructure/net-hub-and-spoke/locals.tf | 26 +-- infrastructure/net-hub-and-spoke/main.tf | 177 ++++++++++-------- infrastructure/net-hub-and-spoke/outputs.tf | 46 ++--- infrastructure/net-hub-and-spoke/routers.tf | 90 +++++---- infrastructure/net-hub-and-spoke/variables.tf | 110 ++++++----- 6 files changed, 279 insertions(+), 210 deletions(-) diff --git a/infrastructure/net-hub-and-spoke/instances.tf b/infrastructure/net-hub-and-spoke/instances.tf index 199286d31..eef907742 100644 --- a/infrastructure/net-hub-and-spoke/instances.tf +++ b/infrastructure/net-hub-and-spoke/instances.tf @@ -13,11 +13,11 @@ # limitations under the License. resource "google_compute_instance" "hub" { - count = "${length(var.hub_subnet_names)}" - project = "${var.project_id}" - name = "${var.prefix}-hub-${element(var.hub_subnet_names, count.index)}" + count = length(var.hub_subnets) + project = var.hub_project_id + name = "${var.prefix}-hub-${element(var.hub_subnets, count.index)["subnet_name"]}" machine_type = "f1-micro" - zone = "${element(var.hub_subnet_regions, count.index)}-b" + zone = "${element(local.hub_subnet_regions, count.index)}-b" tags = ["ssh"] boot_disk { initialize_params { @@ -25,16 +25,17 @@ resource "google_compute_instance" "hub" { } } network_interface { - subnetwork = "${lookup(module.vpc-hub.subnet_self_links, element(var.hub_subnet_names, count.index))}" - access_config = {} + subnetwork = element(module.vpc-hub.subnets_self_links, count.index) + access_config {} } } + resource "google_compute_instance" "spoke-1" { - count = "${length(var.spoke_1_subnet_names)}" - project = "${var.project_id}" - name = "${var.prefix}-spoke-1-${element(var.spoke_1_subnet_names, count.index)}" + count = length(var.spoke_1_subnets) + project = var.spoke_1_project_id + name = "${var.prefix}-spoke-1-${element(var.spoke_1_subnets, count.index)["subnet_name"]}" machine_type = "f1-micro" - zone = "${element(var.spoke_1_subnet_regions, count.index)}-b" + zone = "${element(local.spoke_1_subnet_regions, count.index)}-b" tags = ["ssh"] boot_disk { initialize_params { @@ -42,16 +43,17 @@ resource "google_compute_instance" "spoke-1" { } } network_interface { - subnetwork = "${lookup(module.vpc-spoke-1.subnet_self_links, element(var.spoke_1_subnet_names, count.index))}" - access_config = {} + subnetwork = element(module.vpc-spoke-1.subnets_self_links, count.index) + access_config {} } } + resource "google_compute_instance" "spoke-2" { - count = "${length(var.spoke_2_subnet_names)}" - project = "${var.project_id}" - name = "${var.prefix}-spoke-2-${element(var.spoke_2_subnet_names, count.index)}" + count = length(var.spoke_2_subnets) + project = var.spoke_2_project_id + name = "${var.prefix}-spoke-2-${element(var.spoke_2_subnets, count.index)["subnet_name"]}" machine_type = "f1-micro" - zone = "${element(var.spoke_2_subnet_regions, count.index)}-b" + zone = "${element(local.spoke_2_subnet_regions, count.index)}-b" tags = ["ssh"] boot_disk { initialize_params { @@ -59,7 +61,7 @@ resource "google_compute_instance" "spoke-2" { } } network_interface { - subnetwork = "${lookup(module.vpc-spoke-2.subnet_self_links, element(var.spoke_2_subnet_names, count.index))}" - access_config = {} + subnetwork = element(module.vpc-spoke-2.subnets_self_links, count.index) + access_config {} } -} \ No newline at end of file +} diff --git a/infrastructure/net-hub-and-spoke/locals.tf b/infrastructure/net-hub-and-spoke/locals.tf index 2d5494468..42f616f3d 100644 --- a/infrastructure/net-hub-and-spoke/locals.tf +++ b/infrastructure/net-hub-and-spoke/locals.tf @@ -14,19 +14,13 @@ # limitations under the License. locals { - all_subnets = ["${concat( - var.hub_subnet_cidr_ranges, - var.spoke_1_subnet_cidr_ranges, - var.spoke_2_subnet_cidr_ranges - )}"] - hub_to_spoke_1_router = "${ - var.hub_custom_route_advertisement - ? element(concat(google_compute_router.hub-to-spoke-1-custom.*.name, list("")), 0) - : element(concat(google_compute_router.hub-to-spoke-1-default.*.name, list("")), 0) - }" - hub_to_spoke_2_router = "${ - var.hub_custom_route_advertisement - ? element(concat(google_compute_router.hub-to-spoke-2-custom.*.name, list("")), 0) - : element(concat(google_compute_router.hub-to-spoke-2-default.*.name, list("")), 0) - }" -} \ No newline at end of file + hub_subnet_regions = [for subnet in var.hub_subnets : subnet["subnet_region"]] + spoke_1_subnet_regions = [for subnet in var.spoke_1_subnets : subnet["subnet_region"]] + spoke_2_subnet_regions = [for subnet in var.spoke_2_subnets : subnet["subnet_region"]] + hub_subnet_cidr_ranges = [for subnet in var.hub_subnets : subnet["subnet_ip"]] + spoke_1_subnet_cidr_ranges = [for subnet in var.spoke_1_subnets : subnet["subnet_ip"]] + spoke_2_subnet_cidr_ranges = [for subnet in var.spoke_2_subnets : subnet["subnet_ip"]] + all_subnet_cidrs = concat(local.hub_subnet_cidr_ranges, local.spoke_1_subnet_cidr_ranges, local.spoke_2_subnet_cidr_ranges) + hub_to_spoke_1_router = var.hub_custom_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-1-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-1-default.*.name, list("")), 0) + hub_to_spoke_2_router = var.hub_custom_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-2-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-2-default.*.name, list("")), 0) +} diff --git a/infrastructure/net-hub-and-spoke/main.tf b/infrastructure/net-hub-and-spoke/main.tf index df27a8c90..dd92dda00 100644 --- a/infrastructure/net-hub-and-spoke/main.tf +++ b/infrastructure/net-hub-and-spoke/main.tf @@ -11,101 +11,126 @@ # 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 "vpc-hub" { - source = "../../modules/net-vpc-simple" - prefix = "${var.prefix}-hub" - project_id = "${var.project_id}" - subnet_names = ["${var.hub_subnet_names}"] - subnet_regions = ["${var.hub_subnet_regions}"] - subnet_ip_cidr_ranges = ["${var.hub_subnet_cidr_ranges}"] - routing_mode = "GLOBAL" + source = "terraform-google-modules/network/google" + version = "~> 1.2.0" + + project_id = var.hub_project_id + network_name = "${var.prefix}-hub" + subnets = var.hub_subnets + routing_mode = "GLOBAL" } + module "vpc-spoke-1" { - source = "../../modules/net-vpc-simple" - prefix = "${var.prefix}-spoke-1" - project_id = "${var.project_id}" - subnet_names = ["${var.spoke_1_subnet_names}"] - subnet_regions = ["${var.spoke_1_subnet_regions}"] - subnet_ip_cidr_ranges = ["${var.spoke_1_subnet_cidr_ranges}"] - routing_mode = "GLOBAL" + source = "terraform-google-modules/network/google" + version = "~> 1.2.0" + + project_id = var.spoke_1_project_id + network_name = "${var.prefix}-spoke-1" + subnets = var.spoke_1_subnets + routing_mode = "GLOBAL" } + module "vpc-spoke-2" { - source = "../../modules/net-vpc-simple" - prefix = "${var.prefix}-spoke-2" - project_id = "${var.project_id}" - subnet_names = ["${var.spoke_2_subnet_names}"] - subnet_regions = ["${var.spoke_2_subnet_regions}"] - subnet_ip_cidr_ranges = ["${var.spoke_2_subnet_cidr_ranges}"] - routing_mode = "GLOBAL" + source = "terraform-google-modules/network/google" + version = "~> 1.2.0" + + project_id = var.spoke_2_project_id + network_name = "${var.prefix}-spoke-2" + subnets = var.spoke_2_subnets + routing_mode = "GLOBAL" } + module "firewall-hub" { - source = "../../modules/net-firewall" - project_id = "${var.project_id}" - network = "${module.vpc-hub.name}" + source = "terraform-google-modules/network/google//modules/fabric-net-firewall" + version = "~> 1.2.0" + + project_id = var.hub_project_id + network = module.vpc-hub.network_name admin_ranges_enabled = true - admin_ranges = ["${local.all_subnets}"] + admin_ranges = local.all_subnet_cidrs } + module "firewall-spoke-1" { - source = "../../modules/net-firewall" - project_id = "${var.project_id}" - network = "${module.vpc-spoke-1.name}" + source = "terraform-google-modules/network/google//modules/fabric-net-firewall" + version = "~> 1.2.0" + + project_id = var.spoke_1_project_id + network = module.vpc-spoke-1.network_name admin_ranges_enabled = true - admin_ranges = ["${local.all_subnets}"] + admin_ranges = local.all_subnet_cidrs } + module "firewall-spoke-2" { - source = "../../modules/net-firewall" - project_id = "${var.project_id}" - network = "${module.vpc-spoke-2.name}" + source = "terraform-google-modules/network/google//modules/fabric-net-firewall" + version = "~> 1.2.0" + + project_id = var.spoke_2_project_id + network = module.vpc-spoke-2.network_name admin_ranges_enabled = true - admin_ranges = ["${local.all_subnets}"] + admin_ranges = local.all_subnet_cidrs } + + module "vpn-hub-to-spoke-1" { - source = "../../modules/net-vpn-dynamic" - project_id = "${var.project_id}" - network = "${module.vpc-hub.name}" - region = "${element(var.hub_subnet_regions, 0)}" - prefix = "hub-to-spoke-1" - peer_ip = "${module.vpn-spoke-1-to-hub.gateway_address}" - bgp_cr_session_range = "169.254.0.1/30" - bgp_remote_session_range = "169.254.0.2" - peer_asn = "${var.spoke_1_bgp_asn}" - router = "${local.hub_to_spoke_1_router}" + source = "terraform-google-modules/vpn/google" + version = "~> 1.1.0" + + project_id = var.hub_project_id + network = module.vpc-hub.network_name + region = element(local.hub_subnet_regions, 0) + 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"] + bgp_remote_session_range = ["169.254.0.2"] + peer_asn = [var.spoke_1_bgp_asn] + cr_name = local.hub_to_spoke_1_router } + module "vpn-hub-to-spoke-2" { - source = "../../modules/net-vpn-dynamic" - project_id = "${var.project_id}" - network = "${module.vpc-hub.name}" - region = "${element(var.hub_subnet_regions, 1)}" - prefix = "hub-to-spoke-2" - peer_ip = "${module.vpn-spoke-2-to-hub.gateway_address}" - bgp_cr_session_range = "169.254.1.1/30" - bgp_remote_session_range = "169.254.1.2" - peer_asn = "${var.spoke_2_bgp_asn}" - router = "${local.hub_to_spoke_2_router}" + source = "terraform-google-modules/vpn/google" + version = "~> 1.1.0" + + project_id = var.hub_project_id + network = module.vpc-hub.network_name + region = element(local.hub_subnet_regions, 1) + 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"] + bgp_remote_session_range = ["169.254.1.2"] + peer_asn = [var.spoke_2_bgp_asn] + cr_name = local.hub_to_spoke_2_router } + module "vpn-spoke-1-to-hub" { - source = "../../modules/net-vpn-dynamic" - project_id = "${var.project_id}" - network = "${module.vpc-spoke-1.name}" - region = "${element(var.spoke_1_subnet_regions, 0)}" - prefix = "spoke-1-to-hub" - shared_secret = "${module.vpn-hub-to-spoke-1.shared_secret}" - peer_ip = "${module.vpn-hub-to-spoke-1.gateway_address}" - bgp_cr_session_range = "169.254.0.2/30" - bgp_remote_session_range = "169.254.0.1" - peer_asn = "${var.hub_bgp_asn}" - router = "${google_compute_router.spoke-1.name}" + source = "terraform-google-modules/vpn/google" + version = "~> 1.1.0" + + project_id = var.spoke_1_project_id + network = module.vpc-spoke-1.network_name + region = element(local.spoke_1_subnet_regions, 0) + 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] + bgp_cr_session_range = ["169.254.0.2/30"] + bgp_remote_session_range = ["169.254.0.1"] + peer_asn = [var.hub_bgp_asn] + cr_name = google_compute_router.spoke-1.name } + module "vpn-spoke-2-to-hub" { - source = "../../modules/net-vpn-dynamic" - project_id = "${var.project_id}" - network = "${module.vpc-spoke-2.name}" - region = "${element(var.spoke_2_subnet_regions, 0)}" - prefix = "spoke-2-to-hub" - shared_secret = "${module.vpn-hub-to-spoke-2.shared_secret}" - peer_ip = "${module.vpn-hub-to-spoke-2.gateway_address}" - bgp_cr_session_range = "169.254.1.2/30" - bgp_remote_session_range = "169.254.1.1" - peer_asn = "${var.hub_bgp_asn}" - router = "${google_compute_router.spoke-2.name}" -} \ No newline at end of file + source = "terraform-google-modules/vpn/google" + version = "~> 1.1.0" + + project_id = var.spoke_2_project_id + network = module.vpc-spoke-2.network_name + region = element(local.spoke_2_subnet_regions, 0) + 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] + bgp_cr_session_range = ["169.254.1.2/30"] + bgp_remote_session_range = ["169.254.1.1"] + peer_asn = [var.hub_bgp_asn] + cr_name = google_compute_router.spoke-2.name +} diff --git a/infrastructure/net-hub-and-spoke/outputs.tf b/infrastructure/net-hub-and-spoke/outputs.tf index bbb25ef14..0b19d8a6e 100644 --- a/infrastructure/net-hub-and-spoke/outputs.tf +++ b/infrastructure/net-hub-and-spoke/outputs.tf @@ -11,42 +11,44 @@ # 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. + output "hub" { value = { - name = "${module.vpc-hub.name}" - subnets = "${zipmap( - values(module.vpc-hub.subnet_names), - values(module.vpc-hub.subnet_ranges) - )}" - instances = "${zipmap( + name = module.vpc-hub.network_name + subnets = zipmap( + module.vpc-hub.subnets_names, + module.vpc-hub.subnets_ips + ) + instances = zipmap( google_compute_instance.hub.*.name, google_compute_instance.hub.*.zone - )}" + ) } } + output "spoke-1" { value = { - name = "${module.vpc-spoke-1.name}" - subnets = "${zipmap( - values(module.vpc-spoke-1.subnet_names), - values(module.vpc-spoke-1.subnet_ranges) - )}" - instances = "${zipmap( + name = module.vpc-spoke-1.network_name + subnets = zipmap( + module.vpc-spoke-1.subnets_names, + module.vpc-spoke-1.subnets_ips + ) + instances = zipmap( google_compute_instance.spoke-1.*.name, google_compute_instance.spoke-1.*.zone - )}" + ) } } output "spoke-2" { value = { - name = "${module.vpc-spoke-2.name}" - subnets = "${zipmap( - values(module.vpc-spoke-2.subnet_names), - values(module.vpc-spoke-2.subnet_ranges) - )}" - instances = "${zipmap( + name = module.vpc-spoke-2.network_name + subnets = zipmap( + module.vpc-spoke-2.subnets_names, + module.vpc-spoke-2.subnets_ips + ) + instances = zipmap( google_compute_instance.spoke-2.*.name, google_compute_instance.spoke-2.*.zone - )}" + ) } -} \ No newline at end of file +} diff --git a/infrastructure/net-hub-and-spoke/routers.tf b/infrastructure/net-hub-and-spoke/routers.tf index cce21062f..f159b61fe 100644 --- a/infrastructure/net-hub-and-spoke/routers.tf +++ b/infrastructure/net-hub-and-spoke/routers.tf @@ -13,79 +13,99 @@ # limitations under the License. resource "null_resource" "spoke-1-ranges-to-advertise" { - count = "${length(var.spoke_1_subnet_names)}" + count = length(local.spoke_1_subnet_cidr_ranges) triggers = { - range = "${element(var.spoke_1_subnet_cidr_ranges, count.index)}" + range = element(local.spoke_1_subnet_cidr_ranges, count.index) } } resource "null_resource" "spoke-2-ranges-to-advertise" { - count = "${length(var.spoke_2_subnet_names)}" + count = length(local.spoke_2_subnet_cidr_ranges) triggers = { - range = "${element(var.spoke_2_subnet_cidr_ranges, count.index)}" + range = element(local.spoke_2_subnet_cidr_ranges, count.index) } } + resource "google_compute_router" "hub-to-spoke-1-custom" { - count = "${var.hub_custom_route_advertisement ? 1 : 0}" + count = var.hub_custom_route_advertisement ? 1 : 0 name = "hub-to-spoke-1-custom" - region = "${element(var.hub_subnet_regions, 0)}" - network = "${module.vpc-hub.name}" - project = "${var.project_id}" + region = element(local.hub_subnet_regions, 0) + network = module.vpc-hub.network_name + project = var.hub_project_id bgp { - asn = "${var.hub_bgp_asn}" + asn = var.hub_bgp_asn advertise_mode = "CUSTOM" advertised_groups = ["ALL_SUBNETS"] - advertised_ip_ranges = ["${null_resource.spoke-2-ranges-to-advertise.*.triggers}"] + + dynamic "advertised_ip_ranges" { + for_each = [for trigger in null_resource.spoke-1-ranges-to-advertise.*.triggers: { + range = trigger["range"] + }] + + content { + range = advertised_ip_ranges.value.range + } + } } } + resource "google_compute_router" "hub-to-spoke-2-custom" { - count = "${var.hub_custom_route_advertisement ? 1 : 0}" + count = var.hub_custom_route_advertisement ? 1 : 0 name = "hub-to-spoke-2-custom" - region = "${element(var.hub_subnet_regions, 1)}" - network = "${module.vpc-hub.name}" - project = "${var.project_id}" + region = element(local.hub_subnet_regions, 1) + network = module.vpc-hub.network_name + project = var.hub_project_id bgp { - asn = "${var.hub_bgp_asn}" + asn = var.hub_bgp_asn advertise_mode = "CUSTOM" advertised_groups = ["ALL_SUBNETS"] - advertised_ip_ranges = ["${null_resource.spoke-1-ranges-to-advertise.*.triggers}"] + dynamic "advertised_ip_ranges" { + for_each = [for trigger in null_resource.spoke-2-ranges-to-advertise.*.triggers: { + range = trigger["range"] + }] + + content { + range = advertised_ip_ranges.value.range + } + } } } + resource "google_compute_router" "hub-to-spoke-1-default" { - count = "${var.hub_custom_route_advertisement ? 0 : 1}" + count = var.hub_custom_route_advertisement ? 0 : 1 name = "hub-to-spoke-1-default" - region = "${element(var.hub_subnet_regions, 0)}" - network = "${module.vpc-hub.name}" - project = "${var.project_id}" + region = element(local.hub_subnet_regions, 0) + network = module.vpc-hub.network_name + project = var.hub_project_id bgp { - asn = "${var.hub_bgp_asn}" + asn = var.hub_bgp_asn } } resource "google_compute_router" "hub-to-spoke-2-default" { - count = "${var.hub_custom_route_advertisement ? 0 : 1}" + count = var.hub_custom_route_advertisement ? 0 : 1 name = "hub-to-spoke-2-default" - region = "${element(var.hub_subnet_regions, 1)}" - network = "${module.vpc-hub.name}" - project = "${var.project_id}" + region = element(local.hub_subnet_regions, 1) + network = module.vpc-hub.network_name + project = var.hub_project_id bgp { - asn = "${var.hub_bgp_asn}" + asn = var.hub_bgp_asn } } resource "google_compute_router" "spoke-1" { name = "spoke-1" - region = "${element(var.spoke_1_subnet_regions, 0)}" - network = "${module.vpc-spoke-1.name}" - project = "${var.project_id}" + region = element(local.spoke_1_subnet_regions, 0) + network = module.vpc-spoke-1.network_name + project = var.spoke_1_project_id bgp { - asn = "${var.spoke_1_bgp_asn}" + asn = var.spoke_1_bgp_asn } } resource "google_compute_router" "spoke-2" { name = "spoke-2" - region = "${element(var.spoke_2_subnet_regions, 0)}" - network = "${module.vpc-spoke-2.name}" - project = "${var.project_id}" + region = element(local.spoke_2_subnet_regions, 0) + network = module.vpc-spoke-2.network_name + project = var.spoke_2_project_id bgp { - asn = "${var.spoke_2_bgp_asn}" + asn = var.spoke_2_bgp_asn } -} \ No newline at end of file +} diff --git a/infrastructure/net-hub-and-spoke/variables.tf b/infrastructure/net-hub-and-spoke/variables.tf index 37856b593..e9730aa57 100644 --- a/infrastructure/net-hub-and-spoke/variables.tf +++ b/infrastructure/net-hub-and-spoke/variables.tf @@ -11,62 +11,88 @@ # 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. -variable "project_id" { - description = "Project id to use for resources." + +variable "hub_project_id" { + description = "Hub Project id." } + +variable "spoke_1_project_id" { + description = "Spoke 1 Project id." +} + +variable "spoke_2_project_id" { + description = "Spoke 2 Project id." +} + variable "prefix" { description = "Prefix for VPC names." } -variable "hub_subnet_names" { - description = "Hub VPC subnet names." - default = ["a", "b"] -} -variable "hub_subnet_regions" { - description = "Hub subnet regions." - default = ["europe-west1", "europe-west2"] -} -variable "hub_subnet_cidr_ranges" { - description = "Hub subnet IP CIDR ranges." - default = ["10.10.10.0/24", "10.10.20.0/24"] -} -variable "hub_bgp_asn" { - description = "Hub BGP ASN." - default = 64515 -} + variable "hub_custom_route_advertisement" { description = "Use custom route advertisement in hub routers to advertise all spoke subnets." default = true } -variable "spoke_1_subnet_names" { - description = "Spoke 1 VPC subnet names." - default = ["a", "b"] -} -variable "spoke_1_subnet_regions" { - description = "Spoke 1 subnet regions." - default = ["asia-east1", "asia-northeast1"] -} -variable "spoke_1_subnet_cidr_ranges" { - description = "Spoke 1 subnet IP CIDR ranges." - default = ["10.20.10.0/24", "10.20.20.0/24"] + +variable "hub_bgp_asn" { + description = "Hub BGP ASN." + default = 64515 } + variable "spoke_1_bgp_asn" { description = "Spoke 1 BGP ASN." default = 64516 } -variable "spoke_2_subnet_names" { - description = "Spoke 2 VPC subnet names." - default = ["a", "b"] -} -variable "spoke_2_subnet_regions" { - description = "Spoke 2 subnet regions." - default = ["us-west1", "us-west2"] -} -variable "spoke_2_subnet_cidr_ranges" { - description = "Spoke 2 subnet IP CIDR ranges." - default = ["10.30.10.0/24", "10.30.20.0/24"] -} variable "spoke_2_bgp_asn" { description = "Spoke 2 BGP ASN." default = 64517 -} \ No newline at end of file +} + +variable "hub_subnets" { + description = "Hub VPC subnets configuration." + default = [{ + subnet_name = "subnet-a" + subnet_ip = "10.10.10.0/24" + subnet_region = "europe-west1" + }, + { + + subnet_name = "subnet-b" + subnet_ip = "10.10.20.0/24" + subnet_region = "europe-west2" + }, + ] +} + +variable "spoke_1_subnets" { + description = "Spoke 1 VPC subnets configuration." + default = [{ + subnet_name = "subnet-a" + subnet_ip = "10.20.10.0/24" + subnet_region = "asia-east1" + }, + { + + subnet_name = "subnet-b" + subnet_ip = "10.20.20.0/24" + subnet_region = "asia-northeast1" + }, + ] +} + +variable "spoke_2_subnets" { + description = "Spoke 2 VPC subnets configuration." + default = [{ + subnet_name = "subnet-a" + subnet_ip = "10.30.10.0/24" + subnet_region = "us-west1" + }, + { + + subnet_name = "subnet-b" + subnet_ip = "10.30.20.0/24" + subnet_region = "us-west2" + }, + ] +} + From 9606ceca45f356e702e7e6a219ca6113b169bd6c Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Mon, 7 Oct 2019 18:25:23 +0200 Subject: [PATCH 03/16] Add tfvars sample --- infrastructure/net-hub-and-spoke/terraform.tfvars.sample | 4 ++++ infrastructure/net-hub-and-spoke/variables.tf | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 infrastructure/net-hub-and-spoke/terraform.tfvars.sample diff --git a/infrastructure/net-hub-and-spoke/terraform.tfvars.sample b/infrastructure/net-hub-and-spoke/terraform.tfvars.sample new file mode 100644 index 000000000..5d94a27a6 --- /dev/null +++ b/infrastructure/net-hub-and-spoke/terraform.tfvars.sample @@ -0,0 +1,4 @@ +hub_project_id = "automation-examples" +spoke_1_project_id = "automation-examples" +spoke_2_project_id = "automation-examples" +prefix = "test" \ No newline at end of file diff --git a/infrastructure/net-hub-and-spoke/variables.tf b/infrastructure/net-hub-and-spoke/variables.tf index e9730aa57..13156e8e9 100644 --- a/infrastructure/net-hub-and-spoke/variables.tf +++ b/infrastructure/net-hub-and-spoke/variables.tf @@ -30,7 +30,7 @@ variable "prefix" { variable "hub_custom_route_advertisement" { description = "Use custom route advertisement in hub routers to advertise all spoke subnets." - default = true + default = false } variable "hub_bgp_asn" { From 8eb8c844638e9040437e47c9e262f314cf5a16a7 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Sun, 13 Oct 2019 15:43:26 +0200 Subject: [PATCH 04/16] Fix spoke to spoke route advertisement --- infrastructure/net-hub-and-spoke/locals.tf | 4 +- infrastructure/net-hub-and-spoke/main.tf | 31 ++++++++----- infrastructure/net-hub-and-spoke/routers.tf | 50 +++++++-------------- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/infrastructure/net-hub-and-spoke/locals.tf b/infrastructure/net-hub-and-spoke/locals.tf index 42f616f3d..80572b6e8 100644 --- a/infrastructure/net-hub-and-spoke/locals.tf +++ b/infrastructure/net-hub-and-spoke/locals.tf @@ -21,6 +21,6 @@ locals { spoke_1_subnet_cidr_ranges = [for subnet in var.spoke_1_subnets : subnet["subnet_ip"]] spoke_2_subnet_cidr_ranges = [for subnet in var.spoke_2_subnets : subnet["subnet_ip"]] all_subnet_cidrs = concat(local.hub_subnet_cidr_ranges, local.spoke_1_subnet_cidr_ranges, local.spoke_2_subnet_cidr_ranges) - hub_to_spoke_1_router = var.hub_custom_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-1-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-1-default.*.name, list("")), 0) - hub_to_spoke_2_router = var.hub_custom_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-2-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-2-default.*.name, list("")), 0) + hub_to_spoke_1_router = var.spoke_to_spoke_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-1-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-1-default.*.name, list("")), 0) + hub_to_spoke_2_router = var.spoke_to_spoke_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-2-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-2-default.*.name, list("")), 0) } diff --git a/infrastructure/net-hub-and-spoke/main.tf b/infrastructure/net-hub-and-spoke/main.tf index dd92dda00..6b988e98e 100644 --- a/infrastructure/net-hub-and-spoke/main.tf +++ b/infrastructure/net-hub-and-spoke/main.tf @@ -12,9 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +############################################################## +# VPCs # +############################################################## + module "vpc-hub" { source = "terraform-google-modules/network/google" - version = "~> 1.2.0" + version = "~> 1.2" project_id = var.hub_project_id network_name = "${var.prefix}-hub" @@ -24,7 +28,7 @@ module "vpc-hub" { module "vpc-spoke-1" { source = "terraform-google-modules/network/google" - version = "~> 1.2.0" + version = "~> 1.2" project_id = var.spoke_1_project_id network_name = "${var.prefix}-spoke-1" @@ -34,7 +38,7 @@ module "vpc-spoke-1" { module "vpc-spoke-2" { source = "terraform-google-modules/network/google" - version = "~> 1.2.0" + version = "~> 1.2" project_id = var.spoke_2_project_id network_name = "${var.prefix}-spoke-2" @@ -42,9 +46,13 @@ module "vpc-spoke-2" { routing_mode = "GLOBAL" } +############################################################## +# Firewalls # +############################################################## + module "firewall-hub" { source = "terraform-google-modules/network/google//modules/fabric-net-firewall" - version = "~> 1.2.0" + version = "~> 1.2" project_id = var.hub_project_id network = module.vpc-hub.network_name @@ -54,7 +62,7 @@ module "firewall-hub" { module "firewall-spoke-1" { source = "terraform-google-modules/network/google//modules/fabric-net-firewall" - version = "~> 1.2.0" + version = "~> 1.2" project_id = var.spoke_1_project_id network = module.vpc-spoke-1.network_name @@ -64,7 +72,7 @@ module "firewall-spoke-1" { module "firewall-spoke-2" { source = "terraform-google-modules/network/google//modules/fabric-net-firewall" - version = "~> 1.2.0" + version = "~> 1.2" project_id = var.spoke_2_project_id network = module.vpc-spoke-2.network_name @@ -72,10 +80,13 @@ module "firewall-spoke-2" { admin_ranges = local.all_subnet_cidrs } +############################################################## +# VPNs # +############################################################## module "vpn-hub-to-spoke-1" { source = "terraform-google-modules/vpn/google" - version = "~> 1.1.0" + version = "~> 1.1" project_id = var.hub_project_id network = module.vpc-hub.network_name @@ -90,7 +101,7 @@ module "vpn-hub-to-spoke-1" { module "vpn-hub-to-spoke-2" { source = "terraform-google-modules/vpn/google" - version = "~> 1.1.0" + version = "~> 1.1" project_id = var.hub_project_id network = module.vpc-hub.network_name @@ -105,7 +116,7 @@ module "vpn-hub-to-spoke-2" { module "vpn-spoke-1-to-hub" { source = "terraform-google-modules/vpn/google" - version = "~> 1.1.0" + version = "~> 1.1" project_id = var.spoke_1_project_id network = module.vpc-spoke-1.network_name @@ -121,7 +132,7 @@ module "vpn-spoke-1-to-hub" { module "vpn-spoke-2-to-hub" { source = "terraform-google-modules/vpn/google" - version = "~> 1.1.0" + version = "~> 1.1" project_id = var.spoke_2_project_id network = module.vpc-spoke-2.network_name diff --git a/infrastructure/net-hub-and-spoke/routers.tf b/infrastructure/net-hub-and-spoke/routers.tf index f159b61fe..2f2ab3b4c 100644 --- a/infrastructure/net-hub-and-spoke/routers.tf +++ b/infrastructure/net-hub-and-spoke/routers.tf @@ -12,67 +12,51 @@ # See the License for the specific language governing permissions and # limitations under the License. -resource "null_resource" "spoke-1-ranges-to-advertise" { - count = length(local.spoke_1_subnet_cidr_ranges) - triggers = { - range = element(local.spoke_1_subnet_cidr_ranges, count.index) - } -} - -resource "null_resource" "spoke-2-ranges-to-advertise" { - count = length(local.spoke_2_subnet_cidr_ranges) - triggers = { - range = element(local.spoke_2_subnet_cidr_ranges, count.index) - } -} +############################################################## +# Cloud Routers # +############################################################## resource "google_compute_router" "hub-to-spoke-1-custom" { - count = var.hub_custom_route_advertisement ? 1 : 0 + count = var.spoke_to_spoke_route_advertisement ? 1 : 0 name = "hub-to-spoke-1-custom" region = element(local.hub_subnet_regions, 0) network = module.vpc-hub.network_name project = var.hub_project_id bgp { - asn = var.hub_bgp_asn - advertise_mode = "CUSTOM" - advertised_groups = ["ALL_SUBNETS"] + asn = var.hub_bgp_asn + advertise_mode = "CUSTOM" + advertised_groups = ["ALL_SUBNETS"] dynamic "advertised_ip_ranges" { - for_each = [for trigger in null_resource.spoke-1-ranges-to-advertise.*.triggers: { - range = trigger["range"] - }] - + for_each = toset(local.spoke_2_subnet_cidr_ranges) content { - range = advertised_ip_ranges.value.range + range = advertised_ip_ranges.value } } } } resource "google_compute_router" "hub-to-spoke-2-custom" { - count = var.hub_custom_route_advertisement ? 1 : 0 + count = var.spoke_to_spoke_route_advertisement ? 1 : 0 name = "hub-to-spoke-2-custom" region = element(local.hub_subnet_regions, 1) network = module.vpc-hub.network_name project = var.hub_project_id bgp { - asn = var.hub_bgp_asn - advertise_mode = "CUSTOM" - advertised_groups = ["ALL_SUBNETS"] + asn = var.hub_bgp_asn + advertise_mode = "CUSTOM" + advertised_groups = ["ALL_SUBNETS"] dynamic "advertised_ip_ranges" { - for_each = [for trigger in null_resource.spoke-2-ranges-to-advertise.*.triggers: { - range = trigger["range"] - }] - + for_each = toset(local.spoke_1_subnet_cidr_ranges) content { - range = advertised_ip_ranges.value.range + range = advertised_ip_ranges.value } } } } resource "google_compute_router" "hub-to-spoke-1-default" { - count = var.hub_custom_route_advertisement ? 0 : 1 + count = var.spoke_to_spoke_route_advertisement ? 0 : 1 name = "hub-to-spoke-1-default" region = element(local.hub_subnet_regions, 0) network = module.vpc-hub.network_name @@ -82,7 +66,7 @@ resource "google_compute_router" "hub-to-spoke-1-default" { } } resource "google_compute_router" "hub-to-spoke-2-default" { - count = var.hub_custom_route_advertisement ? 0 : 1 + count = var.spoke_to_spoke_route_advertisement ? 0 : 1 name = "hub-to-spoke-2-default" region = element(local.hub_subnet_regions, 1) network = module.vpc-hub.network_name From 7297dab63e769453c87906167230be5941360aae Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Sun, 13 Oct 2019 15:44:15 +0200 Subject: [PATCH 05/16] Add private, peering, forwarding dns zones --- infrastructure/net-hub-and-spoke/dns.tf | 68 +++++++++++++++++++ infrastructure/net-hub-and-spoke/instances.tf | 6 +- infrastructure/net-hub-and-spoke/variables.tf | 29 +++++++- 3 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 infrastructure/net-hub-and-spoke/dns.tf diff --git a/infrastructure/net-hub-and-spoke/dns.tf b/infrastructure/net-hub-and-spoke/dns.tf new file mode 100644 index 000000000..b0e9932b9 --- /dev/null +++ b/infrastructure/net-hub-and-spoke/dns.tf @@ -0,0 +1,68 @@ +# Copyright 2019 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 +# +# https://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. + +############################################################## +# DNS Zones # +############################################################## + +module "hub-private-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.hub_project_id + type = "private" + name = "${var.private_dns_zone_name}-hub-private" + domain = var.private_dns_zone_domain + + private_visibility_config_networks = [module.vpc-hub.network_self_link] +} + +module "spoke-1-peering-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.spoke_1_project_id + type = "peering" + name = "${var.private_dns_zone_name}-spoke-1-peering" + domain = var.private_dns_zone_domain + + private_visibility_config_networks = [module.vpc-spoke-1.network_self_link] + target_network = module.vpc-hub.network_self_link +} + +module "spoke-2-peering-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.spoke_2_project_id + type = "peering" + name = "${var.private_dns_zone_name}-spoke-2-peering" + domain = var.private_dns_zone_domain + + private_visibility_config_networks = [module.vpc-spoke-2.network_self_link] + target_network = module.vpc-hub.network_self_link +} + +module "hub-forwarding-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.hub_project_id + type = "forwarding" + name = "${var.forwarding_dns_zone_name}-hub-forwarding" + domain = var.forwarding_dns_zone_domain + + private_visibility_config_networks = [module.vpc-hub.network_self_link] + target_name_server_addresses = var.forwarding_zone_server_addresses +} \ No newline at end of file diff --git a/infrastructure/net-hub-and-spoke/instances.tf b/infrastructure/net-hub-and-spoke/instances.tf index eef907742..a1f8cfc23 100644 --- a/infrastructure/net-hub-and-spoke/instances.tf +++ b/infrastructure/net-hub-and-spoke/instances.tf @@ -25,7 +25,7 @@ resource "google_compute_instance" "hub" { } } network_interface { - subnetwork = element(module.vpc-hub.subnets_self_links, count.index) + subnetwork = element(module.vpc-hub.subnets_self_links, count.index) access_config {} } } @@ -43,7 +43,7 @@ resource "google_compute_instance" "spoke-1" { } } network_interface { - subnetwork = element(module.vpc-spoke-1.subnets_self_links, count.index) + subnetwork = element(module.vpc-spoke-1.subnets_self_links, count.index) access_config {} } } @@ -61,7 +61,7 @@ resource "google_compute_instance" "spoke-2" { } } network_interface { - subnetwork = element(module.vpc-spoke-2.subnets_self_links, count.index) + subnetwork = element(module.vpc-spoke-2.subnets_self_links, count.index) access_config {} } } diff --git a/infrastructure/net-hub-and-spoke/variables.tf b/infrastructure/net-hub-and-spoke/variables.tf index 13156e8e9..29ec86ff5 100644 --- a/infrastructure/net-hub-and-spoke/variables.tf +++ b/infrastructure/net-hub-and-spoke/variables.tf @@ -28,9 +28,9 @@ variable "prefix" { description = "Prefix for VPC names." } -variable "hub_custom_route_advertisement" { +variable "spoke_to_spoke_route_advertisement" { description = "Use custom route advertisement in hub routers to advertise all spoke subnets." - default = false + default = true } variable "hub_bgp_asn" { @@ -96,3 +96,28 @@ variable "spoke_2_subnets" { ] } +variable "private_dns_zone_name" { + description = "Private DNS Zone Name." + default = "gcp-private" +} + +variable "private_dns_zone_domain" { + description = "Private DNS Zone Domain." + default = "gcp.private" +} + +variable "forwarding_dns_zone_name" { + description = "Forwarding DNS Zone Name." + default = "on-prem-private" +} + +variable "forwarding_dns_zone_domain" { + description = "Forwarding DNS Zone Domain." + default = "on-prem.private" +} + +variable "forwarding_zone_server_addresses" { + description = "Forwarding DNS Zone Server Addresses" + default = ["8.8.8.8", "8.8.4.4"] +} + \ No newline at end of file From 12fda26e10a92fee6672100e78010b221990ca04 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Sun, 13 Oct 2019 16:26:50 +0200 Subject: [PATCH 06/16] Add DNS entries for the test instances --- infrastructure/net-hub-and-spoke/instances.tf | 39 +++++++++++++++++++ infrastructure/net-hub-and-spoke/variables.tf | 8 ++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/infrastructure/net-hub-and-spoke/instances.tf b/infrastructure/net-hub-and-spoke/instances.tf index a1f8cfc23..127e5e12c 100644 --- a/infrastructure/net-hub-and-spoke/instances.tf +++ b/infrastructure/net-hub-and-spoke/instances.tf @@ -30,6 +30,19 @@ resource "google_compute_instance" "hub" { } } +resource "google_dns_record_set" "hub" { + count = length(var.hub_subnets) + + project = var.hub_project_id + name = "hub-${count.index}.${module.hub-private-zone.domain}" + type = "A" + ttl = 300 + + managed_zone = module.hub-private-zone.name + + rrdatas = [google_compute_instance.hub[count.index].network_interface.0.network_ip] +} + resource "google_compute_instance" "spoke-1" { count = length(var.spoke_1_subnets) project = var.spoke_1_project_id @@ -48,6 +61,19 @@ resource "google_compute_instance" "spoke-1" { } } +resource "google_dns_record_set" "spoke-1" { + count = length(var.spoke_1_subnets) + + project = var.hub_project_id + name = "spoke-1-${count.index}.${module.hub-private-zone.domain}" + type = "A" + ttl = 300 + + managed_zone = module.hub-private-zone.name + + rrdatas = [google_compute_instance.spoke-1[count.index].network_interface.0.network_ip] +} + resource "google_compute_instance" "spoke-2" { count = length(var.spoke_2_subnets) project = var.spoke_2_project_id @@ -65,3 +91,16 @@ resource "google_compute_instance" "spoke-2" { access_config {} } } + +resource "google_dns_record_set" "spoke-2" { + count = length(var.spoke_2_subnets) + + project = var.hub_project_id + name = "spoke-2-${count.index}.${module.hub-private-zone.domain}" + type = "A" + ttl = 300 + + managed_zone = module.hub-private-zone.name + + rrdatas = [google_compute_instance.spoke-2[count.index].network_interface.0.network_ip] +} diff --git a/infrastructure/net-hub-and-spoke/variables.tf b/infrastructure/net-hub-and-spoke/variables.tf index 29ec86ff5..129fa2765 100644 --- a/infrastructure/net-hub-and-spoke/variables.tf +++ b/infrastructure/net-hub-and-spoke/variables.tf @@ -98,22 +98,22 @@ variable "spoke_2_subnets" { variable "private_dns_zone_name" { description = "Private DNS Zone Name." - default = "gcp-private" + default = "gcp-local" } variable "private_dns_zone_domain" { description = "Private DNS Zone Domain." - default = "gcp.private" + default = "gcp.local." } variable "forwarding_dns_zone_name" { description = "Forwarding DNS Zone Name." - default = "on-prem-private" + default = "on-prem-local" } variable "forwarding_dns_zone_domain" { description = "Forwarding DNS Zone Domain." - default = "on-prem.private" + default = "on-prem.local." } variable "forwarding_zone_server_addresses" { From 1c1ac9a511947e6abbbf9bba4065252a94339a03 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Mon, 14 Oct 2019 00:26:03 +0200 Subject: [PATCH 07/16] Refactoring and restructure, update test resources outputs --- infrastructure/net-hub-and-spoke/dns.tf | 68 -------- infrastructure/net-hub-and-spoke/locals.tf | 26 --- infrastructure/net-hub-and-spoke/main.tf | 155 +++++++++++++++++- infrastructure/net-hub-and-spoke/outputs.tf | 47 ++++-- infrastructure/net-hub-and-spoke/routers.tf | 95 ----------- .../net-hub-and-spoke/terraform.tfvars.sample | 3 +- .../{instances.tf => test-resources.tf} | 57 ++++++- infrastructure/net-hub-and-spoke/variables.tf | 8 - 8 files changed, 239 insertions(+), 220 deletions(-) delete mode 100644 infrastructure/net-hub-and-spoke/dns.tf delete mode 100644 infrastructure/net-hub-and-spoke/locals.tf delete mode 100644 infrastructure/net-hub-and-spoke/routers.tf rename infrastructure/net-hub-and-spoke/{instances.tf => test-resources.tf} (58%) diff --git a/infrastructure/net-hub-and-spoke/dns.tf b/infrastructure/net-hub-and-spoke/dns.tf deleted file mode 100644 index b0e9932b9..000000000 --- a/infrastructure/net-hub-and-spoke/dns.tf +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2019 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 -# -# https://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. - -############################################################## -# DNS Zones # -############################################################## - -module "hub-private-zone" { - source = "terraform-google-modules/cloud-dns/google" - version = "~> 2.0" - - project_id = var.hub_project_id - type = "private" - name = "${var.private_dns_zone_name}-hub-private" - domain = var.private_dns_zone_domain - - private_visibility_config_networks = [module.vpc-hub.network_self_link] -} - -module "spoke-1-peering-zone" { - source = "terraform-google-modules/cloud-dns/google" - version = "~> 2.0" - - project_id = var.spoke_1_project_id - type = "peering" - name = "${var.private_dns_zone_name}-spoke-1-peering" - domain = var.private_dns_zone_domain - - private_visibility_config_networks = [module.vpc-spoke-1.network_self_link] - target_network = module.vpc-hub.network_self_link -} - -module "spoke-2-peering-zone" { - source = "terraform-google-modules/cloud-dns/google" - version = "~> 2.0" - - project_id = var.spoke_2_project_id - type = "peering" - name = "${var.private_dns_zone_name}-spoke-2-peering" - domain = var.private_dns_zone_domain - - private_visibility_config_networks = [module.vpc-spoke-2.network_self_link] - target_network = module.vpc-hub.network_self_link -} - -module "hub-forwarding-zone" { - source = "terraform-google-modules/cloud-dns/google" - version = "~> 2.0" - - project_id = var.hub_project_id - type = "forwarding" - name = "${var.forwarding_dns_zone_name}-hub-forwarding" - domain = var.forwarding_dns_zone_domain - - private_visibility_config_networks = [module.vpc-hub.network_self_link] - target_name_server_addresses = var.forwarding_zone_server_addresses -} \ No newline at end of file diff --git a/infrastructure/net-hub-and-spoke/locals.tf b/infrastructure/net-hub-and-spoke/locals.tf deleted file mode 100644 index 80572b6e8..000000000 --- a/infrastructure/net-hub-and-spoke/locals.tf +++ /dev/null @@ -1,26 +0,0 @@ - -# Copyright 2019 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 -# -# https://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 { - hub_subnet_regions = [for subnet in var.hub_subnets : subnet["subnet_region"]] - spoke_1_subnet_regions = [for subnet in var.spoke_1_subnets : subnet["subnet_region"]] - spoke_2_subnet_regions = [for subnet in var.spoke_2_subnets : subnet["subnet_region"]] - hub_subnet_cidr_ranges = [for subnet in var.hub_subnets : subnet["subnet_ip"]] - spoke_1_subnet_cidr_ranges = [for subnet in var.spoke_1_subnets : subnet["subnet_ip"]] - spoke_2_subnet_cidr_ranges = [for subnet in var.spoke_2_subnets : subnet["subnet_ip"]] - all_subnet_cidrs = concat(local.hub_subnet_cidr_ranges, local.spoke_1_subnet_cidr_ranges, local.spoke_2_subnet_cidr_ranges) - hub_to_spoke_1_router = var.spoke_to_spoke_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-1-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-1-default.*.name, list("")), 0) - hub_to_spoke_2_router = var.spoke_to_spoke_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-2-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-2-default.*.name, list("")), 0) -} diff --git a/infrastructure/net-hub-and-spoke/main.tf b/infrastructure/net-hub-and-spoke/main.tf index 6b988e98e..dac93f3be 100644 --- a/infrastructure/net-hub-and-spoke/main.tf +++ b/infrastructure/net-hub-and-spoke/main.tf @@ -12,6 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. +locals { + hub_subnet_regions = [for subnet in var.hub_subnets : subnet["subnet_region"]] + spoke_1_subnet_regions = [for subnet in var.spoke_1_subnets : subnet["subnet_region"]] + spoke_2_subnet_regions = [for subnet in var.spoke_2_subnets : subnet["subnet_region"]] + hub_subnet_cidr_ranges = [for subnet in var.hub_subnets : subnet["subnet_ip"]] + spoke_1_subnet_cidr_ranges = [for subnet in var.spoke_1_subnets : subnet["subnet_ip"]] + spoke_2_subnet_cidr_ranges = [for subnet in var.spoke_2_subnets : subnet["subnet_ip"]] + all_subnet_cidrs = concat(local.hub_subnet_cidr_ranges, local.spoke_1_subnet_cidr_ranges, local.spoke_2_subnet_cidr_ranges) + hub_to_spoke_1_router = var.spoke_to_spoke_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-1-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-1-default.*.name, list("")), 0) + hub_to_spoke_2_router = var.spoke_to_spoke_route_advertisement ? element(concat(google_compute_router.hub-to-spoke-2-custom.*.name, list("")), 0) : element(concat(google_compute_router.hub-to-spoke-2-default.*.name, list("")), 0) +} + ############################################################## # VPCs # ############################################################## @@ -21,7 +33,7 @@ module "vpc-hub" { version = "~> 1.2" project_id = var.hub_project_id - network_name = "${var.prefix}-hub" + network_name = "hub-network" subnets = var.hub_subnets routing_mode = "GLOBAL" } @@ -31,7 +43,7 @@ module "vpc-spoke-1" { version = "~> 1.2" project_id = var.spoke_1_project_id - network_name = "${var.prefix}-spoke-1" + network_name = "spoke-1-network" subnets = var.spoke_1_subnets routing_mode = "GLOBAL" } @@ -41,7 +53,7 @@ module "vpc-spoke-2" { version = "~> 1.2" project_id = var.spoke_2_project_id - network_name = "${var.prefix}-spoke-2" + network_name = "spoke-2-network" subnets = var.spoke_2_subnets routing_mode = "GLOBAL" } @@ -80,6 +92,88 @@ module "firewall-spoke-2" { admin_ranges = local.all_subnet_cidrs } +############################################################## +# Cloud Routers # +############################################################## + +resource "google_compute_router" "hub-to-spoke-1-custom" { + count = var.spoke_to_spoke_route_advertisement ? 1 : 0 + name = "hub-to-spoke-1-custom" + region = element(local.hub_subnet_regions, 0) + network = module.vpc-hub.network_name + project = var.hub_project_id + bgp { + asn = var.hub_bgp_asn + advertise_mode = "CUSTOM" + advertised_groups = ["ALL_SUBNETS"] + + dynamic "advertised_ip_ranges" { + for_each = toset(local.spoke_2_subnet_cidr_ranges) + content { + range = advertised_ip_ranges.value + } + } + } +} + +resource "google_compute_router" "hub-to-spoke-2-custom" { + count = var.spoke_to_spoke_route_advertisement ? 1 : 0 + name = "hub-to-spoke-2-custom" + region = element(local.hub_subnet_regions, 1) + network = module.vpc-hub.network_name + project = var.hub_project_id + bgp { + asn = var.hub_bgp_asn + advertise_mode = "CUSTOM" + advertised_groups = ["ALL_SUBNETS"] + dynamic "advertised_ip_ranges" { + for_each = toset(local.spoke_1_subnet_cidr_ranges) + content { + range = advertised_ip_ranges.value + } + } + } +} + +resource "google_compute_router" "hub-to-spoke-1-default" { + count = var.spoke_to_spoke_route_advertisement ? 0 : 1 + name = "hub-to-spoke-1-default" + region = element(local.hub_subnet_regions, 0) + network = module.vpc-hub.network_name + project = var.hub_project_id + bgp { + asn = var.hub_bgp_asn + } +} +resource "google_compute_router" "hub-to-spoke-2-default" { + count = var.spoke_to_spoke_route_advertisement ? 0 : 1 + name = "hub-to-spoke-2-default" + region = element(local.hub_subnet_regions, 1) + network = module.vpc-hub.network_name + project = var.hub_project_id + bgp { + asn = var.hub_bgp_asn + } +} +resource "google_compute_router" "spoke-1" { + name = "spoke-1" + region = element(local.spoke_1_subnet_regions, 0) + network = module.vpc-spoke-1.network_name + project = var.spoke_1_project_id + bgp { + asn = var.spoke_1_bgp_asn + } +} +resource "google_compute_router" "spoke-2" { + name = "spoke-2" + region = element(local.spoke_2_subnet_regions, 0) + network = module.vpc-spoke-2.network_name + project = var.spoke_2_project_id + bgp { + asn = var.spoke_2_bgp_asn + } +} + ############################################################## # VPNs # ############################################################## @@ -145,3 +239,58 @@ module "vpn-spoke-2-to-hub" { peer_asn = [var.hub_bgp_asn] cr_name = google_compute_router.spoke-2.name } + +############################################################## +# DNS Zones # +############################################################## + +module "hub-private-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.hub_project_id + type = "private" + name = "${var.private_dns_zone_name}-hub-private" + domain = var.private_dns_zone_domain + + private_visibility_config_networks = [module.vpc-hub.network_self_link] +} + +module "hub-forwarding-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.hub_project_id + type = "forwarding" + name = "${var.forwarding_dns_zone_name}-hub-forwarding" + domain = var.forwarding_dns_zone_domain + + private_visibility_config_networks = [module.vpc-hub.network_self_link] + target_name_server_addresses = var.forwarding_zone_server_addresses +} + +module "spoke-1-peering-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.spoke_1_project_id + type = "peering" + name = "${var.private_dns_zone_name}-spoke-1-peering" + domain = var.private_dns_zone_domain + + private_visibility_config_networks = [module.vpc-spoke-1.network_self_link] + target_network = module.vpc-hub.network_self_link +} + +module "spoke-2-peering-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.spoke_2_project_id + type = "peering" + name = "${var.private_dns_zone_name}-spoke-2-peering" + domain = var.private_dns_zone_domain + + private_visibility_config_networks = [module.vpc-spoke-2.network_self_link] + target_network = module.vpc-hub.network_self_link +} diff --git a/infrastructure/net-hub-and-spoke/outputs.tf b/infrastructure/net-hub-and-spoke/outputs.tf index 0b19d8a6e..747c1c5cd 100644 --- a/infrastructure/net-hub-and-spoke/outputs.tf +++ b/infrastructure/net-hub-and-spoke/outputs.tf @@ -14,41 +14,58 @@ output "hub" { value = { - name = module.vpc-hub.network_name - subnets = zipmap( + network_name = module.vpc-hub.network_name + subnets_ips = zipmap( module.vpc-hub.subnets_names, module.vpc-hub.subnets_ips ) - instances = zipmap( - google_compute_instance.hub.*.name, - google_compute_instance.hub.*.zone + subnets_regions = zipmap( + module.vpc-hub.subnets_names, + module.vpc-hub.subnets_regions ) + privte_dns_zone = { + name = module.hub-private-zone.name + domain = module.hub-private-zone.domain + } + forwarding_dns_zone = { + name = module.hub-forwarding-zone.name + domain = module.hub-forwarding-zone.domain + } } } output "spoke-1" { value = { - name = module.vpc-spoke-1.network_name - subnets = zipmap( + network_name = module.vpc-spoke-1.network_name + subnets_ips = zipmap( module.vpc-spoke-1.subnets_names, module.vpc-spoke-1.subnets_ips ) - instances = zipmap( - google_compute_instance.spoke-1.*.name, - google_compute_instance.spoke-1.*.zone + subnets_regions = zipmap( + 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 + } } } + output "spoke-2" { value = { - name = module.vpc-spoke-2.network_name - subnets = zipmap( + network_name = module.vpc-spoke-2.network_name + subnets_ips = zipmap( module.vpc-spoke-2.subnets_names, module.vpc-spoke-2.subnets_ips ) - instances = zipmap( - google_compute_instance.spoke-2.*.name, - google_compute_instance.spoke-2.*.zone + subnets_regions = zipmap( + 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 + } } } diff --git a/infrastructure/net-hub-and-spoke/routers.tf b/infrastructure/net-hub-and-spoke/routers.tf deleted file mode 100644 index 2f2ab3b4c..000000000 --- a/infrastructure/net-hub-and-spoke/routers.tf +++ /dev/null @@ -1,95 +0,0 @@ -# Copyright 2019 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 -# -# https://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. - -############################################################## -# Cloud Routers # -############################################################## - -resource "google_compute_router" "hub-to-spoke-1-custom" { - count = var.spoke_to_spoke_route_advertisement ? 1 : 0 - name = "hub-to-spoke-1-custom" - region = element(local.hub_subnet_regions, 0) - network = module.vpc-hub.network_name - project = var.hub_project_id - bgp { - asn = var.hub_bgp_asn - advertise_mode = "CUSTOM" - advertised_groups = ["ALL_SUBNETS"] - - dynamic "advertised_ip_ranges" { - for_each = toset(local.spoke_2_subnet_cidr_ranges) - content { - range = advertised_ip_ranges.value - } - } - } -} - -resource "google_compute_router" "hub-to-spoke-2-custom" { - count = var.spoke_to_spoke_route_advertisement ? 1 : 0 - name = "hub-to-spoke-2-custom" - region = element(local.hub_subnet_regions, 1) - network = module.vpc-hub.network_name - project = var.hub_project_id - bgp { - asn = var.hub_bgp_asn - advertise_mode = "CUSTOM" - advertised_groups = ["ALL_SUBNETS"] - dynamic "advertised_ip_ranges" { - for_each = toset(local.spoke_1_subnet_cidr_ranges) - content { - range = advertised_ip_ranges.value - } - } - } -} - -resource "google_compute_router" "hub-to-spoke-1-default" { - count = var.spoke_to_spoke_route_advertisement ? 0 : 1 - name = "hub-to-spoke-1-default" - region = element(local.hub_subnet_regions, 0) - network = module.vpc-hub.network_name - project = var.hub_project_id - bgp { - asn = var.hub_bgp_asn - } -} -resource "google_compute_router" "hub-to-spoke-2-default" { - count = var.spoke_to_spoke_route_advertisement ? 0 : 1 - name = "hub-to-spoke-2-default" - region = element(local.hub_subnet_regions, 1) - network = module.vpc-hub.network_name - project = var.hub_project_id - bgp { - asn = var.hub_bgp_asn - } -} -resource "google_compute_router" "spoke-1" { - name = "spoke-1" - region = element(local.spoke_1_subnet_regions, 0) - network = module.vpc-spoke-1.network_name - project = var.spoke_1_project_id - bgp { - asn = var.spoke_1_bgp_asn - } -} -resource "google_compute_router" "spoke-2" { - name = "spoke-2" - region = element(local.spoke_2_subnet_regions, 0) - network = module.vpc-spoke-2.network_name - project = var.spoke_2_project_id - bgp { - asn = var.spoke_2_bgp_asn - } -} diff --git a/infrastructure/net-hub-and-spoke/terraform.tfvars.sample b/infrastructure/net-hub-and-spoke/terraform.tfvars.sample index 5d94a27a6..b11c2e5d0 100644 --- a/infrastructure/net-hub-and-spoke/terraform.tfvars.sample +++ b/infrastructure/net-hub-and-spoke/terraform.tfvars.sample @@ -1,4 +1,3 @@ hub_project_id = "automation-examples" spoke_1_project_id = "automation-examples" -spoke_2_project_id = "automation-examples" -prefix = "test" \ No newline at end of file +spoke_2_project_id = "automation-examples" \ No newline at end of file diff --git a/infrastructure/net-hub-and-spoke/instances.tf b/infrastructure/net-hub-and-spoke/test-resources.tf similarity index 58% rename from infrastructure/net-hub-and-spoke/instances.tf rename to infrastructure/net-hub-and-spoke/test-resources.tf index 127e5e12c..770cc3d38 100644 --- a/infrastructure/net-hub-and-spoke/instances.tf +++ b/infrastructure/net-hub-and-spoke/test-resources.tf @@ -12,10 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +############################################################################### +# Hub test VMs and DNS records # +############################################################################### + resource "google_compute_instance" "hub" { count = length(var.hub_subnets) project = var.hub_project_id - name = "${var.prefix}-hub-${element(var.hub_subnets, count.index)["subnet_name"]}" + name = "hub-${element(var.hub_subnets, count.index)["subnet_name"]}" machine_type = "f1-micro" zone = "${element(local.hub_subnet_regions, count.index)}-b" tags = ["ssh"] @@ -43,10 +47,14 @@ resource "google_dns_record_set" "hub" { rrdatas = [google_compute_instance.hub[count.index].network_interface.0.network_ip] } +############################################################################### +# Spoke 1 test VMs and DNS records # +############################################################################### + resource "google_compute_instance" "spoke-1" { count = length(var.spoke_1_subnets) project = var.spoke_1_project_id - name = "${var.prefix}-spoke-1-${element(var.spoke_1_subnets, count.index)["subnet_name"]}" + name = "spoke-1-${element(var.spoke_1_subnets, count.index)["subnet_name"]}" machine_type = "f1-micro" zone = "${element(local.spoke_1_subnet_regions, count.index)}-b" tags = ["ssh"] @@ -74,10 +82,14 @@ resource "google_dns_record_set" "spoke-1" { rrdatas = [google_compute_instance.spoke-1[count.index].network_interface.0.network_ip] } +############################################################################### +# Spoke 2 test VMs and DNS records # +############################################################################### + resource "google_compute_instance" "spoke-2" { count = length(var.spoke_2_subnets) project = var.spoke_2_project_id - name = "${var.prefix}-spoke-2-${element(var.spoke_2_subnets, count.index)["subnet_name"]}" + name = "spoke-2-${element(var.spoke_2_subnets, count.index)["subnet_name"]}" machine_type = "f1-micro" zone = "${element(local.spoke_2_subnet_regions, count.index)}-b" tags = ["ssh"] @@ -104,3 +116,42 @@ resource "google_dns_record_set" "spoke-2" { rrdatas = [google_compute_instance.spoke-2[count.index].network_interface.0.network_ip] } + +############################################################################### +# test outputs # +############################################################################### + +output "test-instances" { + value = { + hub = { + instance_zones = zipmap( + google_compute_instance.hub.*.name, + google_compute_instance.hub.*.zone + ) + instances_dns_names = zipmap( + google_compute_instance.hub.*.name, + google_dns_record_set.hub.*.name + ) + } + spoke-1 = { + instances_zones = zipmap( + google_compute_instance.spoke-1.*.name, + google_compute_instance.spoke-1.*.zone + ) + instances_dns_names = zipmap( + google_compute_instance.spoke-1.*.name, + google_dns_record_set.spoke-1.*.name + ) + } + spoke-2 = { + instances_zones = zipmap( + google_compute_instance.spoke-2.*.name, + google_compute_instance.spoke-2.*.zone + ) + instances_dns_names = zipmap( + google_compute_instance.spoke-2.*.name, + google_dns_record_set.spoke-2.*.name + ) + } + } +} diff --git a/infrastructure/net-hub-and-spoke/variables.tf b/infrastructure/net-hub-and-spoke/variables.tf index 129fa2765..520da6fdb 100644 --- a/infrastructure/net-hub-and-spoke/variables.tf +++ b/infrastructure/net-hub-and-spoke/variables.tf @@ -24,10 +24,6 @@ variable "spoke_2_project_id" { description = "Spoke 2 Project id." } -variable "prefix" { - description = "Prefix for VPC names." -} - variable "spoke_to_spoke_route_advertisement" { description = "Use custom route advertisement in hub routers to advertise all spoke subnets." default = true @@ -56,7 +52,6 @@ variable "hub_subnets" { subnet_region = "europe-west1" }, { - subnet_name = "subnet-b" subnet_ip = "10.10.20.0/24" subnet_region = "europe-west2" @@ -72,7 +67,6 @@ variable "spoke_1_subnets" { subnet_region = "asia-east1" }, { - subnet_name = "subnet-b" subnet_ip = "10.20.20.0/24" subnet_region = "asia-northeast1" @@ -88,7 +82,6 @@ variable "spoke_2_subnets" { subnet_region = "us-west1" }, { - subnet_name = "subnet-b" subnet_ip = "10.30.20.0/24" subnet_region = "us-west2" @@ -120,4 +113,3 @@ variable "forwarding_zone_server_addresses" { description = "Forwarding DNS Zone Server Addresses" default = ["8.8.8.8", "8.8.4.4"] } - \ No newline at end of file From fe65998bb9e56ef7ee3faf03caac8e3742054021 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Mon, 14 Oct 2019 00:29:47 +0200 Subject: [PATCH 08/16] Rename example folder to 'net-hub-and-spoke-vpns' --- .../{net-hub-and-spoke => net-hub-and-spoke-vpns}/main.tf | 0 .../{net-hub-and-spoke => net-hub-and-spoke-vpns}/outputs.tf | 0 .../terraform.tfvars.sample | 0 .../test-resources.tf | 0 .../{net-hub-and-spoke => net-hub-and-spoke-vpns}/variables.tf | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename infrastructure/{net-hub-and-spoke => net-hub-and-spoke-vpns}/main.tf (100%) rename infrastructure/{net-hub-and-spoke => net-hub-and-spoke-vpns}/outputs.tf (100%) rename infrastructure/{net-hub-and-spoke => net-hub-and-spoke-vpns}/terraform.tfvars.sample (100%) rename infrastructure/{net-hub-and-spoke => net-hub-and-spoke-vpns}/test-resources.tf (100%) rename infrastructure/{net-hub-and-spoke => net-hub-and-spoke-vpns}/variables.tf (100%) diff --git a/infrastructure/net-hub-and-spoke/main.tf b/infrastructure/net-hub-and-spoke-vpns/main.tf similarity index 100% rename from infrastructure/net-hub-and-spoke/main.tf rename to infrastructure/net-hub-and-spoke-vpns/main.tf diff --git a/infrastructure/net-hub-and-spoke/outputs.tf b/infrastructure/net-hub-and-spoke-vpns/outputs.tf similarity index 100% rename from infrastructure/net-hub-and-spoke/outputs.tf rename to infrastructure/net-hub-and-spoke-vpns/outputs.tf diff --git a/infrastructure/net-hub-and-spoke/terraform.tfvars.sample b/infrastructure/net-hub-and-spoke-vpns/terraform.tfvars.sample similarity index 100% rename from infrastructure/net-hub-and-spoke/terraform.tfvars.sample rename to infrastructure/net-hub-and-spoke-vpns/terraform.tfvars.sample diff --git a/infrastructure/net-hub-and-spoke/test-resources.tf b/infrastructure/net-hub-and-spoke-vpns/test-resources.tf similarity index 100% rename from infrastructure/net-hub-and-spoke/test-resources.tf rename to infrastructure/net-hub-and-spoke-vpns/test-resources.tf diff --git a/infrastructure/net-hub-and-spoke/variables.tf b/infrastructure/net-hub-and-spoke-vpns/variables.tf similarity index 100% rename from infrastructure/net-hub-and-spoke/variables.tf rename to infrastructure/net-hub-and-spoke-vpns/variables.tf From 0fd942ab3fe0d60568ed73b0cdd58d3e5f4cad92 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Mon, 14 Oct 2019 08:07:51 +0200 Subject: [PATCH 09/16] New line at the end of the file --- infrastructure/net-hub-and-spoke-vpns/terraform.tfvars.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/net-hub-and-spoke-vpns/terraform.tfvars.sample b/infrastructure/net-hub-and-spoke-vpns/terraform.tfvars.sample index b11c2e5d0..cfe767c3d 100644 --- a/infrastructure/net-hub-and-spoke-vpns/terraform.tfvars.sample +++ b/infrastructure/net-hub-and-spoke-vpns/terraform.tfvars.sample @@ -1,3 +1,3 @@ hub_project_id = "automation-examples" spoke_1_project_id = "automation-examples" -spoke_2_project_id = "automation-examples" \ No newline at end of file +spoke_2_project_id = "automation-examples" From 92e6a9808374fb4e044b28874e22f66888fc8a9c Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Mon, 14 Oct 2019 08:09:40 +0200 Subject: [PATCH 10/16] Add backend.tf sample --- .../net-hub-and-spoke-vpns/backend.tf.sample | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 infrastructure/net-hub-and-spoke-vpns/backend.tf.sample diff --git a/infrastructure/net-hub-and-spoke-vpns/backend.tf.sample b/infrastructure/net-hub-and-spoke-vpns/backend.tf.sample new file mode 100644 index 000000000..a6fa9443e --- /dev/null +++ b/infrastructure/net-hub-and-spoke-vpns/backend.tf.sample @@ -0,0 +1,20 @@ +# Copyright 2019 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 +# +# https://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. + + +terraform { + backend "gcs" { + bucket = "" + } +} From c279fac621439eb0ffd55c649f6bc299fd213f28 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 30 Oct 2019 09:57:33 +0100 Subject: [PATCH 11/16] Rename to hub-and-spoke-vpns --- .../backend.tf.sample | 0 .../{net-hub-and-spoke-vpns => hub-and-spoke-vpns}/main.tf | 0 .../{net-hub-and-spoke-vpns => hub-and-spoke-vpns}/outputs.tf | 0 .../terraform.tfvars.sample | 0 .../test-resources.tf | 0 .../{net-hub-and-spoke-vpns => hub-and-spoke-vpns}/variables.tf | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename infrastructure/{net-hub-and-spoke-vpns => hub-and-spoke-vpns}/backend.tf.sample (100%) rename infrastructure/{net-hub-and-spoke-vpns => hub-and-spoke-vpns}/main.tf (100%) rename infrastructure/{net-hub-and-spoke-vpns => hub-and-spoke-vpns}/outputs.tf (100%) rename infrastructure/{net-hub-and-spoke-vpns => hub-and-spoke-vpns}/terraform.tfvars.sample (100%) rename infrastructure/{net-hub-and-spoke-vpns => hub-and-spoke-vpns}/test-resources.tf (100%) rename infrastructure/{net-hub-and-spoke-vpns => hub-and-spoke-vpns}/variables.tf (100%) diff --git a/infrastructure/net-hub-and-spoke-vpns/backend.tf.sample b/infrastructure/hub-and-spoke-vpns/backend.tf.sample similarity index 100% rename from infrastructure/net-hub-and-spoke-vpns/backend.tf.sample rename to infrastructure/hub-and-spoke-vpns/backend.tf.sample diff --git a/infrastructure/net-hub-and-spoke-vpns/main.tf b/infrastructure/hub-and-spoke-vpns/main.tf similarity index 100% rename from infrastructure/net-hub-and-spoke-vpns/main.tf rename to infrastructure/hub-and-spoke-vpns/main.tf diff --git a/infrastructure/net-hub-and-spoke-vpns/outputs.tf b/infrastructure/hub-and-spoke-vpns/outputs.tf similarity index 100% rename from infrastructure/net-hub-and-spoke-vpns/outputs.tf rename to infrastructure/hub-and-spoke-vpns/outputs.tf diff --git a/infrastructure/net-hub-and-spoke-vpns/terraform.tfvars.sample b/infrastructure/hub-and-spoke-vpns/terraform.tfvars.sample similarity index 100% rename from infrastructure/net-hub-and-spoke-vpns/terraform.tfvars.sample rename to infrastructure/hub-and-spoke-vpns/terraform.tfvars.sample diff --git a/infrastructure/net-hub-and-spoke-vpns/test-resources.tf b/infrastructure/hub-and-spoke-vpns/test-resources.tf similarity index 100% rename from infrastructure/net-hub-and-spoke-vpns/test-resources.tf rename to infrastructure/hub-and-spoke-vpns/test-resources.tf diff --git a/infrastructure/net-hub-and-spoke-vpns/variables.tf b/infrastructure/hub-and-spoke-vpns/variables.tf similarity index 100% rename from infrastructure/net-hub-and-spoke-vpns/variables.tf rename to infrastructure/hub-and-spoke-vpns/variables.tf From 5bd25ea48fd1dfd013eeb3df5d3d4dc18822c0a1 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 30 Oct 2019 11:55:14 +0100 Subject: [PATCH 12/16] Use the same two regions for hub, spoke1, spoke2 --- infrastructure/hub-and-spoke-vpns/variables.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/hub-and-spoke-vpns/variables.tf b/infrastructure/hub-and-spoke-vpns/variables.tf index 520da6fdb..5fa27a851 100644 --- a/infrastructure/hub-and-spoke-vpns/variables.tf +++ b/infrastructure/hub-and-spoke-vpns/variables.tf @@ -64,12 +64,12 @@ variable "spoke_1_subnets" { default = [{ subnet_name = "subnet-a" subnet_ip = "10.20.10.0/24" - subnet_region = "asia-east1" + subnet_region = "europe-west1" }, { subnet_name = "subnet-b" subnet_ip = "10.20.20.0/24" - subnet_region = "asia-northeast1" + subnet_region = "europe-west2" }, ] } @@ -79,12 +79,12 @@ variable "spoke_2_subnets" { default = [{ subnet_name = "subnet-a" subnet_ip = "10.30.10.0/24" - subnet_region = "us-west1" + subnet_region = "europe-west1" }, { subnet_name = "subnet-b" subnet_ip = "10.30.20.0/24" - subnet_region = "us-west2" + subnet_region = "europe-west2" }, ] } From 5f9438d0ae76ed848f49b93c0ce75e53835849ee Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 30 Oct 2019 12:04:58 +0100 Subject: [PATCH 13/16] Add spoke peering zones to hub forwarding zone --- infrastructure/hub-and-spoke-vpns/main.tf | 38 +++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/infrastructure/hub-and-spoke-vpns/main.tf b/infrastructure/hub-and-spoke-vpns/main.tf index dac93f3be..b56e0177c 100644 --- a/infrastructure/hub-and-spoke-vpns/main.tf +++ b/infrastructure/hub-and-spoke-vpns/main.tf @@ -166,7 +166,7 @@ resource "google_compute_router" "spoke-1" { } resource "google_compute_router" "spoke-2" { name = "spoke-2" - region = element(local.spoke_2_subnet_regions, 0) + region = element(local.spoke_2_subnet_regions, 1) network = module.vpc-spoke-2.network_name project = var.spoke_2_project_id bgp { @@ -230,7 +230,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, 0) + region = element(local.spoke_2_subnet_regions, 1) 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] @@ -269,28 +269,54 @@ module "hub-forwarding-zone" { target_name_server_addresses = var.forwarding_zone_server_addresses } -module "spoke-1-peering-zone" { +module "spoke-1-peering-zone-to-hub-private-zone" { source = "terraform-google-modules/cloud-dns/google" version = "~> 2.0" project_id = var.spoke_1_project_id type = "peering" - name = "${var.private_dns_zone_name}-spoke-1-peering" + name = "${var.private_dns_zone_name}-spoke-1-peering-to-hub-private" domain = var.private_dns_zone_domain private_visibility_config_networks = [module.vpc-spoke-1.network_self_link] target_network = module.vpc-hub.network_self_link } -module "spoke-2-peering-zone" { +module "spoke-2-peering-zone-to-hub-private-zone" { source = "terraform-google-modules/cloud-dns/google" version = "~> 2.0" project_id = var.spoke_2_project_id type = "peering" - name = "${var.private_dns_zone_name}-spoke-2-peering" + name = "${var.private_dns_zone_name}-spoke-2-peering-to-hub-private" domain = var.private_dns_zone_domain private_visibility_config_networks = [module.vpc-spoke-2.network_self_link] target_network = module.vpc-hub.network_self_link } + +module "spoke-1-peering-zone-to-hub-forwarding-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.spoke_1_project_id + type = "peering" + name = "${var.private_dns_zone_name}-spoke-1-peering-to-hub-forwarding" + domain = var.forwarding_dns_zone_domain + + private_visibility_config_networks = [module.vpc-spoke-1.network_self_link] + target_network = module.vpc-hub.network_self_link +} + +module "spoke-2-peering-zone-to-hub-forwarding-zone" { + source = "terraform-google-modules/cloud-dns/google" + version = "~> 2.0" + + project_id = var.spoke_2_project_id + type = "peering" + name = "${var.private_dns_zone_name}-spoke-2-peering-to-hub-forwarding" + domain = var.forwarding_dns_zone_domain + + private_visibility_config_networks = [module.vpc-spoke-2.network_self_link] + target_network = module.vpc-hub.network_self_link +} From 00cbabf8fc748e9a13d45665be81a0e7bdc43470 Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 30 Oct 2019 13:51:42 +0100 Subject: [PATCH 14/16] Add README.md, add descriptions to the outputs --- infrastructure/hub-and-spoke-vpns/README.md | 69 ++++++++++++++++++ infrastructure/hub-and-spoke-vpns/diagram.png | Bin 0 -> 139690 bytes infrastructure/hub-and-spoke-vpns/main.tf | 4 + infrastructure/hub-and-spoke-vpns/outputs.tf | 23 ++++-- .../hub-and-spoke-vpns/test-resources.tf | 1 + .../hub-and-spoke-vpns/variables.tf | 8 +- 6 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 infrastructure/hub-and-spoke-vpns/README.md create mode 100644 infrastructure/hub-and-spoke-vpns/diagram.png 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 0000000000000000000000000000000000000000..17d2851185cc8fd77e403b777f9998ec0e6c71a0 GIT binary patch literal 139690 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

sF@PG)>oW@s87-0X$Ldqcl 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 I8hc#&Cw0eiNB{r; literal 0 HcmV?d00001 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" }, From b0d3351c37cb063d683af6ab402aa91207ce31ab Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 30 Oct 2019 14:03:23 +0100 Subject: [PATCH 15/16] Add reference to the HA VPN configurations --- infrastructure/hub-and-spoke-vpns/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/infrastructure/hub-and-spoke-vpns/README.md b/infrastructure/hub-and-spoke-vpns/README.md index d135bc5f3..41c4f951e 100644 --- a/infrastructure/hub-and-spoke-vpns/README.md +++ b/infrastructure/hub-and-spoke-vpns/README.md @@ -1,7 +1,9 @@ # 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. +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 via IPsec VPNs. + 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. @@ -12,6 +14,8 @@ The benefits of this topology include: 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). +> **NOTE**: This example is not desined to provide HA, please refer to the [documentation](https://cloud.google.com/vpn/docs/concepts/advanced#ha-options) for information on Cloud VPNs and HA. + ![High-level diagram](diagram.png "High-level diagram") ## Managed resources and services From cb3260b3979cdc72a3920b385c0f997256bcf34a Mon Sep 17 00:00:00 2001 From: Aleksandr Averbukh Date: Wed, 30 Oct 2019 16:38:26 +0100 Subject: [PATCH 16/16] Add Cloud DNS Policy for inbound forwarding --- infrastructure/hub-and-spoke-vpns/README.md | 10 +++++++++- infrastructure/hub-and-spoke-vpns/main.tf | 18 ++++++++++++++++++ infrastructure/hub-and-spoke-vpns/provider.tf | 4 ++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 infrastructure/hub-and-spoke-vpns/provider.tf diff --git a/infrastructure/hub-and-spoke-vpns/README.md b/infrastructure/hub-and-spoke-vpns/README.md index 41c4f951e..a0969c246 100644 --- a/infrastructure/hub-and-spoke-vpns/README.md +++ b/infrastructure/hub-and-spoke-vpns/README.md @@ -2,6 +2,9 @@ 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 via IPsec VPNs. +> **NOTE**: This example is not desined to provide HA, please refer to the [documentation](https://cloud.google.com/vpn/docs/concepts/advanced#ha-options) for information on Cloud VPNs and HA. + + The benefits of this topology include: - Network/Security Admin manages Central Services Project (Hub). @@ -14,7 +17,6 @@ The benefits of this topology include: 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). -> **NOTE**: This example is not desined to provide HA, please refer to the [documentation](https://cloud.google.com/vpn/docs/concepts/advanced#ha-options) for information on Cloud VPNs and HA. ![High-level diagram](diagram.png "High-level diagram") @@ -27,6 +29,7 @@ This sample creates several distinct groups of resources: - 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) +- one Cloud DNS Policy for inbound forwarding - 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) @@ -40,6 +43,11 @@ A set of test resources are included for convenience, as they facilitate experim 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. +## Known issues + - It is not possible to get inbound DNS forwarding IPs in the terraform output. + - Please refer to the [bug](https://github.com/terraform-providers/terraform-provider-google/issues/3753) for more details. + - Please refer to the [documentation](https://cloud.google.com/dns/zones/#creating_a_dns_policy_that_enables_inbound_dns_forwarding) on how to get the IPs with `gcloud`. + ## Inputs diff --git a/infrastructure/hub-and-spoke-vpns/main.tf b/infrastructure/hub-and-spoke-vpns/main.tf index 21806722a..9761c8375 100644 --- a/infrastructure/hub-and-spoke-vpns/main.tf +++ b/infrastructure/hub-and-spoke-vpns/main.tf @@ -324,3 +324,21 @@ module "spoke-2-peering-zone-to-hub-forwarding-zone" { private_visibility_config_networks = [module.vpc-spoke-2.network_self_link] target_network = module.vpc-hub.network_self_link } + +############################################################## +# Inbount DNS Forwarding # +############################################################## + +# TODO Provide resolver addresses in the output once https://github.com/terraform-providers/terraform-provider-google/issues/3753 resolved. +# For now please refer to the documentation on how to get the compute addresses for the DNS Resolver https://cloud.google.com/dns/zones/#creating_a_dns_policy_that_enables_inbound_dns_forwarding +resource "google_dns_policy" "google_dns_policy" { + provider = "google-beta" + + project = var.hub_project_id + name = "inbound-dns-forwarding-policy" + enable_inbound_forwarding = true + + networks { + network_url = module.vpc-hub.network_self_link + } +} diff --git a/infrastructure/hub-and-spoke-vpns/provider.tf b/infrastructure/hub-and-spoke-vpns/provider.tf new file mode 100644 index 000000000..d2148c614 --- /dev/null +++ b/infrastructure/hub-and-spoke-vpns/provider.tf @@ -0,0 +1,4 @@ +provider "google" { +} +provider "google-beta" { +}