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.
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.
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!
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:
- A GCP environment with an organisation.
(Access Context Manager requires a Google Cloud Organisation to be set up.) - A GCP project with a VPC and subnets already configured (default is fine)
- The Google Cloud SDK installed on the machine you are using to connect to the VM instance.
- A service account that Terraform will use to provision the resources into the GCP project.
(We will assume terraform will be running as this highly privileged service account)
Getting started
Create a Linux VM in the project and enable OS Login
Create the Firewall rule to allow incoming ssh connections from Google IAP servers to our instance
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
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
Connect to the instance by connecting to the tunnel that has been established at localhost:5901
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
Thanks to Jake Nelson