Configuring an isolated network in Google Cloud
Tags: googlecloud, terraform, networking
Published: 2021-06-23 08:54:36

In the [first part][part-1] we introduced the security patterns that could be implemented to secure the connectivity between Google Kubernetes Engine and Cloud SQL. In this part we will implement the network isolation by deploying the following GCP resources:

* VPC with 2 subnets
  * 1 web subnet for GKE.
  * 1 data subnet.
* Cloud NAT attached to the web subnet.
* Firewall rules to restrict access to subnets to only authorized networks.

> It is recommended to group similar applications into fewer, more manageable and larger subnets.

![VPC isolated network](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gdtn3ync0z8mpvpaxbkj.png)

> If you have multiples GKE clusters per environment, Google Cloud recommends to use [Shared VPC][gcp-1] to reduce management and topology complexity.

# VPC

Let's start with the `Virtual Private Cloud`. 

Create a terraform file `infra/plan/vpc.tf`:

* A simple VPC resource
* The web subnet. It will host our Google Kubernetes Engine
* The data subnet. It could host your Cloud Dataflow jobs, Cloud Composer environments, etc.

```terraform
resource "google_compute_network" "custom" {
  name                    = "custom"
  auto_create_subnetworks = "false" 
  routing_mode            = "GLOBAL"
}

resource "google_compute_subnetwork" "web" {
  name          = "web"
  ip_cidr_range = "10.10.10.0/24"
  network       = google_compute_network.custom.id
  region        = var.region

  secondary_ip_range  = [
    {
        range_name    = "services"
        ip_cidr_range = "10.10.11.0/24"
    },
    {
        range_name    = "pods"
        ip_cidr_range = "10.1.0.0/20"
    }
  ]

  private_ip_google_access = true
}

resource "google_compute_subnetwork" "data" {
  name          = "data"
  ip_cidr_range = "10.20.10.0/24"
  network       = google_compute_network.custom.id
  region        = var.region

  private_ip_google_access = true
}
```

# Cloud NAT

In order to allow our web subnet used by GKE to access the internet, we need to create a Cloud NAT. We associate Cloud NAT with the subnet using Cloud Router.

Create a terraform file `infra/plan/nat.tf`

```terraform
resource "google_compute_address" "web" {
  name    = "web"
  region  = var.region
}

resource "google_compute_router" "web" {
  name    = "web"
  network = google_compute_network.custom.id
}

resource "google_compute_router_nat" "web" {
  name                               = "web"
  router                             = google_compute_router.web.name
  nat_ip_allocate_option             = "MANUAL_ONLY"
  nat_ips                            = [ google_compute_address.web.self_link ]
  source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" 
  subnetwork {
    name                    = google_compute_subnetwork.web.id
    source_ip_ranges_to_nat = ["ALL_IP_RANGES"]
  }
  depends_on                         = [ google_compute_address.web ]
}
```

# Firewall

Firewall allows us to restrict the inbound and outbound network traffic to and from a VM instance, a network tag or a service account. In our case, we can implement the following rules:

* A rule to restrict access to the Cloud SQL MySQL instance port to only GKE nodes.
* A rule to restrict network access to only authorized networks.

Create a terraform file `infra/plan/firewall.tf`

```terraform
resource "google_compute_firewall" "mysql" {
  name    = "allow-only-gke-cluster"
  network = google_compute_network.custom.name

  allow {
    protocol = "tcp"
    ports    = ["3306"]
  }

  priority = 1000

  source_ranges = ["10.10.10.0/24"]
}

resource "google_compute_firewall" "web" {
  name    = "allow-only-authorized-networks"
  network = google_compute_network.custom.name

  allow {
    protocol = "tcp"
  }

  priority = 1000

  source_ranges = var.authorized_source_ranges
}
```

Let's configure our terraform.

Create a terraform file `infra/plan/variable.tf`

```terraform
variable "region" {
  type = string
  default = "europe-west1"
}

variable "authorized_source_ranges" {
  type        = list(string)
  description = "Addresses or CIDR blocks which are allowed to connect to GKE API Server."
}
```

Add a `infra/plan/version.tf` file

```terraform
terraform {
  required_providers {
    google = {
      source = "hashicorp/google"
      version = "3.71.0"
    }
  }
}
```

Add a `infra/plan/provider.tf` file

```terraform
provider "google" {
  region  = "europe-west1"
}
```

And a `infra/plan/backend.tf`

```terraform
terraform {
  backend "gcs" {
  }
}
```

Now, export the following variables and create a bucket to save your terraform states.

```shell
export PROJECT_ID=<PROJECT_ID>
export REGION=<REGION>
export TERRAFORM_BUCKET_NAME=<BUCKET_NAME>

gcloud config set project ${PROJECT_ID}

gsutil mb -c standard -l ${REGION} gs://${TERRAFORM_BUCKET_NAME}
gsutil versioning set on gs://${TERRAFORM_BUCKET_NAME}
```

Create a `infra/plan/terraform.tfvars` and deploy the infrastructure:

```shell
authorized_source_ranges    = ["<AUTHORIZED_NETWORK>"]
```

```shell
cd infra/plan

sed -i "s,<AUTHORIZED_NETWORK>,$AUTHORIZED_NETWORK,g" terraform.tfvars

terraform init \
  -backend-config="bucket=${TERRAFORM_BUCKET_NAME}" \
  -backend-config="prefix=state"

terraform apply
```

Let's check if all the resources have been created and are working correctly

VPC & Subnets

![VPC & Subnets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dssddjvca0chr9ch6lvm.png)

Cloud NAT

![Cloud NAT](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/439grkh3b2kpg4ein5vy.png)

Firewalls

![Firewall rules](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6esgcjxqflsiixhaytxl.png)

# Conclusion

Our network is now ready to host our GCP resources. In the [next part][part-3], we will focus on setting up GKE Autopilot.

[part-1]: https://dev.to/stack-labs/designing-a-defense-in-depth-network-security-model-between-gke-autopilot-and-cloud-sql-187d
[part-3]: https://dev.to/stack-labs/creating-a-google-kubernetes-engine-autopilot-cluster-using-terraform-1l22
[gcp-1]: https://cloud.google.com/vpc/docs/shared-vpc