Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terraform AWS Wireguard module #1

Merged
merged 3 commits into from
Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
29 changes: 29 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Local .terraform directories
**/.terraform/*

# Terraform lockfile
.terraform.lock.hcl

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log

# Exclude all .tfvars files, which are likely to contain sentitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Ignore CLI configuration files
.terraformrc
terraform.rc
77 changes: 76 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,76 @@
# terraform-aws-wireguard
# Terraform AWS Wireguard

This module creates EC2 instance with Wireguard inside.


How to use:



## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.13.5 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.0 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 3.0 |
| <a name="provider_template"></a> [template](#provider\_template) | n/a |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_autoscaling_group.wireguard_asg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | resource |
| [aws_eip.wireguard](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource |
| [aws_iam_instance_profile.wireguard_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource |
| [aws_iam_policy.wireguard_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.wireguard_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.wireguard_roleattach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_launch_configuration.wireguard_launch_config](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_configuration) | resource |
| [aws_route53_record.wireguard](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource |
| [aws_security_group.sg_wireguard](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
| [aws_ami.ubuntu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.ec2_assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.wireguard_policy_doc](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [template_file.wg_client_data_json](https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_ami_id"></a> [ami\_id](#input\_ami\_id) | The AWS AMI to use for the Wireguard server, defaults to the latest Ubuntu 20.04 AMI if not specified. | `any` | `null` | no |
| <a name="input_asg_desired_capacity"></a> [asg\_desired\_capacity](#input\_asg\_desired\_capacity) | We may want more than one machine in a scaling group, but 1 is recommended. | `number` | `1` | no |
| <a name="input_asg_max_size"></a> [asg\_max\_size](#input\_asg\_max\_size) | We may want more than one machine in a scaling group, but 1 is recommended. | `number` | `1` | no |
| <a name="input_asg_min_size"></a> [asg\_min\_size](#input\_asg\_min\_size) | We may want more than one machine in a scaling group, but 1 is recommended. | `number` | `1` | no |
| <a name="input_aws_region"></a> [aws\_region](#input\_aws\_region) | n/a | `string` | n/a | yes |
| <a name="input_env"></a> [env](#input\_env) | The name of environment for Wireguard. | `any` | n/a | yes |
| <a name="input_instance_type"></a> [instance\_type](#input\_instance\_type) | The machine type to launch, some machines may offer higher throughput for higher use cases. | `string` | `"t2.micro"` | no |
| <a name="input_route53_geo"></a> [route53\_geo](#input\_route53\_geo) | Route53 Geolocation config. | `any` | `null` | no |
| <a name="input_route53_hosted_zone_id"></a> [route53\_hosted\_zone\_id](#input\_route53\_hosted\_zone\_id) | Route53 Hosted zone ID. | `string` | `null` | no |
| <a name="input_route53_record_name"></a> [route53\_record\_name](#input\_route53\_record\_name) | Route53 Record name. | `string` | `null` | no |
| <a name="input_ssh_key_id"></a> [ssh\_key\_id](#input\_ssh\_key\_id) | A SSH public key ID to add to the VPN instance. | `any` | n/a | yes |
| <a name="input_subnet_ids"></a> [subnet\_ids](#input\_subnet\_ids) | 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. | `list(string)` | n/a | yes |
| <a name="input_target_group_arns"></a> [target\_group\_arns](#input\_target\_group\_arns) | Running a scaling group behind an LB requires this variable, default null means it won't be included if not set. | `list(string)` | `null` | no |
| <a name="input_use_eip"></a> [use\_eip](#input\_use\_eip) | Whether to enable Elastic IP switching code in user-data on wg server startup. If true, eip\_id must also be set to the ID of the Elastic IP. | `bool` | `false` | no |
| <a name="input_use_route53"></a> [use\_route53](#input\_use\_route53) | Whether to use SSM to store Wireguard Server private key. | `bool` | `false` | no |
| <a name="input_vpc_id"></a> [vpc\_id](#input\_vpc\_id) | The VPC ID in which Terraform will launch the resources. | `any` | n/a | yes |
| <a name="input_wg_clients"></a> [wg\_clients](#input\_wg\_clients) | List of client objects with IP and public key. See Usage in README for details. | `list(object({ friendly_name = string, public_key = string, client_ip = string }))` | n/a | yes |
| <a name="input_wg_persistent_keepalive"></a> [wg\_persistent\_keepalive](#input\_wg\_persistent\_keepalive) | Persistent Keepalive - useful for helping connection stability over NATs. | `number` | `25` | no |
| <a name="input_wg_server_interface"></a> [wg\_server\_interface](#input\_wg\_server\_interface) | The default interface to forward network traffic to. | `string` | `"eth0"` | no |
| <a name="input_wg_server_net"></a> [wg\_server\_net](#input\_wg\_server\_net) | IP range for vpn server - make sure your Client ips are in this range but not the specific ip i.e. not .1 | `string` | `"10.0.0.1/24"` | no |
| <a name="input_wg_server_port"></a> [wg\_server\_port](#input\_wg\_server\_port) | Port for the vpn server. | `number` | `51820` | no |
| <a name="input_wg_server_private_key"></a> [wg\_server\_private\_key](#input\_wg\_server\_private\_key) | Wireguard server private key. | `string` | `null` | no |

## Outputs

No outputs
52 changes: 52 additions & 0 deletions iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
data "aws_iam_policy_document" "ec2_assume_role" {
statement {
actions = [
"sts:AssumeRole"
]

principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}

data "aws_iam_policy_document" "wireguard_policy_doc" {
statement {
actions = [
"ec2:AssociateAddress",
"ssm:GetParameter"
]

resources = ["*"]
}
}

data "aws_caller_identity" "current" {}

resource "aws_iam_policy" "wireguard_policy" {
name = "${var.env}-${var.aws_region}-tf-wireguard"
description = "Terraform Managed. Allows Wireguard instance to attach EIP."
policy = data.aws_iam_policy_document.wireguard_policy_doc.json
count = (var.use_eip ? 1 : 0) # only used for EIP mode
}

resource "aws_iam_role" "wireguard_role" {
name = "${var.env}-${var.aws_region}-tf-wireguard"
description = "Terraform Managed. Role to allow Wireguard instance to attach EIP."
path = "/"
assume_role_policy = data.aws_iam_policy_document.ec2_assume_role.json
count = (var.use_eip ? 1 : 0) # only used for EIP mode
}

resource "aws_iam_role_policy_attachment" "wireguard_roleattach" {
role = aws_iam_role.wireguard_role[0].name
policy_arn = aws_iam_policy.wireguard_policy[0].arn
count = (var.use_eip ? 1 : 0) # only used for EIP mode
}

resource "aws_iam_instance_profile" "wireguard_profile" {
name = "${var.env}-${var.aws_region}-tf-wireguard"
role = aws_iam_role.wireguard_role[0].name
count = (var.use_eip ? 1 : 0) # only used for EIP mode
}
112 changes: 112 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
resource "aws_eip" "wireguard" {
vpc = true
tags = {
Name = "${var.env}-wireguard"
}
}

resource "aws_route53_record" "wireguard" {
count = var.use_route53 ? 1 : 0
allow_overwrite = true
set_identifier = "${var.env}-${var.aws_region}-wireguard"
zone_id = var.route53_hosted_zone_id
name = var.route53_record_name
type = "A"
ttl = "60"
records = [aws_eip.wireguard.public_ip]

dynamic "geolocation_routing_policy" {
for_each = try(length(var.route53_geo.policy) > 0 ? var.route53_geo.policy : tomap(false), {})

content {
continent = geolocation_routing_policy.value.continent
}
}
}

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

vars = {
friendly_name = var.wg_clients[count.index].friendly_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
}
}

data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}

resource "aws_launch_configuration" "wireguard_launch_config" {
name_prefix = "${var.env}-wireguard-"
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 = (var.use_eip ? aws_iam_instance_profile.wireguard_profile[0].name : null)
user_data = templatefile("${path.module}/templates/user-data.txt", {
wg_server_private_key = var.wg_server_private_key,
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 = aws_eip.wireguard.id,
wg_server_interface = var.wg_server_interface
})
security_groups = [aws_security_group.sg_wireguard.id]
associate_public_ip_address = var.use_eip

lifecycle {
create_before_destroy = true
}
}

resource "aws_autoscaling_group" "wireguard_asg" {
name = aws_launch_configuration.wireguard_launch_config.name
launch_configuration = aws_launch_configuration.wireguard_launch_config.name
min_size = var.asg_min_size
desired_capacity = var.asg_desired_capacity
max_size = var.asg_max_size
vpc_zone_identifier = var.subnet_ids
health_check_type = "EC2"
termination_policies = ["OldestLaunchConfiguration", "OldestInstance"]
target_group_arns = var.target_group_arns

lifecycle {
create_before_destroy = true
}

tags = [
{
key = "Name"
value = aws_launch_configuration.wireguard_launch_config.name
propagate_at_launch = true
},
{
key = "Project"
value = "wireguard"
propagate_at_launch = true
},
{
key = "env"
value = var.env
propagate_at_launch = true
},
{
key = "tf-managed"
value = "True"
propagate_at_launch = true
},
]
}
33 changes: 33 additions & 0 deletions sg.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
resource "aws_security_group" "sg_wireguard" {
name = "${var.env}-${var.aws_region}-wireguard"
description = "Terraform Managed. Allow Wireguard client traffic from internet."
vpc_id = var.vpc_id

tags = {
Name = "${var.env}-${var.aws_region}-wireguard"
Project = "wireguard"
tf-managed = "True"
env = var.env
}

ingress {
from_port = var.wg_server_port
to_port = var.wg_server_port
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"]
}

ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
5 changes: 5 additions & 0 deletions templates/client-data.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[Peer]
# friendly_name = ${friendly_name}
kobrikx marked this conversation as resolved.
Show resolved Hide resolved
PublicKey = ${client_pub_key}
AllowedIPs = ${client_ip}
kobrikx marked this conversation as resolved.
Show resolved Hide resolved
PersistentKeepalive = ${persistent_keepalive}
64 changes: 64 additions & 0 deletions templates/user-data.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/bash -v
kobrikx marked this conversation as resolved.
Show resolved Hide resolved
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -o Dpkg::Options::="--force-confnew"
apt-get install -y \
apt-transport-https \
ca-certificates \
build-essential \
software-properties-common \
unzip \
curl \
wget \
gnupg \
net-tools \
jq \
wireguard-dkms \
wireguard-tools && \
rm -rf /var/lib/apt/lists/*

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
kobrikx marked this conversation as resolved.
Show resolved Hide resolved
unzip awscliv2.zip && \
rm -f awscliv2.zip && \
./aws/install

cat > /etc/wireguard/wg0.conf <<- EOF
[Interface]
kobrikx marked this conversation as resolved.
Show resolved Hide resolved
Address = ${wg_server_net}
PrivateKey = ${wg_server_private_key}
ListenPort = ${wg_server_port}
PostUp = sysctl -w -q net.ipv4.ip_forward=1
PostUp = iptables -P FORWARD DROP
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o ENI -j MASQUERADE
PostDown = sysctl -w -q net.ipv4.ip_forward=0
PostDown = iptables -P FORWARD ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o ENI -j MASQUERADE

${peers}
EOF

export ENI=$(ip route get 8.8.8.8 | grep 8.8.8.8 | awk '{print $5}')
sed -i "s/ENI/$ENI/g" /etc/wireguard/wg0.conf

# Use EIP if it is provided
if [ "${use_eip}" != "disabled" ]; then
export INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
export REGION=$(curl -fsq http://169.254.169.254/latest/meta-data/placement/availability-zone | sed 's/[a-z]$//')
aws --region $${REGION} ec2 associate-address --allocation-id ${eip_id} --instance-id $${INSTANCE_ID}
fi

chown -R root:root /etc/wireguard/
chmod -R og-rwx /etc/wireguard/*
sysctl -p
systemctl enable [email protected]
systemctl start [email protected]

until systemctl is-active --quiet [email protected]
do
sleep 1
done

ufw allow ssh
ufw allow ${wg_server_port}/udp
ufw --force enable
Loading