Blog

Using Terraform to create secure IAP tunnels on GCP with conditional IAM policies

By June 25, 2021 No Comments

Calum Hunter, Kasna Cloud Engineer

Summary.

The Why.

The problem.

The solution.

Authentication and Authorisation.

gcloud compute ssh <instance-name> \ --tunnel-through-iap --project=<project-name> \ --zone=<zone>

Terraform code.

Assumptions

Getting started.

Create a Linux VM in the project and enable OS Login.

# Create an instance
resource "google_compute_instance" "my-instance" {
    project         = var.project_id
    name            = "my-instance-01"
    machine_type    = "e2-standard-2"
    zone            = var.zone

    boot_disk {
        initialize_params {
            image = "debian-cloud/debian-9"
        }
    }
    network_interface {
        network = "default"
  }
    service_account {
      email = "<project-id>-compute@developer.gserviceaccount.com"
      scopes = ["cloud-platform"]
    }
    metadata = {
        enable-os-login = "true"
  }
}

Create the Firewall rule to allow incoming ssh connections from Google IAP servers to our instance.

## Allow incoming access to our instance via
## port 22, from the IAP servers
resource "google_compute_firewall" "inbound-ip-ssh" {
    name        = "allow-incoming-ssh-from-iap"
    project     = var.project_id
    network     = "default"

    direction = "INGRESS"
    allow {
        protocol = "tcp"
        ports    = ["22"]  
    }
    source_ranges = [
        "35.235.240.0/20"
    ]
    target_service_accounts = ["<project-id>-compute@developer.gserviceaccount.com"]
}

Create the Access List on the default Access Policy.

NAME          ORGANIZATION  TITLE965510000000  465000000000
# Creates an Access Level
# This access level will be used in
# a conditional IAM policy to restrict access
# to authorised users coming from authorised networks

resource "google_access_context_manager_access_level" "access-level" {
    parent  = "accessPolicies/<access-policy-id>"
    name    = "accessPolicies/<access-policy-id>/accessLevels/<my_access_level_name>"
    title   = "secure-iap-access-level"
    description = "This access level lists the authorised network addresses"
    basic {
        conditions {
            ip_subnetworks = [
                "10.0.0.1/32",
                ]
        }
    }
}
# Create a conditional IAM rule that grants access to establish an IAP tunnel
# IF the user is connecting from an authorised network defined in the access 
# list
resource "google_iap_tunnel_iam_member" "allow-remote-access-to-iap" {
    project = "<your-project-id>"
    role    = "roles/iap.tunnelResourceAccessor"
    member  = "user:calum.hunter@the.cloud"

    condition {
      title = "allow_remote_access_to_iap"
      description = "Allow access to IAP tunnel for authorized users"
      expression = "\"accessPolicies/<access-policy-id>/accessLevels/<my-access-level-name>\" in request.auth.access_levels"
    }
}
# Define the required roles to access the VM
locals {
    compute_roles = [ 
        "roles/compute.viewer",
        "roles/compute.osLogin",
    ]
}

# Apply the roles to a user account
resource "google_project_iam_member" "assign-roles" {
    count   = length(local.compute_roles)
    project = var.project_id
    role    = local.roles[count.index]
    member  = "user:calum.hunter@the.cloud"
}

Connecting to the instance with SSH via the IAP secure tunnel

gcloud compute ssh my-instance-01 
   --tunnel-through-iap 
   --project=<my-project> 
   --zone=<my-zone>

Bonus — Connecting via VNC and RDP

## Allow incoming access to our instance via
## port 22, from the IAP servers
resource "google_compute_firewall" "inbound-ip-ssh" {
    name        = "allow-incoming-access-from-iap"
    project     = var.project_id
    network     = "default"

    direction = "INGRESS"
    allow {
        protocol = "tcp"
        ports    = ["22", "3389", "5901"]  
    }
    source_ranges = [
        "35.235.240.0/20"
    ]
    target_service_accounts = ["<project-id>-compute@developer.gserviceaccount.com"]
}
gcloud compute start-iap-tunnel <my-instance-name> 5901 
  --local-host-port=localhost:5901

Connect to the instance by connecting to the tunnel that has been established at localhost:5901

Connected to our VM in GCP
resource "google_compute_firewall" "inbound-ip-ssh" {
  name        = "allow-incoming-ssh-from-iap"
  project     = var.project_id
  network     = "default"
  direction = "INGRESS"
  
  allow {
    protocol = "tcp"
    ports    = ["22", "5901", "3389", "8000"]
  }
  
  source_ranges = ["35.235.240.0/20"]  target_service_accounts = [
    "<project-id> compute@developer.gserviceaccount.com"
  ]
}
gcloud compute start-iap-tunnel <my-instance-name> 8000
  --local-host-port=localhost:8000

Thanks to Jake Nelson.