bdeshi 4 months ago
commit
d3d08bf71c
Signed by: bdeshi
GPG Key ID: 410D03DA9A3468E0
  1. 1
      .terraform-version
  2. 64
      .terraform.lock.hcl
  3. 28
      data.tf
  4. 29
      files/acl.hujson.tftpl
  5. 6
      files/relay-init.sh.tftpl
  6. 17
      locals.tf
  7. 49
      outputs.tf
  8. 24
      tailscale-network.tf
  9. 54
      tailscale-server.tf
  10. 41
      terraform.tf
  11. 106
      variables.tf

1
.terraform-version

@ -0,0 +1 @@ @@ -0,0 +1 @@
1.2.7

64
.terraform.lock.hcl

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/davidsbond/tailscale" {
version = "0.12.2"
constraints = "~> 0.12.0"
hashes = [
"h1:Ct6l3oqTKNi+sLcSqQFI4pnZ0MzoXVTTaK8xm34M/OI=",
"zh:5eaf378ea124dfd2628531012582e7cbd7a782c710f00794c04f3960118b2ad9",
"zh:770eb677c0230f17f8d5a48ea6b8d06424c860d541256a6f58cebba8011762ef",
"zh:7a784e8a05668e83e85f8791465adee0fb3ce9fe4885ba2a2b7f732df361a4f6",
"zh:80c6ba786a454cbc151e52f366cdc19c9d81f23ae00080fccb2d5083d2837c2d",
"zh:a0c6e08cc4c52536194a68ac835823b5467f4ff5268191bf1a89dc7d41bfd472",
"zh:aebf5123b045b2682a2fdbefab2273ff2a5699ff7f72b6c9a4759abe001221c9",
"zh:b2eb7e260749222f8a104ae9c883210afee1c71242eff5d3c6e1783f95ffe5cb",
"zh:b55906ea0be52c3ee674b9ac933d94af960573d462b4c93602701ea9f42dcd98",
"zh:d3878a61638d1a5fffcdf7e646ebfd6fa4d02c563e3f87c2b8333a43d45c9c69",
"zh:efa0221f96aaf75737702a1e1f3ea644ff18ba571ef1512d402b1b9e8a327d9e",
"zh:f0758b8f96065b559ab61b1f72cb7d2c27a4ba92487f8f5a41e244c16ca7478c",
"zh:f2f6659be19e4cdce78e8b56a2d91f56ddc0eb37da0328d54ae21912d4ff6961",
"zh:f5cf9652da93ac9b930aa5cf661e5ed2861b4c9aeeb242f692b3180a1f24fcf6",
"zh:fc00eec79e8719c14880b8ca97d5dfff1a206a60cf73bd465af6a8ce3d3726a2",
]
}
provider "registry.terraform.io/hashicorp/aws" {
version = "4.26.0"
constraints = "~> 4.26.0"
hashes = [
"h1:jt8jLpFFhaapdbBqw4WQpDuLN8y7zF8/iLyCzypDxSQ=",
"zh:0579b105ae471894846fbd740bc9f10b2bd8a48860d8e640b4a9b53fb7d63ffe",
"zh:0ce445cfbffb6c0eee9e0e2a95850b5749d56aa8211b95a686c24dc2847a36ea",
"zh:41f0cf0810363cea4e54f3d9c452f2eb77123bcdaacc18b978c825496168cae2",
"zh:431a7e967b5c9d7ebde6c714abedd9464be6a62f7eafa1808a86a8bd92851317",
"zh:4afebd3c3a8c0646f0874493840b6f8c82f7f4302780faec5c7b0c616077eebe",
"zh:7f077662efc8d7b91ef604999daf6b45a968cb2f5d8c4512a00d2feb4db05a7a",
"zh:9a58d1ef049ccaa9615fe5722ba815065f45d172f8bc656ffdbab4ca16f6b786",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
"zh:9d30b70a2daa0d94661590f6533e07071d2c7052b8279f05090f1bf037f56607",
"zh:b75f88be5d048849a632895d43b836ed1693031e586cd873ee915b5d3cf4fae6",
"zh:c57ac099b01fe49dd4e1e4674a06f61029fa6316e4f92a6a2a3bdc0444b371f9",
"zh:cb48a175ebb2a12fecae7dc6580bf88fbcf5408cdc53f3cf057150ebe9144034",
]
}
provider "registry.terraform.io/hashicorp/http" {
version = "3.0.1"
constraints = "~> 3.0.1"
hashes = [
"h1:4N7YctkZrU+K2AvUF57c1qUvoD92bBJj6vXwf/FKMhM=",
"zh:3b161998147d8cc3986a1580ddb065009ab628747424934cbcb9d221783541f8",
"zh:62c78b565cde08d8e3b98e8138cd8e46b50fdc2ddc560ac1f62b5646ce8e9b1f",
"zh:69ba560cd6360a285e83e1c220ab140d3119371850756ff2ed0abe39d362ea49",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
"zh:95f38aebfa176a3424a329bc0f2e958bcf5a1f98d91dee21a436ca670fb2d570",
"zh:97eae729eb859948201d4393761f5c1a7ffe84046473527f65163f062d9af5d9",
"zh:b42de839114707e2fcfdf5ebf3a89129e5e17ebb5f84651c5775daecd776dc3b",
"zh:c47fa93605b8378504008534e0057e295d209a2128553c7b1bcc4fc7f6efafa2",
"zh:d9d4fe5143f80c1ccf22b055f069445ab7470942bb46027dadda8f3bc62d2780",
"zh:f051820764c50f4736d21e40d9b13a1ffde678748a9e6e1ef22a26adf27db9bf",
"zh:f67c9b73998fce13e94623be9b7afe89b30e3e6d34b504f765a344b11b8808b8",
"zh:f7d255dac5a73d30c7e629699fdf064decf705cd701d29e2120cef7bf0fb1d7f",
]
}

