Ansible
Ansible is a software tool that enables at scale management of infrastructure. Ansible is agentless — all it needs to function is the ability to SSH to the target and Python installed on the target.
Ansible works alongside Terraform to streamline the Cloudflare Tunnel setup process. In this guide, you will use Terraform to deploy an SSH server on Google Cloud and create a locally-managed tunnel that makes the server available over the Internet. Terraform will automatically run an Ansible playbook that installs and configures cloudflared
on the server.
To complete the steps in this guide, you will need:
- A Google Cloud Project ↗ and GCP CLI installed and authenticated ↗.
- Basic knowledge of Terraform andTerraform installed ↗.
- A zone on Cloudflare.
- A Cloudflare API token with
Cloudflare Tunnel
andDNS
permissions.
Refer to the Ansible installation instructions ↗.
Terraform and Ansible require an unencrypted SSH key to connect to the GCP server. If you do not already have a key, you can generate one as follows:
-
Open a terminal and type the following command:
Terminal window ssh-keygen -t rsa -f ~/.ssh/gcp_ssh -C <username in GCP> -
When prompted for a passphrase, press the
Enter
key twice to leave it blank. Terraform cannot decode encrypted private keys.
Two files will be generated: gcp_ssh
which contains the private key, and gcp_ssh.pub
which contains the public key.
-
Create a folder for your Terraform and Ansible configuration files:
Terminal window mkdir ansible-tunnel -
Change to the new directory:
Terminal window cd ansible-tunnel
The following variables will be passed into your GCP and Cloudflare configuration.
-
In your configuration directory, create a
.tf
file:Terminal window touch variables.tf -
Open the file in a text editor and copy and paste the following:
# 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 = stringsensitive = true}
-
In your configuration directory, create a
.tfvars
file:Terminal window touch terraform.tfvarsTerraform will automatically use these variables if the file is named
terraform.tfvars
, otherwise the variable file will need to be manually passed in. -
Add the following variables to
terraform.tfvars
. Be sure to modify the example with your own values.cloudflare_zone = "example.com"cloudflare_zone_id = "023e105f4ecef8ad9ca31a8372d0c353"cloudflare_account_id = "372e67954025e0ba6aaa6d586b9e0b59"cloudflare_email = "user@example.com"cloudflare_token = "y3AalHS_E7Vabk3c3lX950F90_Xl7YtjSlzyFn_X"gcp_project_id = "testvm-123"zone = "us-central1-a"machine_type = "e2-medium"
You will need to declare the providers ↗ used to provision the infrastructure.
-
In your configuration directory, create a
.tf
file:Terminal window touch providers.tf -
Add the following providers to
providers.tf
. Therandom
provider is used to generate a tunnel secret.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" {}
The following configuration will modify settings in your Cloudflare account.
-
In your configuration directory, create a
.tf
file:Terminal window touch Cloudflare-config.tf -
Add the following resources to
Cloudflare-config.tf
:# Generates a 64-character secret for the tunnel.# Using `random_password` means the result is treated as sensitive and, thus,# not displayed in console output. Refer to: https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/passwordresource "random_password" "tunnel_secret" {length = 64}# Creates a new locally-managed tunnel for the GCP VM.resource "cloudflare_tunnel" "auto_tunnel" {account_id = var.cloudflare_account_idname = "Ansible GCP tunnel"secret = base64sha256(random_password.tunnel_secret.result)}# Creates the CNAME record that routes ssh_app.${var.cloudflare_zone} to the tunnel.resource "cloudflare_record" "ssh_app" {zone_id = var.cloudflare_zone_idname = "ssh_app"value = "${cloudflare_argo_tunnel.auto_tunnel.id}.cfargotunnel.com"type = "CNAME"proxied = true}
The following configuration defines the specifications for the GCP virtual machine and installs Python3 on the machine. Python3 allows Ansible to configure the GCP instance instead of having to run a startup script on boot.
-
In your configuration directory, create a
.tf
file:Terminal window touch GCP-config.tf -
Open the file in a text editor and copy and paste the following example. Be sure to insert your own GCP username and SSH key pair.
# 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 = "ansible-inst"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}}scheduling {preemptible = trueautomatic_restart = false}// Installs Python3 on the VM.provisioner "remote-exec" {inline = ["sudo apt update", "sudo apt install python3 -y", "echo Done!"]connection {host = self.network_interface.0.access_config.0.nat_ipuser = "<username in GCP>"type = "ssh"private_key= file("<path to private key>")}}provisioner "local-exec" {// If specifying an SSH key and user, add `--private-key <path to private key> -u var.name`command = "ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -u <username in GCP> --private-key <path to private key> -i ${self.network_interface.0.access_config.0.nat_ip}, playbook.yml"}metadata = {cf-email = var.cloudflare_emailcf-zone = var.cloudflare_zonessh-keys = "<username in GCP>:${file("<path to public key>")}"}depends_on = [local_file.tf_ansible_vars_file]}
The following Terraform resource exports the tunnel ID and other variables to tf_ansible_vars_file.yml
. Ansible will use this data to configure and run cloudflared
on the server.
-
In your configuration directory, create a new
tf
file:Terminal window touch export.tf -
Copy and paste the following content into
export.tf
:resource "local_file" "tf_ansible_vars_file" {content = <<-DOC# Ansible vars_file containing variable values from Terraform.tunnel_id: ${cloudflare_argo_tunnel.auto_tunnel.id}account: ${var.cloudflare_account_id}tunnel_name: ${cloudflare_argo_tunnel.auto_tunnel.name}secret: ${random_id.tunnel_secret.b64_std}zone: ${var.cloudflare_zone}DOCfilename = "./tf_ansible_vars_file.yml"}
Ansible playbooks are YAML files that declare the configuration Ansible will deploy.
-
Create a new
.yml
file:Terminal window touch playbook.yml -
Open the file in a text editor and copy and paste the following content:
---- hosts: all become: yes # Import tunnel variables into the VM. vars_files: - ./tf_ansible_vars_file.yml # Execute the following commands on the VM. tasks: - name: Download the cloudflared Linux package. shell: wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb - name: Depackage cloudflared. shell: sudo dpkg -i cloudflared-linux-amd64.deb - name: Create a cloudflared service directory. shell: mkdir -p /etc/cloudflared/ - name: Create the config file for cloudflared and define the ingress rules for the tunnel. copy: dest: "/etc/cloudflared/config.yml" content: | tunnel: "{{ tunnel_id }}" credentials-file: /etc/cloudflared/cert.json logfile: /var/log/cloudflared.log loglevel: info ingress: - hostname: "ssh_app.{{ zone }}" service: ssh://localhost:22 - service: http_status:404 - name: Create the tunnel credentials file for cloudflared. copy: dest: "/etc/cloudflared/cert.json" content: | { "AccountTag" : "{{ account | quote }}", "TunnelID" : "{{ tunnel_id | quote }}", "TunnelName" : "{{ tunnel_name | quote }}", "TunnelSecret" : "{{ secret | quote }}" } - name: Install the tunnel as a systemd service. shell: cloudflared service install - name: Start the tunnel. systemd: name: cloudflared state: started enabled: true masked: no
Keywords ↗ define how Ansible will execute the configuration. In the example above, the vars_files
keyword specifies where variable definitions are stored, and the tasks
keyword specifies the actions Ansible will perform.
Modules ↗ specify what tasks to complete. In this example, the copy
module creates a file and populates it with content.
Once you have created the configuration files, you can deploy them through Terraform. The Ansible deployment happens within the Terraform deployment when the ansible-playbook
command is run.
-
Initialize your configuration directory:
Terminal window terraform init -
(Optional) Preview everything that will be created:
Terminal window terraform plan -
Deploy the configuration:
Terminal window terraform apply
It may take several minutes for the GCP instance and tunnel to come online. You can view your new tunnel in Zero Trust ↗ under Networks > Tunnels.
You can now SSH to the GCP server through the new ssh_app.<zone>
hostname. For instructions on how to connect, refer to our SSH guide.