Setting up a VPN Client Service using Google Cloud

Google Cloud provides high performance infrastructure for cloud computing, data analytics and machine learning. The infrastructure is provided in secure, global, highly available, and highly reliable manner for end users to consume when building applications. The user range from individuals, startups to large organisations.

In order to provide global reach google cloud operates out of multiple regions (35 at the time when this was written) and multiple data centres in those regions. It also provides high speed connectivity in and across those regions using a custom network built to handle terabytes of traffic securely and reliably.

One of the advantages of having a global presence with high speed connectivity is that you can setup client VPN service that allows you to connect your personal devices to a VPN server in a particular region and then egress out to the wider internet from that region.

Getting Started

In order to start using Google Cloud you need a Google Account. Once you have a Google Account, you can start using the Google Cloud Console, gcloud cli, Cloud APIs etc. Once you have created a Google account and logged in, navigate to the cloud console page. On the cloud console page start by creating a project and giving it a name like MyVPNService. The project is the bases of everything in Google Cloud. A project organises all your resources. You can have multiple projects to organise your resources into logical groups. Inside each project a default VPC is created for you. This default VPC has subnets in every region of Google Cloud. In order to create resources (such as a VM) in a particular region, you can choose the subnet for that region and create the resource. In this blog I will be using the europe-west-2 (London) region hence the VPN will be in the UK.

Creating a static IP and the VM resource

Once we have the project ready and have selected a region where we want the VPN server to be hosted from, we are going to register a static external IP for the VM instance we are creating next. The reason why we need a static public IP allocation is so that if the VM is restarted a new public IP is not allocated as that will require the VPN configuration to change on the clients every time. The static IP can be reserved using the IP Addresses option under the VPC network page.

The VM instance will be created using an machine image from the Google Cloud Marketplace. The marketplace lets you quickly deploy software on the Google Cloud platform by having third parties and vendors publish solution and machine images with pre built software. In order to create a VPN server we will be using a prebuilt image called OpenVPN Access Server from the Marketplace. This is a free image (you only pay for the infrastructure cost of the instance running) that comes with an installation of the OpenVPN server package and the default license allows you to have two concurrent VPN connections.

Choose Launch to create an instance. On the configuration page we can give the VM a name plus choose the region we want to host it in (by selecting the zone and the subnet in the network setting tab). The important thing to set is the instance CPU and memory size as that determines a major part of the cost of running this solution. Since we are planning to use the VPN for short bursts, I selected the e2-micro instance as it provides a cheap option to run burstable instances on Google Compute Engine.

The default firewall rules allow you to access the VPN service and the configuration website from anywhere on the internet. You can restrict it to your IP if required. One important action to perform is to make sure you use the reserved static ip from the previous step when creating the instance. You can do that by selecting the name of the reserved static ip in the networking tab under the external ip option:

Deployment Manager

Within a few minutes the server should be ready. Now we can access the OpenVPN Admin site and the page to download the configuration for your device client running on your machine or phone. This configuration (URLs, username, password etc) can be accessed via the Google Deployment Manager.

The Admin URL takes you to the advanced configuration setting for OpenVPN. The default configurations are ok to start of with and create the initial connection.

OpenVPN Access Server Client Configuration

Once the server is up and running you can connect to the Site address (listed in the Deployment Manager as shown above) to login and download the client configuration to connect to the VPN server. The website allows you download the client.opvn file that can be used with the OpenVPN client application on your device to connect.

And thats it, now you should be able to establish your VPN session to the VM Instance.

Bonus – Start and Stop the VPN server when you require

Its great to be able to run your VPN infrastructure but running the VM Instance all the time is not very efficient. It would be better to have the ability to start and stop the instance on demand. We can achieve this by using a Google Cloud Function.

Start/Stop Cloud Function

Cloud Functions allow you to run your code without any infrastructure creation or management. We can create functions that when invoked can be used to start or stop the VM instance. There are multiple ways to invoke a Cloud Function. One way is that Cloud Functions come with a web endpoint that when accessed via a HTTP request can invoke the functions. We can create a Start Function and a Stop function that can be invoked by us to start or stop the instance on demand. We can use the following code in the two functions that we create to Start of Stop the VM instance on demand.

import sys
import time
from typing import Any

from google.api_core.extended_operation import ExtendedOperation
from google.cloud import compute_v1


def wait_for_extended_operation(
    operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
) -> Any:
    """
    This method will wait for the extended (long-running) operation to
    complete. If the operation is successful, it will return its result.
    If the operation ends with an error, an exception will be raised.
    If there were any warnings during the execution of the operation
    they will be printed to sys.stderr.

    Args:
        operation: a long-running operation you want to wait on.
        verbose_name: (optional) a more verbose name of the operation,
            used only during error and warning reporting.
        timeout: how long (in seconds) to wait for operation to finish.
            If None, wait indefinitely.

    Returns:
        Whatever the operation.result() returns.

    Raises:
        This method will raise the exception received from `operation.exception()`
        or RuntimeError if there is no exception set, but there is an `error_code`
        set for the `operation`.

        In case of an operation taking longer than `timeout` seconds to complete,
        a `concurrent.futures.TimeoutError` will be raised.
    """
    result = operation.result(timeout=timeout)

    if operation.error_code:
        print(
            f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
            file=sys.stderr,
            flush=True,
        )
        print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
        raise operation.exception() or RuntimeError(operation.error_message)

    if operation.warnings:
        print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
        for warning in operation.warnings:
            print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)

    return result


def start_instance(project_id: str, zone: str, instance_name: str) -> None:
    """
    Starts a stopped Google Compute Engine instance (with unencrypted disks).
    Args:
        project_id: project ID or project number of the Cloud project your instance belongs to.
        zone: name of the zone your instance belongs to.
        instance_name: name of the instance your want to start.
    """
    instance_client = compute_v1.InstancesClient()

    operation = instance_client.start(
        project=project_id, zone=zone, instance=instance_name
    )

    wait_for_extended_operation(operation, "instance start")
    return

def stop_instance(project_id: str, zone: str, instance_name: str) -> None:
    """
    Stops a running Google Compute Engine instance.
    Args:
        project_id: project ID or project number of the Cloud project your instance belongs to.
        zone: name of the zone your instance belongs to.
        instance_name: name of the instance your want to stop.
    """
    instance_client = compute_v1.InstancesClient()

    operation = instance_client.stop(
        project=project_id, zone=zone, instance=instance_name
    )
    wait_for_extended_operation(operation, "instance stopping")
    return


def main(request):
    start_instance('myvpn-365722','europe-west2-a','openvpn-access-server-1-vm')
    return 'Success', 200

Please leave your comments below if you found this useful.

Leave a comment