28
data.tf

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
data "aws_vpc" "selected" {
id = var.vpc_id
}
data "aws_subnet" "selected" {
id = var.subnet_id
}
data "aws_ami" "selected" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-*"]
}
}
data "http" "relay_auth_key_response" {
url = "https://api.tailscale.com/api/v2/tailnet/${var.tailscale_domain}/keys/${tailscale_tailnet_key.relay_auth.id}"
# Optional request headers
request_headers = {
Accept = "application/json"
Authorization = "Basic ${local.tailscale_auth_token}"
}
}

29
files/acl.hujson.tftpl

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
{
"groups": {
"group:admin": [ %{~ for admin in admins ~} "${admin}@${domain}", %{~ endfor ~} ]
},
"acls": [
{ "action": "accept", "users": ["*"], "ports": ["*:*"] }
],
"tagOwners": {
"${tag}": ["group:admin", "${tag}"]
},
"autoApprovers": {
"routes": {
%{~ for route in routes ~}
"${route}": ["group:admin", "${tag}"],
%{~ endfor ~}
},
"exitNode": ["${tag}"]
},
%{~ if enable_ssh ~}
"ssh": [
{
"action": "check",
"src": ["autogroup:members"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot", "root"]
}
]
%{~ endif ~}
}

6
files/relay-init.sh.tftpl

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
#!/bin/bash
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.conf
sysctl -p /etc/sysctl.conf
curl -fsSL https://gist.githubusercontent.com/bdeshi/ba8fed1b5d357320d0314e8380c58454/raw/4978c0b60443e448607b59bc67c09f1dbbac9a56/tailscale-install.sh | sh
tailscale up %{ if length(routes) > 0 } --advertise-routes "${join(",", routes)}" %{ endif } --authkey "${auth_key}" --accept-dns=false

17
locals.tf

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
locals {
tailscale_auth_token = base64encode("${var.tailscale_api_key}:")
# list of cidr routes: cidrs of selected vpc + additional cidrs if defined
tailscale_routes = var.advertise_routes ? concat(
data.aws_vpc.selected.cidr_block_associations[*].cidr_block,
length(var.additional_routes) > 0 ? var.additional_routes : []
) : []
# list of vpc dns servers: each vpc cidr base + 2 & fallback_nameservers if defined
tailscale_nameservers = var.advertise_nameservers ? concat(
[for cidr_block in data.aws_vpc.selected.cidr_block_associations : cidrhost(cidr_block.cidr_block, 2)],
length(var.fallback_nameservers) > 0 ? var.fallback_nameservers : []
) : []
}

49
outputs.tf

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
output "relay_auth_key" {
value = "${tailscale_tailnet_key.relay_auth.id} | expires: ${jsondecode(data.http.relay_auth_key_response.response_body).expires}"
description = "tailscale relay auth key"
}
output "forwarded_routes" {
value = join(", ", local.tailscale_routes)
description = "forwarded routes"
}
output "forwarded_nameservers" {
value = join(", ", local.tailscale_nameservers)
description = "forwarded nameservers"
}
output "vpc_detail" {
value = "${var.vpc_id}%{for k, v in data.aws_vpc.selected.tags}%{if k == "Name"} | ${v}%{endif}%{endfor}"
description = "selected vpc"
}
output "subnet_detail" {
value = "${var.subnet_id}%{for k, v in data.aws_subnet.selected.tags}%{if k == "Name"} | ${v}%{endif}%{endfor} | ${data.aws_subnet.selected.cidr_block}"
description = "selected subnet"
}
output "security_group_detail" {
value = "${aws_security_group.tailscale.id} | ${aws_security_group.tailscale.name}"
description = "security group"
}
output "ami_detail" {
value = "${data.aws_ami.selected.id} | ${data.aws_ami.selected.name}"
description = "selected ami"
}
output "ec2_detail" {
value = "${aws_instance.tailscale.id} | ${var.relay_instance_type}"
description = "tailscale relay id"
}
output "ec2_ip" {
value = "${aws_instance.tailscale.private_ip}%{if aws_instance.tailscale.public_ip != ""}, ${aws_instance.tailscale.public_ip}%{endif}"
description = "tailscale relay ip"
}
output "ec2_ssh" {
value = var.relay_key_name == null ? "" : var.relay_key_name
description = "tailscale relay ssh"
}

24
tailscale-network.tf

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
# configures tailscale network to use the relay server.
resource "tailscale_acl" "default" {
acl = templatefile("${path.module}/files/acl.hujson.tftpl", {
admins = var.tailscale_admin_users
domain = var.tailscale_domain
tag = var.relay_tag
routes = local.tailscale_routes
enable_ssh = var.enable_tailscale_ssh
})
}
resource "tailscale_tailnet_key" "relay_auth" {
preauthorized = true
reusable = true
ephemeral = false
tags = [var.relay_tag]
depends_on = [tailscale_acl.default]
}
resource "tailscale_dns_nameservers" "vpc_dns" {
count = var.advertise_nameservers ? 1 : 0
nameservers = local.tailscale_nameservers
}

54
tailscale-server.tf

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
# deploys a tailscale relay server EC2 instance in AWS VPC.
# module "ec2_instance" {
# source = "terraform-aws-modules/ec2-instance/aws"
# version = "~> 3.0"
# create = true
# name = var.tailscale_relay_name
# ami =
#
# }
resource "aws_instance" "tailscale" {
ami = data.aws_ami.selected.id
instance_type = var.relay_instance_type
associate_public_ip_address = var.relay_associate_public_ip
key_name = var.relay_key_name
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.tailscale.id]
user_data = templatefile("${path.module}/files/relay-init.sh.tftpl", {
routes = local.tailscale_routes
auth_key = tailscale_tailnet_key.relay_auth.key
})
tags = {
Name = "tailscale"
}
}
resource "aws_security_group" "tailscale" {
name_prefix = "tailscale"
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = (var.relay_key_name == null || var.relay_key_name == "") ? [] : [1]
content {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
lifecycle {
create_before_destroy = true
}
}

41
terraform.tf

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
terraform {
required_version = "~> 1.2.0"
required_providers {
tailscale = {
source = "davidsbond/tailscale"
version = "~> 0.12.0"
}
aws = {
source = "hashicorp/aws"
version = "~> 4.26.0"
}
http = {
source = "hashicorp/http"
version = "~> 3.0.1"
}
# null = {
# source = "hashicorp/null"
# version = ">= 3.1.1"
# }
# time = {
# source = "hashicorp/time"
# version = "~> 0.8.0"
# }
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
ManagedBy = "terraform"
Component = "tailscale"
}
}
}
provider "tailscale" {
api_key = var.tailscale_api_key
tailnet = var.tailscale_domain
}

