Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Before using this module, you'll need to generate a key pair for your server and
| Variable Name | Type | Required | Description |
|---------------------------------|----------------------------|-----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `subnet_ids` | `list` | Yes | A list of subnets for the Autoscaling Group to use for launching instances. May be a single subnet, but it must be an element in a list. |
| `ssh_key_id` | `string` | Yes | A SSH public key ID to add to the VPN instance. |
| `vpc_id` | `string` | Yes | The VPC ID in which Terraform will launch the resources. |
| `ssh_key_id` | `string` | Optional | A SSH public key ID to add to the VPN instance. |
| `env` | `string` | Optional - defaults to `prod` | The name of environment for WireGuard. Used to differentiate multiple deployments. |
| `use_eip` | `bool` | Optional | Whether to attach an [Elastic IP](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html) address to the VPN server. Useful for avoiding changing IPs. |
| `eip_id` | `string` | Optional | When `use_eip` is enabled, specify the ID of the Elastic IP to which the VPN server will attach. |
Expand All @@ -36,10 +36,12 @@ Before using this module, you'll need to generate a key pair for your server and
| `wg_server_private_key_param` | `string` | Optional - defaults to `/wireguard/wg-server-private-key` | The Parameter Store key to use for the VPN server Private Key. |
| `ami_id` | `string` | Optional - defaults to `null` | AMI to use for the VPN server. Determined automatically if not specified. |
| `ami_prefix` | `string` | Optional - defaults to `ubuntu/images/hvm-ssd/ubuntu` | Prefix to look for in AMI name when automatically choosing an image. |
| `ami_release` | `string` | Optional - defaults to `focal-20.04` | OS release to look for in AMI name when automatically choosing an image. |
| `ami_arch` | `string` | Optional - defaults to `amd64` | Architecture to look for in AMI name when automatically choosing an image. Ensure this is appropriate for your chosen instance_type. |
| `ami_release` | `string` | Optional - defaults to `jammy-22.04` | OS release to look for in AMI name when automatically choosing an image. |
| `ami_arch` | `string` | Optional - defaults to `arm64` | Architecture to look for in AMI name when automatically choosing an image. Ensure this is appropriate for your chosen instance_type. |
| `ami_owner_id` | `string` | Optional - defaults to `099720109477` (amazon) | Look for an AMI with this owner account ID when automatically choosing an image. |
| `wg_server_interface` | `string` | Optional - defaults to eth0 | Server interface to route traffic to for installations forwarding traffic to private networks. |
| `wg_server_interface` | `string` | Optional | Server interface to route traffic to for installations forwarding traffic to private networks. |
| `install_ssm` | `bool` | Optional - defaults to true | Install AWS Session Manager repository and package. Attach the necessary policy `arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM` to the EC2 Instance Role. |
| `wg_allowed_cidr_blocks` | `list(string)` | Optional - defaults to ["0.0.0.0/0"] | Defines IP ranges WireGuard clients can access, limiting full internet access if desired. |

## Examples

Expand Down
110 changes: 63 additions & 47 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,11 @@ terraform {
aws = {
source = "hashicorp/aws"
}
template = {
source = "hashicorp/template"
}
}
}

data "template_file" "user_data" {
template = file("${path.module}/templates/user-data.txt")

vars = {
wg_server_private_key = data.aws_ssm_parameter.wg_server_private_key.value
wg_server_net = var.wg_server_net
wg_server_port = var.wg_server_port
peers = join("\n", data.template_file.wg_client_data_json.*.rendered)
use_eip = var.use_eip ? "enabled" : "disabled"
eip_id = var.eip_id
wg_server_interface = var.wg_server_interface
}
}

data "template_file" "wg_client_data_json" {
template = file("${path.module}/templates/client-data.tpl")
count = length(var.wg_clients)

vars = {
client_name = var.wg_clients[count.index].name
client_pub_key = var.wg_clients[count.index].public_key
client_ip = var.wg_clients[count.index].client_ip
persistent_keepalive = var.wg_persistent_keepalive
}
}

