Skip to main content

Summary

This post will detail how to create a secure IAP (Identity Aware Proxy) tunnel to a VM (Virtual Machine) inside a VPC without requiring a public IP address or VPN

This will enable you to establish secure remote access to VM’s over protocols such as SSH, RDP or VNC.

As part of this process, we will use also use a conditional IAM policy that will ensure that access to the VM is secured based upon the source IP address range.

We will use Google’s IAP (Identity Aware Proxy) service to provide authentication and then leverage Google’s conditional IAM policies with an ‘Access Level’ defined in Google’s Access Context Manager to restrict access to a specific region, source IP address or IP address range.

First, we will run through the problem, some simple diagrams to illustrate it, and finally, how we will go about using Google’s services mentioned above to solve the problem.

At the end of the post will be some example Terraform code that will show how to create the resources required for this solution.

The why

Remote working has increased dramatically in the last year or so thanks to COVID, so providing secure remote access to services for users has become more and more important.

In years gone by, simply being on the ‘corporate network’ was often enough to provide secure access to services, but with the rise of remote working, this is often no longer possible, or the corporate VPN that is provided is too broad and is not sufficient to provide effective controls to services.

For this example, let’s assume that you have a simple HTTP web server created in a private VPC on a private IP subnet in a GCP project.

We will make this an overly simple example using the below diagram, showing connections from a client device on the public internet to this web server going via a load balancer and the request is then forwarded on to the web server in the private VPC.

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

The problem

A user may need to access the remote HTTP web server over a remote access protocol such as SSH, VNC or RDP in order to perform administrative tasks or maintenance.

As the HTTP web server does not have a public IP address and is not directly accessible from the internet, this presents a problem. How do we easily and securely access this server without additional complex infrastructure such as a VPN?

The solution

Introducing IAP (Identity Aware Proxy). Google’s IAP service is able to solve this problem by providing a method to allow a user to securely connect to an instance by establishing a TCP tunnel through Google’s IAP servers.

In this example, we will show how we can use IAP to allow SSH access to a VM instance.

An overly simple diagram shows how we will be configuring access to the VM via SSH.

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

Authentication and Authorisation

In the example above, the client requests an ssh connection to the webserver using Google’s gcloud command-line tool.

By using the tunnel-through-iap flag, we can tell gcloud to tunnel the ssh connection through the IAP servers.

Example command:

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

What this command will do, is to first attempt to authenticate the user to GCP. Then GCP will check the IAM policy to see if it user has the required permission (roles/iap.tunnelResourceAccessor) to establish an IAP tunnel to the IAP servers.
(The IAP servers sit in this IP address range 35.235.240.0/20)

In this case, we will be using a conditional IAM role whereby the incoming connection will be checked against an Access List (Access Context Manager) to ensure that the IP address range the client device is on is within the range specified and if this evaluates to true the user will be granted the required role: roles/iap.tunnelResourceAccessor

Once the user has been authenticated and had their IAM policy checked to ensure the user is authorised to access the service, the next step is to make a connection from the IAP servers to the VM.

This will require a firewall rule in the VPC allowing incoming connections to the VM from the IAP servers subnet. 35.235.240.0/20

With all this in place, we will be able to securely establish a remote access connection!

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

Terraform code

Now we know what we are trying to achieve and how we can get stuck into implementing this in Terraform.

Assumptions

The assumption here is that you already have access to the following:

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

Note: In order to get the ID for the Access Policy, try the following commands:

  • Get your organization ID

    gcloud organizations list
  • Find your Access Policy ID

    gcloud access-context-manager policies list --organization <org-id>

The access-policy-id or name will be the number under the NAME column
e.g.:

NAME ORGANIZATION TITLE965510000000 465000000000

Create the Access List

# 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 the conditional IAM policy assigning the role

roles/iap.tunnelResourceAccessor to a user if the Access List evaluates to true (Eg. the user is making the request from an IP in the defined ip_subnetworks above)

# 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"
    }
}

Ok, not quite.

There are a couple of extra settings we need to make to ensure we can connect to the VM:

Assign compute roles to the user to allow them access to the VM

# 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

While this post focused on access to a VM using SSH. This solution can easily be extended to use any TCP port. (See: tunneling_other_tcp_connections)

So obtaining remote access via VNC or RDP to a VM is as simple as adding the required port/protocol to the firewall rule that allows connections to the VM from the IAP servers.
Accessing the VM using VNC or RDP, however, is a little bit different.
Instead, we will use the

gcloudcommand to create a tunnel bound to a port on the localhost.

Then we can point our VNC or RDP client to this localhost address and have it tunnel through to our VM.

Update the firewall rule to enable VNC (port 5901) and RDP (port 3389)

## 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"]
}

Create a tunnel using VNC

gcloud compute start-iap-tunnel <my-instance-name> 5901 
  --local-host-port=localhost:5901

Connect to your VM using your VNC client
Now that we have established a tunnel to the instance using VNC, we can use our VNC viewer application to connect to

localhost:5901

and use the tunnel to make the connection to our VM

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

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

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

Connected to our VM in GCP

The same process as above works for Microsoft RDP as well, simply change the port in the gcloud command from 5901 to 3389 then use your remote desktop application to connect to localhost:3389 and the connection will go through as well.

Other possibilities

From here you can also see that it is trivial to open a tunnel over any TCP port to our VM in GCP. (Don’t forget to update the list of ports to allow in from our IAP servers in the firewall rule)

For example, we might have a web server running on our VM in GCP that we wish to access, but without having a public IP address or load balancer setup, we can simply create an IAP tunnel to port 80 (or whatever port our web server is listening on) and then establish that on our local host. From here we could access it in a web browser by going to http://localhost:80

Example:

Start a simple web server on our VM in GCP

python -m SimpleHTTPServer

Update our firewall to allow port 8000 from IAP servers to our VM

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"
  ]
}

Now lets start a tunnel a try connecting to it via the tunnel

gcloud compute start-iap-tunnel <my-instance-name> 8000
  --local-host-port=localhost:8000

Access it in a browser

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

Thanks to Jake Nelson