diff --git a/CHANGELOG.md b/CHANGELOG.md index 978779ecd..61fdf3ff4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- [[#962](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/962)] Add filtering-proxy-psc blueprint ([kunzese](https://github.com/kunzese)) - [[#913](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/913)] Adding support for PSA ranges, starting with Redis instances. ([aurelienlegrand](https://github.com/aurelienlegrand)) - [[#939](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/939)] Temporarily duplicate cloud armor example ([ludoo](https://github.com/ludoo)) @@ -64,6 +65,7 @@ All notable changes to this project will be documented in this file. ### FAST +- [[#966](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/966)] FAST: improve GitHub workflow, stage 01 output fixes ([ludoo](https://github.com/ludoo)) - [[#963](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/963)] **incompatible change:** Refactor vps-sc module for Terraform 1.3 ([ludoo](https://github.com/ludoo)) - [[#956](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/956)] FAST: bootstrap and extra stage CI/CD improvements and fixes ([ludoo](https://github.com/ludoo)) - [[#949](https://github.com/GoogleCloudPlatform/cloud-foundation-fabric/pull/949)] **incompatible change:** Refactor VPC firewall module for Terraform 1.3 ([ludoo](https://github.com/ludoo)) diff --git a/blueprints/README.md b/blueprints/README.md index bfb4c4836..c0b7687b8 100644 --- a/blueprints/README.md +++ b/blueprints/README.md @@ -8,7 +8,7 @@ Currently available blueprints: - **data solutions** - [GCE and GCS CMEK via centralized Cloud KMS](./data-solutions/cmek-via-centralized-kms), [Cloud Composer version 2 private instance, supporting Shared VPC and external CMEK key](./data-solutions/composer-2), [Cloud SQL instance with multi-region read replicas](./data-solutions/cloudsql-multiregion), [Data Platform](./data-solutions/data-platform-foundations), [Spinning up a foundation data pipeline on Google Cloud using Cloud Storage, Dataflow and BigQuery](./data-solutions/gcs-to-bq-with-least-privileges), [#SQL Server Always On Groups blueprint](./data-solutions/sqlserver-alwayson), [Data Playground](./data-solutions/data-playground) - **factories** - [The why and the how of Resource Factories](./factories), [Google Cloud Identity Group Factory](./factories/cloud-identity-group-factory), [Google Cloud BQ Factory](./factories/bigquery-factory), [Google Cloud VPC Firewall Factory](./factories/net-vpc-firewall-yaml), [Minimal Project Factory](./factories/project-factory) - **GKE** - [Binary Authorization Pipeline Blueprint](./gke/binauthz), [Storage API](./gke/binauthz/image), [Multi-cluster mesh on GKE (fleet API)](./gke/multi-cluster-mesh-gke-fleet-api), [GKE Multitenant Blueprint](./gke/multitenant-fleet), [Shared VPC with GKE support](./networking/shared-vpc-gke/) -- **networking** - [Decentralized firewall management](./networking/decentralized-firewall), [Decentralized firewall validator](./networking/decentralized-firewall/validator), [Network filtering with Squid](./networking/filtering-proxy), [HTTP Load Balancer with Cloud Armor](./networking/glb-and-armor), [Hub and Spoke via VPN](./networking/hub-and-spoke-vpn), [Hub and Spoke via VPC Peering](./networking/hub-and-spoke-peering), [Internal Load Balancer as Next Hop](./networking/ilb-next-hop), [Nginx-based reverse proxy cluster](./networking/nginx-reverse-proxy-cluster), [On-prem DNS and Google Private Access](./networking/onprem-google-access-dns), [Calling a private Cloud Function from On-premises](./networking/private-cloud-function-from-onprem), [Hybrid connectivity to on-premise services through PSC](./networking/psc-hybrid), [PSC Producer](./networking/psc-hybrid/psc-producer), [PSC Consumer](./networking/psc-hybrid/psc-consumer), [Shared VPC with optional GKE cluster](./networking/shared-vpc-gke) +- **networking** - [Decentralized firewall management](./networking/decentralized-firewall), [Decentralized firewall validator](./networking/decentralized-firewall/validator), [Network filtering with Squid](./networking/filtering-proxy), [Network filtering with Squid with isolated VPCs using Private Service Connect](./networking/filtering-proxy-psc), [HTTP Load Balancer with Cloud Armor](./networking/glb-and-armor), [Hub and Spoke via VPN](./networking/hub-and-spoke-vpn), [Hub and Spoke via VPC Peering](./networking/hub-and-spoke-peering), [Internal Load Balancer as Next Hop](./networking/ilb-next-hop), [Nginx-based reverse proxy cluster](./networking/nginx-reverse-proxy-cluster), [On-prem DNS and Google Private Access](./networking/onprem-google-access-dns), [Calling a private Cloud Function from On-premises](./networking/private-cloud-function-from-onprem), [Hybrid connectivity to on-premise services through PSC](./networking/psc-hybrid), [PSC Producer](./networking/psc-hybrid/psc-producer), [PSC Consumer](./networking/psc-hybrid/psc-consumer), [Shared VPC with optional GKE cluster](./networking/shared-vpc-gke) - **serverless** - [Creating multi-region deployments for API Gateway](./serverless/api-gateway) - **third party solutions** - [OpenShift on GCP user-provisioned infrastructure](./third-party-solutions/openshift), [Wordpress deployment on Cloud Run](./third-party-solutions/wordpress/cloudrun) diff --git a/blueprints/networking/filtering-proxy-psc/README.md b/blueprints/networking/filtering-proxy-psc/README.md new file mode 100644 index 000000000..6459f3bf1 --- /dev/null +++ b/blueprints/networking/filtering-proxy-psc/README.md @@ -0,0 +1,28 @@ +# Network filtering with Squid with isolated VPCs using Private Service Connect + +This blueprint shows how to deploy a filtering HTTP proxy to restrict Internet access. Here we show one way to do this using isolated VPCs and Private Service Connect: + +- The `app` subnet hosts the consumer VMs that will have their Internet access tightly controlled by a non-caching filtering forward proxy. +- The `proxy` subnet hosts a Cloud NAT instance and a [Squid](http://www.squid-cache.org/) server. +- The `psc` subnet is reserved for the Private Service Connect. + +The reason for using Privat Service Connect in this setup is to have a common proxy setup between all environments without having to share a VPC between projects. This allows us to enforce the `compute.vmExternalIpAccess` [organization policy](https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints), which prevents the service projects from having external IPs, thus forcing all outbound Internet connections through the proxy. + +To allow Internet connectivity to the proxy subnet, a Cloud NAT instance is configured to allow usage from [that subnet only](https://cloud.google.com/nat/docs/using-nat#specify_subnet_ranges_for_nat). All other subnets are not allowed to use the Cloud NAT instance. + +To simplify the usage of the proxy, a Cloud DNS private zone is created in each consumer VPC and the IP address of the proxy is exposed with the FQDN `proxy.internal`. In addition, system-wide `http_proxy` and `https_proxy` environment variables and an APT configuration are rolled out via a [startup script](startup.sh). + + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [prefix](variables.tf#L44) | Prefix used for resources that need unique names. | string | ✓ | | +| [project_id](variables.tf#L66) | Project id used for all resources. | string | ✓ | | +| [allowed_domains](variables.tf#L17) | List of domains allowed by the squid proxy. | list(string) | | […] | +| [cidrs](variables.tf#L28) | CIDR ranges for subnets. | map(string) | | {…} | +| [nat_logging](variables.tf#L38) | Enables Cloud NAT logging if not null, value is one of 'ERRORS_ONLY', 'TRANSLATIONS_ONLY', 'ALL'. | string | | "ERRORS_ONLY" | +| [project_create](variables.tf#L49) | Set to non null if project needs to be created. | object({…}) | | null | +| [region](variables.tf#L71) | Default region for resources. | string | | "europe-west1" | + + diff --git a/blueprints/networking/filtering-proxy-psc/consumer.tf b/blueprints/networking/filtering-proxy-psc/consumer.tf new file mode 100644 index 000000000..5bcf03356 --- /dev/null +++ b/blueprints/networking/filtering-proxy-psc/consumer.tf @@ -0,0 +1,105 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +############################################################################### +# Consumer project and VPC # +############################################################################### + +module "vpc-consumer" { + source = "../../../modules/net-vpc" + project_id = module.project.project_id + name = "${var.prefix}-app" + subnets = [ + { + name = "${var.prefix}-app" + ip_cidr_range = var.cidrs.app + region = var.region + } + ] +} + +############################################################################### +# Test VM # +############################################################################### + +module "test-vm-consumer" { + source = "../../../modules/compute-vm" + project_id = module.project.project_id + zone = "${var.region}-b" + name = "${var.prefix}-test-vm" + instance_type = "e2-micro" + tags = ["ssh"] + network_interfaces = [{ + network = module.vpc-consumer.self_link + subnetwork = module.vpc-consumer.subnet_self_links["${var.region}/${var.prefix}-app"] + nat = false + addresses = null + }] + boot_disk = { + image = "debian-cloud/debian-10" + type = "pd-standard" + size = 10 + } + service_account_create = true + metadata = { + startup-script = templatefile("${path.module}/startup.sh", { proxy_url = "http://proxy.internal:3128" }) + } +} + +############################################################################### +# PSC Consuner # +############################################################################### + +resource "google_compute_address" "psc_endpoint_address" { + name = "${var.prefix}-psc-proxy-address" + project = module.project.project_id + address_type = "INTERNAL" + subnetwork = module.vpc-consumer.subnet_self_links["${var.region}/${var.prefix}-app"] + region = var.region +} + +resource "google_compute_forwarding_rule" "psc_ilb_consumer" { + name = "${var.prefix}-psc-proxy-fw-rule" + project = module.project.project_id + region = var.region + target = google_compute_service_attachment.service_attachment.id + load_balancing_scheme = "" + network = module.vpc-consumer.self_link + ip_address = google_compute_address.psc_endpoint_address.id +} + +############################################################################### +# DNS and Firewall # +############################################################################### + +module "private-dns" { + source = "../../../modules/dns" + project_id = module.project.project_id + type = "private" + name = "${var.prefix}-internal" + domain = "internal." + client_networks = [module.vpc-consumer.self_link] + recordsets = { + "A squid" = { ttl = 60, records = [google_compute_address.psc_endpoint_address.address] } + "CNAME proxy" = { ttl = 3600, records = ["squid.internal."] } + } +} + +module "firewall-consumer" { + source = "../../../modules/net-vpc-firewall" + project_id = module.project.project_id + network = module.vpc-consumer.name +} diff --git a/blueprints/networking/filtering-proxy-psc/main.tf b/blueprints/networking/filtering-proxy-psc/main.tf new file mode 100644 index 000000000..81499c7b5 --- /dev/null +++ b/blueprints/networking/filtering-proxy-psc/main.tf @@ -0,0 +1,210 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +############################################################################### +# Host project and VPC resources # +############################################################################### + +module "project" { + source = "../../../modules/project" + project_create = var.project_create != null + billing_account = try(var.project_create.billing_account, null) + parent = try(var.project_create.parent, null) + name = var.project_id + services = [ + "dns.googleapis.com", + "compute.googleapis.com", + "logging.googleapis.com" + ] +} + +module "vpc" { + source = "../../../modules/net-vpc" + project_id = module.project.project_id + name = "${var.prefix}-vpc" + subnets = [ + { + name = "proxy" + ip_cidr_range = var.cidrs.proxy + region = var.region + } + ] + subnets_psc = [ + { + name = "psc" + ip_cidr_range = var.cidrs.psc + region = var.region + } + ] +} + +module "firewall" { + source = "../../../modules/net-vpc-firewall" + project_id = module.project.project_id + network = module.vpc.name + ingress_rules = { + allow-ingress-squid = { + description = "Allow squid ingress traffic" + source_ranges = [ + var.cidrs.psc, "35.191.0.0/16", "130.211.0.0/22" + ] + targets = [module.service-account-squid.email] + use_service_accounts = true + rules = [{ + protocol = "tcp" + ports = [3128] + }] + } + } +} + +module "nat" { + source = "../../../modules/net-cloudnat" + project_id = module.project.project_id + region = var.region + name = "default" + router_network = module.vpc.name + config_source_subnets = "LIST_OF_SUBNETWORKS" + # 64512/11 = 5864 . 11 is the number of usable IPs in the proxy subnet + config_min_ports_per_vm = 5864 + subnetworks = [ + { + self_link = module.vpc.subnet_self_links["${var.region}/proxy"] + config_source_ranges = ["ALL_IP_RANGES"] + secondary_ranges = null + } + ] + logging_filter = var.nat_logging +} + +############################################################################### +# PSC resources # +############################################################################### + +resource "google_compute_service_attachment" "service_attachment" { + name = "psc" + project = module.project.project_id + region = var.region + enable_proxy_protocol = false + connection_preference = "ACCEPT_MANUAL" + nat_subnets = [module.vpc.subnets_psc["${var.region}/psc"].self_link] + target_service = module.squid-ilb.forwarding_rule_self_link + consumer_accept_lists { + project_id_or_num = module.project.project_id + connection_limit = 10 + } +} + +############################################################################### +# Squid resources # +############################################################################### + +module "service-account-squid" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = "svc-squid" + iam_project_roles = { + (module.project.project_id) = [ + "roles/logging.logWriter", + "roles/monitoring.metricWriter", + ] + } +} + +module "cos-squid" { + source = "../../../modules/cloud-config-container/squid" + allow = var.allowed_domains + clients = [var.cidrs.psc] +} + +module "squid-vm" { + source = "../../../modules/compute-vm" + project_id = module.project.project_id + zone = "${var.region}-b" + name = "squid-vm" + instance_type = "e2-medium" + create_template = true + network_interfaces = [{ + network = module.vpc.self_link + subnetwork = module.vpc.subnet_self_links["${var.region}/proxy"] + }] + boot_disk = { + image = "cos-cloud/cos-stable" + } + service_account = module.service-account-squid.email + service_account_scopes = ["https://www.googleapis.com/auth/cloud-platform"] + metadata = { + user-data = module.cos-squid.cloud_config + } +} + +module "squid-mig" { + source = "../../../modules/compute-mig" + project_id = module.project.project_id + location = "${var.region}-b" + name = "squid-mig" + instance_template = module.squid-vm.template.self_link + target_size = 1 + auto_healing_policies = { + initial_delay_sec = 60 + } + autoscaler_config = { + max_replicas = 10 + min_replicas = 1 + cooldown_period = 30 + scaling_signals = { + cpu_utilization = { + target = 0.65 + } + } + } + health_check_config = { + enable_logging = true + tcp = { + port = 3128 + } + } + update_policy = { + minimal_action = "REPLACE" + type = "PROACTIVE" + max_surge = { + fixed = 3 + } + min_ready_sec = 60 + } +} + +module "squid-ilb" { + source = "../../../modules/net-ilb" + project_id = module.project.project_id + region = var.region + name = "squid-ilb" + ports = [3128] + service_label = "squid-ilb" + vpc_config = { + network = module.vpc.self_link + subnetwork = module.vpc.subnet_self_links["${var.region}/proxy"] + } + backends = [{ + group = module.squid-mig.group_manager.instance_group + }] + health_check_config = { + enable_logging = true + tcp = { + port = 3128 + } + } +} diff --git a/blueprints/networking/filtering-proxy-psc/startup.sh b/blueprints/networking/filtering-proxy-psc/startup.sh new file mode 100644 index 000000000..bdc2b9cbc --- /dev/null +++ b/blueprints/networking/filtering-proxy-psc/startup.sh @@ -0,0 +1,26 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cat < /etc/apt/apt.conf.d/proxy.conf +Acquire { + HTTP::proxy "${proxy_url}"; + HTTPS::proxy "${proxy_url}"; +} +EOF + +cat < /etc/profile.d/proxy.sh +export http_proxy="${proxy_url}" +export https_proxy="${proxy_url}" +export no_proxy="127.0.0.1,localhost" +EOF diff --git a/blueprints/networking/filtering-proxy-psc/variables.tf b/blueprints/networking/filtering-proxy-psc/variables.tf new file mode 100644 index 000000000..620107e4b --- /dev/null +++ b/blueprints/networking/filtering-proxy-psc/variables.tf @@ -0,0 +1,75 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "allowed_domains" { + description = "List of domains allowed by the squid proxy." + type = list(string) + default = [ + ".google.com", + ".github.com", + ".fastlydns.net", + ".debian.org" + ] +} + +variable "cidrs" { + description = "CIDR ranges for subnets." + type = map(string) + default = { + app = "10.0.0.0/24" + proxy = "10.0.2.0/28" + psc = "10.0.3.0/28" + } +} + +variable "nat_logging" { + description = "Enables Cloud NAT logging if not null, value is one of 'ERRORS_ONLY', 'TRANSLATIONS_ONLY', 'ALL'." + type = string + default = "ERRORS_ONLY" +} + +variable "prefix" { + description = "Prefix used for resources that need unique names." + type = string +} + +variable "project_create" { + description = "Set to non null if project needs to be created." + type = object({ + billing_account = string + parent = string + }) + default = null + validation { + condition = ( + var.project_create == null + ? true + : can(regex("(organizations|folders)/[0-9]+", var.project_create.parent)) + ) + error_message = "Project parent must be of the form folders/folder_id or organizations/organization_id." + } +} + +variable "project_id" { + description = "Project id used for all resources." + type = string +} + +variable "region" { + description = "Default region for resources." + type = string + default = "europe-west1" +} diff --git a/blueprints/networking/filtering-proxy-psc/versions.tf b/blueprints/networking/filtering-proxy-psc/versions.tf new file mode 100644 index 000000000..286536a65 --- /dev/null +++ b/blueprints/networking/filtering-proxy-psc/versions.tf @@ -0,0 +1,29 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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 { + required_version = ">= 1.3.1" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 4.40.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 4.40.0" # tftest + } + } +} + + diff --git a/tests/blueprints/networking/filtering_proxy_psc/__init__.py b/tests/blueprints/networking/filtering_proxy_psc/__init__.py new file mode 100644 index 000000000..6d6d1266c --- /dev/null +++ b/tests/blueprints/networking/filtering_proxy_psc/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/blueprints/networking/filtering_proxy_psc/fixture/main.tf b/tests/blueprints/networking/filtering_proxy_psc/fixture/main.tf new file mode 100644 index 000000000..eb01058d1 --- /dev/null +++ b/tests/blueprints/networking/filtering_proxy_psc/fixture/main.tf @@ -0,0 +1,25 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module "test" { + source = "../../../../../blueprints/networking/filtering-proxy-psc" + prefix = "fabric" + project_create = { + billing_account = "123456-ABCDEF-123456" + parent = "folders/1234567890" + } + project_id = "test-project" +} diff --git a/tests/blueprints/networking/filtering_proxy_psc/test_plan.py b/tests/blueprints/networking/filtering_proxy_psc/test_plan.py new file mode 100644 index 000000000..9a2c3c2fa --- /dev/null +++ b/tests/blueprints/networking/filtering_proxy_psc/test_plan.py @@ -0,0 +1,19 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def test_resources(e2e_plan_runner): + "Test that plan works and the numbers of resources is as expected." + modules, resources = e2e_plan_runner() + assert len(modules) == 12 + assert len(resources) == 33