Deploy Tunnels with Terraform
Terraform is an infrastructure as code software tool that allows you to deploy services from different providers using a standardized configuration syntax. When creating a Terraform configuration file, you define the final state of the configuration rather than the step-by-step procedure. This allows you to easily deploy, modify, and manage your Tunnels alongside your other infrastructure.
In this guide, you will use Terraform to deploy:
- A Google Cloud Project (GCP) virtual machine that runs a simple HTTP test server
- A Cloudflare Tunnel that makes the server available over the Internet
- A Cloudflare Access policy that defines who can connect to the server
Prerequisites
To complete the following procedure, you will need:
1. Install Terraform
Refer to the Terraform installation guide for your operating system.
2. Install the gcloud CLI
Install the gcloud CLI so that Terraform can interact with your GCP account.
Authenticate with the CLI by running:
$ gcloud auth application-default login
3. Create a Cloudflare API token
Create an API token so that Terraform can interact with your Cloudflare account. At minimum, your token should include the following permissions:
Permission type | Permission | Access level |
---|---|---|
Account | Cloudflare Tunnel | Edit |
Account | Access: Apps and Policies | Edit |
Zone | DNS | Edit |
4. Create a configuration directory
Terraform functions through a working directory that contains the configuration files. You can store your configuration in multiple files or just one — Terraform will evaluate all of the configuration files in the directory as if they were in a single document.
Create a folder for your Terraform configuration:
$ mkdir gcp-tunnelChange into the directory:
$ cd gcp-tunnel
5. Create Terraform configuration files
Define input variables
The following variables will be passed into your GCP and Cloudflare configuration.
In your configuration directory, create a
.tf
file:$ touch variables.tfOpen the file in a text editor and copy and paste the following:
variables.tf
# GCP variablesvariable "gcp_project_id" {description = "Google Cloud Platform (GCP) project ID"type = string}variable "zone" {description = "Geographical zone for the GCP VM instance"type = string}variable "machine_type" {description = "Machine type for the GCP VM instance"type = string}# Cloudflare variablesvariable "cloudflare_zone" {description = "Domain used to expose the GCP VM instance to the Internet"type = string}variable "cloudflare_zone_id" {description = "Zone ID for your domain"type = string}variable "cloudflare_account_id" {description = "Account ID for your Cloudflare account"type = stringsensitive = true}variable "cloudflare_email" {description = "Email address for your Cloudflare account"type = stringsensitive = true}variable "cloudflare_token" {description = "Cloudflare API token created at https://dash.cloudflare.com/profile/api-tokens"type = string}
Assign values to the variables
Configure Terraform providers
You will need to declare the providers used to provision the infrastructure.
In your configuration directory, create a
.tf
file:$ touch providers.tfAdd the following providers to
providers.tf
. Therandom
provider is used to generate a tunnel secret.providers.tf
terraform {required_providers {cloudflare = {source = "cloudflare/cloudflare"}google = {source = "hashicorp/google"}random = {source = "hashicorp/random"}}required_version = ">= 0.13"}# Providersprovider "cloudflare" {api_token = var.cloudflare_token}provider "google" {project = var.gcp_project_id}provider "random" {}
Configure Cloudflare resources
The following configuration will modify settings in your Cloudflare account.
In your configuration directory, create a
.tf
file:$ touch Cloudflare-config.tfAdd the following resources to
Cloudflare-config.tf
:Cloudflare-config.tf
# Generates a 35-character secret for the tunnel.resource "random_id" "tunnel_secret" {byte_length = 35}# Creates a new locally-managed tunnel for the GCP VM.resource "cloudflare_argo_tunnel" "auto_tunnel" {account_id = var.cloudflare_account_idname = "Terraform GCP tunnel"secret = random_id.tunnel_secret.b64_std}# Creates the CNAME record that routes http_app.${var.cloudflare_zone} to the tunnel.resource "cloudflare_record" "http_app" {zone_id = var.cloudflare_zone_idname = "http_app"value = "${cloudflare_argo_tunnel.auto_tunnel.id}.cfargotunnel.com"type = "CNAME"proxied = true}# Creates an Access application to control who can connect.resource "cloudflare_access_application" "http_app" {zone_id = var.cloudflare_zone_idname = "Access application for http_app.${var.cloudflare_zone}"domain = "http_app.${var.cloudflare_zone}"session_duration = "1h"}# Creates an Access policy for the application.resource "cloudflare_access_policy" "http_policy" {application_id = cloudflare_access_application.http_app.idzone_id = var.cloudflare_zone_idname = "Example policy for http_app.${var.cloudflare_zone}"precedence = "1"decision = "allow"include {email = [var.cloudflare_email]}}To learn more about these resources, refer to the Cloudflare provider documentation.
Configure GCP resources
The following configuration defines the specifications for the GCP virtual machine and creates a startup script to run upon boot.
In your configuration directory, create a
.tf
file:$ touch GCP-config.tfAdd the following content to
GCP-config.tf
:GCP-config.tf
# Selects the OS for the GCP VM.data "google_compute_image" "image" {family = "ubuntu-minimal-2004-lts"project = "ubuntu-os-cloud"}# Sets up a GCP VM instance.resource "google_compute_instance" "origin" {name = "test"machine_type = var.machine_typezone = var.zonetags = []boot_disk {initialize_params {image = data.google_compute_image.image.self_link}}network_interface {network = "default"access_config {// Ephemeral IP}}// Optional config to make the instance ephemeralscheduling {preemptible = trueautomatic_restart = false}// Configures the VM to run a startup script that takes in the Terraform variables.metadata_startup_script = templatefile("./install-tunnel.tpl",{web_zone = var.cloudflare_zone,account = var.cloudflare_account_id,tunnel_id = cloudflare_argo_tunnel.auto_tunnel.id,tunnel_name = cloudflare_argo_tunnel.auto_tunnel.name,secret = random_id.tunnel_secret.b64_std})}
Create a startup script
The following script will install cloudflared
, create a permissions and configuration file for the tunnel, and set up the tunnel to run as a service. This example also installs a lightweight HTTP application that you can use to test connectivity.
In your configuration directory, create a Terraform template file:
$ touch install-tunnel.tftplOpen the file in a text editor and copy and paste the following bash script:
install-tunnel.tftpl
# Script to install Cloudflare Tunnel and Docker resources# Docker configurationcd /tmpsudo apt-get install software-properties-common# Retrieving the docker repository for this OScurl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"# The OS is updated and docker is installedsudo apt update -y && sudo apt upgrade -ysudo apt install docker docker-compose -y# Add the HTTPBin application and run it on localhost:8080.cat > /tmp/docker-compose.yml << "EOF"version: '3'services:httpbin:image: kennethreitz/httpbinrestart: alwayscontainer_name: httpbinports:- 8080:80EOF# cloudflared configurationcd ~# Retrieve the cloudflared Linux packagewget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.debsudo dpkg -i cloudflared-linux-amd64.deb# Create a local user directory to temporarily hold the tunnel configuration.mkdir ~/.cloudflaredtouch ~/.cloudflared/cert.jsontouch ~/.cloudflared/config.yml# Populate the tunnel credentials file.cat > ~/.cloudflared/cert.json << "EOF"{"AccountTag" : "${account}","TunnelID" : "${tunnel_id}","TunnelName" : "${tunnel_name}","TunnelSecret" : "${secret}"}EOF# Define the ingress rules the tunnel will use.cat > ~/.cloudflared/config.yml << "EOF"tunnel: ${tunnel_id}credentials-file: /etc/cloudflared/cert.jsonlogfile: /var/log/cloudflared.logloglevel: infoingress:- hostname: http_app.${web_zone}service: http://localhost:8080- hostname: "*"service: hello-worldEOF# Install the tunnel as a systemd service. This automatically copies cert.json to /etc/cloudflared.sudo cloudflared service install# The credentials file does not get copied over so we do that manually.sudo cp -via ~/.cloudflared/cert.json /etc/cloudflared/# Start HTTPBin and start the tunnelcd /tmpsudo docker-compose up -d && sudo systemctl start cloudflared
6. Deploy Terraform
Once the configuration files are created, they can be deployed.
Initialize your configuration directory:
$ terraform initThis will set up the directory so that your infrastructure can be deployed.
Before actually deploying your infrastructure, you can preview everything that will be created:
$ terraform planDeploy the configuration:
$ terraform apply
It may take several minutes for the GCP instance and tunnel to come online. You can view your new tunnel, Access application, and Access policy in the Access section of Zero Trust. The new DNS records are available in the Cloudflare dashboard.
7. Test the connection
In Access > Tunnels, verify that your tunnel is active.
In Access > Applications, verify that your Cloudflare email is allowed by the Access policy.
From any device, open a browser and go to
http_app.<cloudflare_zone>
(for example,http_app.example.com
).You will see the Access login page if you have not recently logged in.
Log in with your Cloudflare email.
You should see the HTTPBin homepage.