Published on

Exposing resources securely with Argo Tunnel

5 min read

Authors
banner

In this article, we'll see how to leverage Cloudflare's argo tunnels without publicly exposing our services. In this setup we'll use EC2 as an example, but this is works with Fargate as well.

Dev Env CF & EC2

What is an Argo Tunnel?

Argo Tunnel provides a secure way to connect your origin to Cloudflare without a publicly routable IP address. With Argo Tunnel, you do not expose an external IP from your infrastructure to the Internet. Instead, a lightweight daemon runs in your infrastructure and creates outbound-only connections to Cloudflare's edge.

There are tons of use cases like using exposing internal applications, replacing VPN setup with Cloudflare Access possibilities are endless!

Currently, I use this setup for developing remotely on EC2's securely.

tunnels

What is Cloudflare Access?

If we want to only expose this to your team internally, we can configure a zero trust access with Cloudflare access

no trust access

We'll use Terraform to define our cloud resources

Let's define the providers aws and cloudflare which we'll be using:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.7.0"
    }
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 2.26.0"
    }
  }
  required_version = ">= 0.13"
}

provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

We can define our api keys and secrets as variables in variables.tf

variable "region" {
  type    = string
  default = "us-west-2"
}

variable "cloudflare_api_token" {
  type        = string
  description = "Cloudflare api token"
  default     = "<YOUR_API_TOKEN>"
}

variable "cloudflare_account_id" {
  type        = string
  description = "Cloudflare account id"
  default     = "<YOUR_ACCOUNT_ID>"
}

variable "cloudflare_zone_id" {
  type        = string
  description = "Cloudflare zone id"
  default     = "<YOUR_ZONE_ID>"
}

variable "subdomain" {
  type        = string
  description = "your subdomain"
  default     = "pretty-cool"
}

In our main.tf file, we'll define resources. We'll first create an argo tunnel and add a cloudflare proxied record CNAME for it so that we can use in our user_data init script.

resource "random_id" "argo_secret" {
  byte_length = 35
}

resource "cloudflare_argo_tunnel" "argo_tunnel" {
  account_id = var.cloudflare_account_id
  name       = "${var.subdomain}-tunnel"
  secret     = random_id.argo_secret.b64_std
}

resource "cloudflare_record" "http_app" {
  zone_id = var.cloudflare_zone_id
  name    = var.subdomain
  value   = "${cloudflare_argo_tunnel.argo_tunnel.id}.cfargotunnel.com"
  type    = "CNAME"
  proxied = true
}
data "template_file" "init" {
  template = file("my-project/init.tpl")

  vars = {
    subdomain   = var.subdomain
    domain      = var.cloudflare_zone,
    account     = var.cloudflare_account_id,
    tunnel_id   = cloudflare_argo_tunnel.argo_tunnel.id,
    tunnel_name = cloudflare_argo_tunnel.argo_tunnel.name,
    secret      = random_id.argo_secret.b64_std
  }
}

resource "aws_instance" "api" {
  ami           = "ami-03d5c68bab01f3496"
  instance_type = "t3.large"
  user_data = data.template_file.init.rendered
}

In the init.tpl we'll simply install cloudflared and create a config.ymland then run it in background as our EC2 starts.

#!/bin/bash

# Logs are availabe at /var/logs/cloud-init-output.log

# Install cloudflared
wget https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-amd64.deb
sudo dpkg -i cloudflared-stable-linux-amd64.deb

# Create required dirs
mkdir ~/.cloudflared
touch ~/.cloudflared/cert.json
touch ~/.cloudflared/config.yml

# Create cert.json
cat > ~/.cloudflared/cert.json << "EOF"
{
    "AccountTag"   : "${account}",
    "TunnelID"     : "${tunnel_id}",
    "TunnelName"   : "${tunnel_name}",
    "TunnelSecret" : "${secret}"
}
EOF

# Add a config file
cat > ~/.cloudflared/config.yml << "EOF"
tunnel: ${tunnel_id}
credentials-file: /etc/cloudflared/cert.json
logfile: /var/log/cloudflared.log
loglevel: info

ingress:
  - hostname: ${subdomain}.${domain}
    service: http://localhost:80
  - service: http_status:404
EOF

# Start Cloudflared service
sudo cloudflared service install
sudo cp -via ~/.cloudflared/cert.json /etc/cloudflared/
sudo service cloudflared start

Once our EC2 starts, we'll be able to access it via https://pretty-cool.your-cloudflare-domain.com and all without allowing any ingress in the security group!

Hope this was helpful!

© 2024 Karan Pratap Singh