Let your servers join your Tailscale tailnet with Terraform and cloud-init

First generate an oauth client credentials for use in Terraform by going to https://login.tailscale.com/admin/settings/oauth and clicking “Generate oauth client”:

Screenshot 2024-08-03 at 13.57.36.png

Note down your oauth_client_id and you oauth_client_secret.

Inside Terraform first add the Tailscale provider:

terraform {
  required_providers {
    tailscale = {
      source  = "tailscale/tailscale"
      version = "0.16.1"
    }
  }
}

After running terraform init, you can then create a file named tailscale.tf and insert this:

provider "tailscale" {
  oauth_client_id = ""
  oauth_client_secret = ""
}

resource "tailscale_tailnet_key" "hourlykey" {
  reusable      = true
  ephemeral     = true
  preauthorized = true
  expiry        = 3600
  description   = "Hourly key Terraform managed"
  tags          = ["tag:server"]
}

Now Terraform will automatically create tailnet keys that works just for an hour, and they will automatically expire, but give you enough time to bootstrap servers and for them to join your Tailnet, neat!

So how do we use it? #

Most providers allow for a user_data configuration option that uses cloud-init, some commands that will be run when the server boots:

resource "digitalocean_droplet" "server" {

  user_data = <<EOF
#cloud-config
runcmd:
  - tailscale up --authkey ${tailscale_tailnet_key.hourlykey.key} --accept-routes --accept-dns
EOF

Some people like to add a command that also downloads the Tailscale client, this means that your Tailscale will be up to date, but I don’t like the idea of installing software when I boot up servers, instead I prebake the Tailscale installation into my Packer images. But if you’d like to setup Tailscale as part of cloud-init you do this:

resource "digitalocean_droplet" "server" {

  user_data = <<EOF
#cloud-config
runcmd:
  - ['sh', '-c', 'curl -fsSL https://tailscale.com/install.sh | sh']
  - tailscale up --authkey ${tailscale_tailnet_key.hourlykey.key} --accept-routes --accept-dns
EOF

So now everything is great, right? #

Not so fast! Because what happens if we run Terraform an hour later after our key have expired? Terraform will try and replace our servers with new servers using the newly generated Tailscale key!

The way to avoid is to ignore changes to user_data, so the final configuration looks like this:

resource "digitalocean_droplet" "server" {

  user_data = <<EOF
#cloud-config
runcmd:
  - ['sh', '-c', 'curl -fsSL https://tailscale.com/install.sh | sh']
  - tailscale up --authkey ${tailscale_tailnet_key.hourlykey.key} --accept-routes --accept-dns
EOF

  lifecycle {
    ignore_changes = [
      user_data
    ]
  }
 
0
Kudos
 
0
Kudos

Now read this

Building Raspberry Pi images with Packer

I recently had to build a couple of Raspberry Pi images, I decided to try and set it up using Packer so the process was documented, version controlled and automatic. I searched around for a bit, and found the project named... Continue →