# Automatically find the latest version of our operating system image (e.g. Ubuntu)
data "aws_ami" "os" {
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
Expand All @@ -61,24 +32,59 @@ locals {
security_groups_ids = compact(concat(var.additional_security_group_ids, local.sg_wireguard_external))
}

resource "aws_launch_configuration" "wireguard_launch_config" {
name_prefix = "wireguard-${var.env}-"
image_id = var.ami_id != null ? var.ami_id : data.aws_ami.os.id
instance_type = var.instance_type
key_name = var.ssh_key_id
iam_instance_profile = (var.use_eip ? aws_iam_instance_profile.wireguard_profile[0].name : null)
user_data = data.template_file.user_data.rendered
security_groups = local.security_groups_ids
associate_public_ip_address = var.use_eip
locals {
launch_name_prefix = "wireguard-${var.env}-"
wg_client_data = templatefile("${path.module}/templates/client-data.tftpl", {
users = var.wg_clients,
persistent_keepalive = var.wg_persistent_keepalive
})
}

lifecycle {
create_before_destroy = true
resource "aws_launch_template" "wireguard_launch_config" {
name_prefix = local.launch_name_prefix
image_id = var.ami_id == null ? data.aws_ami.ubuntu.id : var.ami_id
instance_type = var.instance_type
key_name = var.ssh_key_id
iam_instance_profile {
arn = aws_iam_instance_profile.wireguard_profile.arn
}

metadata_options {
http_tokens = "required"
}

user_data = base64encode(templatefile("${path.module}/templates/user-data.tftpl", {
wg_server_private_key_param = var.wg_server_private_key_param
wg_server_net = var.wg_server_net
wg_server_port = var.wg_server_port
peers = local.wg_client_data
use_eip = var.use_eip ? "enabled" : "disabled"
install_ssm = var.install_ssm ? "enabled" : "disabled"
eip_id = var.eip_id
wg_server_interface = var.wg_server_interface
arch = var.ami_arch
wg_allowed_cidr_blocks = join(" ", var.wg_allowed_cidr_blocks)
}))

network_interfaces {
associate_public_ip_address = var.use_eip
security_groups = local.security_groups_ids
}

tag_specifications {
resource_type = "instance"

tags = {
launch-template-name = local.launch_name_prefix
project = "wireguard"
env = var.env
tf-managed = "True"
}
}
}

resource "aws_autoscaling_group" "wireguard_asg" {
name = aws_launch_configuration.wireguard_launch_config.name
launch_configuration = aws_launch_configuration.wireguard_launch_config.name
name = aws_launch_template.wireguard_launch_config.name
min_size = var.asg_min_size
desired_capacity = var.asg_desired_capacity
max_size = var.asg_max_size
Expand All @@ -87,19 +93,29 @@ resource "aws_autoscaling_group" "wireguard_asg" {
termination_policies = ["OldestLaunchConfiguration", "OldestInstance"]
target_group_arns = var.target_group_arns

launch_template {
id = aws_launch_template.wireguard_launch_config.id
version = aws_launch_template.wireguard_launch_config.latest_version
}

lifecycle {
create_before_destroy = true
}

instance_refresh {
strategy = "Rolling"
}

tag {
key = "Name"
value = aws_launch_configuration.wireguard_launch_config.name
key = "Name"
value = aws_launch_template.wireguard_launch_config.name
propagate_at_launch = true
}

tag {
key = "env"
value = var.env
key = "env"
value = var.env
propagate_at_launch = true
}
}

6 changes: 6 additions & 0 deletions templates/client-data.tftpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
%{ for user in users ~}
[Peer]
PublicKey = ${user.public_key}
AllowedIPs = ${user.client_ip}
PersistentKeepalive = ${persistent_keepalive}
%{ endfor ~}
4 changes: 0 additions & 4 deletions templates/client-data.tpl

This file was deleted.

66 changes: 66 additions & 0 deletions templates/user-data.tftpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/bin/bash -v
apt-get update -y
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -o Dpkg::Options::="--force-confnew"
apt-get install -y wireguard-tools awscli

# Find interface if not defined
if [ -z "${wg_server_interface}" ]; then
INTERFACE=$(ip -o -4 route show to default | awk '{print $5}')
else
INTERFACE="${wg_server_interface}"
fi

TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600" -s)
REGION=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -fsq http://169.254.169.254/latest/meta-data/placement/availability-zone | sed 's/[a-z]$//')
PRIVATE_KEY=$(
aws ssm get-parameter --name ${wg_server_private_key_param} \
--region $${REGION} --query='Parameter.Value' \
--output=text --with-decryption
)

read -ra wg_allowed_cidr_blocks <<< "${wg_allowed_cidr_blocks}"

POST_UP_RULES=""
POST_DOWN_RULES=""
for CIDR in "$${wg_allowed_cidr_blocks[@]}"; do
POST_UP_RULES+="iptables -t nat -A POSTROUTING -o $INTERFACE -d $CIDR -j MASQUERADE; "
POST_DOWN_RULES+="iptables -t nat -D POSTROUTING -o $INTERFACE -d $CIDR -j MASQUERADE; "
done

cat > /etc/wireguard/wg0.conf <<- EOF
[Interface]
Address = ${wg_server_net}
PrivateKey = $${PRIVATE_KEY}
ListenPort = ${wg_server_port}
PostUp = $${POST_UP_RULES} iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT
PostDown = $${POST_DOWN_RULES} iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT

${peers}
EOF

# we go with the eip if it is provided
if [ "${use_eip}" != "disabled" ]; then
INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/instance-id)
aws --region $${REGION} ec2 associate-address --allocation-id ${eip_id} --instance-id $${INSTANCE_ID}
fi

# Install the ssm if it is enabled (installed by default on Ubuntu Server22.04
# LTS, 20.10 STR & 20.04, 18.04, and 16.04 LTS (with Snap))
if [ "${install_ssm}" = "enabled" ]; then
# https://docs.aws.amazon.com/systems-manager/latest/userguide/agent-install-ubuntu-64-snap.html
systemctl enable snap.amazon-ssm-agent.amazon-ssm-agent.service
systemctl start snap.amazon-ssm-agent.amazon-ssm-agent.service
fi

# reduce MTU to prevent packet fragmentation with NAT
ip link set dev $${INTERFACE} mtu 1500

chown -R root:root /etc/wireguard/
chmod -R og-rwx /etc/wireguard/*
sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /etc/sysctl.conf
sysctl -p
ufw allow ssh
ufw allow ${wg_server_port}/udp
ufw --force enable
systemctl enable [email protected]
systemctl start [email protected]
35 changes: 0 additions & 35 deletions templates/user-data.txt

This file was deleted.

Loading