106
variables.tf

@ -0,0 +1,106 @@ @@ -0,0 +1,106 @@
variable "tailscale_domain" {
type = string
default = "example.net"
description = "The domain name of the tailscale network to manage."
}
variable "tailscale_admin_users" {
type = list(string)
default = ["admin"]
description = "usernames of the tailscale network's admins, minus the `@domain` part."
}
variable "tailscale_api_key" {
type = string
default = "tskey-XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXX"
sensitive = true
description = "The tailscale API key to use."
validation {
condition = can(regex("^tskey-", var.tailscale_api_key))
error_message = "The tailscale API key must start with `tskey-`"
}
}
variable "relay_tag" {
type = string
default = "tag:tailscale"
description = "The tag to use for the tailscale network's relay nodes."
validation {
condition = can(regex("^tag:\\w+", var.relay_tag))
error_message = "tailscale tags must start with `tag:` followed by a tag name."
}
}
variable "relay_instance_type" {
type = string
default = "t2.micro"
description = "The EC2 instance type to use for the relay server."
}
variable "relay_key_name" {
type = string
default = "default"
description = "The name of the pre-existing key pair to use for ssh access to the relay server."
}
variable "aws_region" {
type = string
default = "us-east-1"
description = "The AWS region to use."
}
variable "vpc_id" {
type = string
default = "vpc-XXXXXXXXXXXXXXXXXXXX"
description = "ID of the vpc to deploy tailscale relay to."
}
variable "subnet_id" {
type = string
default = "subnet-XXXXXXXXXXXXXXXXXXXX"
description = "ID of the subnet to attach tailscale relay to."
}
variable "additional_routes" {
type = list(string)
default = []
description = "The routes in addition to selected VPC's routes, to add to the tailscale network."
validation {
condition = length(var.additional_routes) == 0 ? true : alltrue([
for route in var.additional_routes :
regex("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}/\\d{1,2}$", route)
])
error_message = "routes must be in CIDR format."
}
}
variable "fallback_nameservers" {
type = list(string)
# default = ["169.254.169.253", "1.1.1.1", "1.0.0.1", "8.8.8.8", "8.8.4.4"]
default = ["1.1.1.1", "1.0.0.1", "8.8.8.8", "8.8.4.4"]
description = "additional nameservers to push to the tailscale network."
}
variable "advertise_nameservers" {
type = bool
default = true
description = "Whether to advertise the tailscale network's nameservers to clients."
}
variable "advertise_routes" {
type = bool
default = true
description = "Whether to advertise the tailscale server's subnet routes to clients."
}
variable "enable_tailscale_ssh" {
type = bool
default = true
description = "Whether to enable ssh-over-tailscale for tailscale network nodes."
}
variable "relay_associate_public_ip" {
type = bool
default = true
description = "Whether to associate a public IP address with the relay server."
}
Loading…
Cancel
Save