diff --git a/network/wireguard-as/README.md b/network/wireguard-as/README.md new file mode 100644 index 0000000..2b3ea0b --- /dev/null +++ b/network/wireguard-as/README.md @@ -0,0 +1,7 @@ +# WireGuard Access Server + +TODO + +## Helpful links + +https://fabianlee.org/2017/05/21/golang-running-a-go-binary-as-a-systemd-service-on-ubuntu-16-04/ diff --git a/network/wireguard-as/compute.tf b/network/wireguard-as/compute.tf new file mode 100644 index 0000000..bf3b4e2 --- /dev/null +++ b/network/wireguard-as/compute.tf @@ -0,0 +1,35 @@ +module "vpn_asg" { + source = "terraform-aws-modules/autoscaling/aws" + + name = "${var.prefix}-vpn" + image_id = data.aws_ami.ubuntu.image_id + instance_type = var.instance_type + security_groups = [aws_security_group.vpn.id] + iam_instance_profile = aws_iam_instance_profile.vpn.name + asg_name = "${var.prefix}-vpn" + lc_name = "${var.prefix}-vpn" + health_check_type = "EC2" + vpc_zone_identifier = var.public_subnets + min_size = 1 + max_size = 1 + desired_capacity = 1 + wait_for_capacity_timeout = 0 + default_cooldown = 120 + health_check_grace_period = 120 + key_name = var.key_name + + + user_data = templatefile("${path.module}/files/vpn_user_data.sh", { + EIP_ID = aws_eip.vpn.id + REGION = data.aws_region.current.name + CONFIG_BUCKET = aws_s3_bucket.configs.id + WIREGUARD_INTERFACE = var.wireguard_interface + WIREGUARD_PORT = var.wireguard_vpn_port + }) + + tags_as_map = { + "Name" = "${var.prefix}-vpn" + "App" = "WireGuard" + "Terraform" = "true" + } +} diff --git a/network/wireguard-as/files/vpn_user_data.sh b/network/wireguard-as/files/vpn_user_data.sh new file mode 100644 index 0000000..27f2879 --- /dev/null +++ b/network/wireguard-as/files/vpn_user_data.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +set -x + +# Elastic IP attachment +INSTANCE_ID=$(curl -s 169.254.169.254/latest/meta-data/instance-id) +aws ec2 associate-address --allocation-id ${EIP_ID} --instance-id $INSTANCE_ID --region ${REGION} + + +# Install WireGuard and other dependencies +apt-get install -y software-properties-common +add-apt-repository -y ppa:wireguard/wireguard +apt-get update +apt-get install -y "linux-headers-$(uname -r)" +apt-get install -y wireguard iptables resolvconf awscli git sudo + +# Initialization WireGuard configs +aws s3api head-object --bucket ${CONFIG_BUCKET} --key wg0.conf +if [[ "$?" -eq 0 ]]; then + echo "[+] Copying existing WireGuard config to system from s3://${CONFIG_BUCKET}" + aws s3 cp s3://${CONFIG_BUCKET}/wg0.conf /etc/wireguard/wg0.conf +else + echo "[+] Generating new WireGuard config" + wg genkey | tee /opt/privkey | wg pubkey > /opt/pubkey + cat << EOF > /etc/wireguard/wg0.conf +[Interface] +Address = ${WIREGUARD_INTERFACE} +ListenPort = ${WIREGUARD_PORT} +PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; +PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; +PrivateKey = $(cat /opt/privkey) +SaveConfig = true +EOF + aws s3 cp /etc/wireguard/wg0.conf s3://${CONFIG_BUCKET}/wg0.conf +fi + + +# Install Rust and app as a systemd service +sudo apt install build-essential -y + +cat << EOF > /opt/install_app.sh +#!/bin/bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | RUSTUP_HOME=~/.rustup sh -s -- -y +source ~/.cargo/env +git clone https://github.com/lalanza808/wgas-rs ~/wgas-rs +cd ~/wgas-rs +rustup override set nightly +cargo build --release +EOF +chmod +x /opt/install_app.sh +sudo -u ubuntu /opt/install_app.sh +useradd wgas-rs -s /sbin/nologin -M +cat << EOF > /lib/systemd/system/wgas-rs.service +[Unit] +Description=WireGuard Access Server Service +ConditionPathExists=/home/ubuntu/wgas-rs/target/release/wgas-rs +After=network.target + +[Service] +Type=simple +User=wgas-rs +Group=wgas-rs +LimitNOFILE=1024 + +Restart=on-failure +RestartSec=10 +startLimitIntervalSec=60 + +WorkingDirectory=/home/ubuntu/wgas-rs +ExecStart=/home/ubuntu/wgas-rs/target/release/wgas-rs + +# make sure log directory exists and owned by syslog +PermissionsStartOnly=true +ExecStartPre=/bin/mkdir -p /var/log/wgas-rs +ExecStartPre=/bin/chown syslog:adm /var/log/wgas-rs +ExecStartPre=/bin/chmod 755 /var/log/wgas-rs +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=wgas-rs + +[Install] +WantedBy=multi-user.target +EOF +chmod 755 /lib/systemd/system/wgas-rs.service +systemctl daemon-reload +systemctl enable wgas-rs +systemctl start wgas-rs diff --git a/network/wireguard-as/iam.tf b/network/wireguard-as/iam.tf new file mode 100644 index 0000000..16d0b07 --- /dev/null +++ b/network/wireguard-as/iam.tf @@ -0,0 +1,77 @@ +data "aws_iam_policy_document" "vpn_assume_role" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = [ + "ec2.amazonaws.com" + ] + } + } +} + +data "aws_iam_policy_document" "vpn" { + statement { + actions = [ + "route53:ListHostedZones", + "route53:GetChange" + ] + resources = [ + "*" + ] + } + statement { + actions = [ + "route53:ChangeResourceRecordSets" + ] + resources = [ + "arn:aws:route53:::hostedzone/${data.aws_route53_zone.domain.zone_id}" + ] + } + statement { + actions = [ + "ec2:AssociateAddress", + "ec2:DescribeInstances", + "ec2:DescribeNetworkInterfaces" + ] + resources = [ + "*" + ] + } + statement { + actions = [ + "s3:Put*", + "s3:Get*" + ] + resources = [ + "${aws_s3_bucket.configs.arn}/*" + ] + } +} + +resource "aws_iam_policy" "vpn" { + name_prefix = aws_iam_role.vpn.name + description = "WireGuard VPN server policy for managing resources on AWS" + policy = data.aws_iam_policy_document.vpn.json +} + +resource "aws_iam_role" "vpn" { + name_prefix = "${var.prefix}-vpn-" + assume_role_policy = data.aws_iam_policy_document.vpn_assume_role.json +} + +resource "aws_iam_role_policy_attachment" "vpn" { + role = aws_iam_role.vpn.name + policy_arn = aws_iam_policy.vpn.arn +} + +resource "aws_iam_role_policy_attachment" "ssm" { + role = aws_iam_role.vpn.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +resource "aws_iam_instance_profile" "vpn" { + name = aws_iam_role.vpn.name + role = aws_iam_role.vpn.name +} diff --git a/network/wireguard-as/meta.tf b/network/wireguard-as/meta.tf new file mode 100644 index 0000000..c6c176a --- /dev/null +++ b/network/wireguard-as/meta.tf @@ -0,0 +1,23 @@ +data "aws_route53_zone" "domain" { + name = var.domain_name +} + +data "aws_region" "current" {} + +data "aws_caller_identity" "current" {} + +data "aws_ami" "ubuntu" { + most_recent = true + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + owners = ["099720109477"] # Canonical +} diff --git a/network/wireguard-as/network.tf b/network/wireguard-as/network.tf new file mode 100644 index 0000000..ac4c6e7 --- /dev/null +++ b/network/wireguard-as/network.tf @@ -0,0 +1,63 @@ +resource "aws_security_group" "vpn" { + name_prefix = "${var.prefix}-vpn-" + description = "Allow connectivity to and from the WireGuard VPN instance." + vpc_id = var.vpc_id +} + +resource "aws_security_group_rule" "vpn_access" { + type = "ingress" + from_port = var.wireguard_vpn_port + to_port = var.wireguard_vpn_port + protocol = "udp" + cidr_blocks = var.vpn_access_cidrs + security_group_id = aws_security_group.vpn.id +} + +resource "aws_security_group_rule" "management_access_80" { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = var.management_access_cidrs + security_group_id = aws_security_group.vpn.id +} + +resource "aws_security_group_rule" "management_access_8000" { + type = "ingress" + from_port = 8000 + to_port = 8000 + protocol = "tcp" + cidr_blocks = var.management_access_cidrs + security_group_id = aws_security_group.vpn.id +} + +resource "aws_security_group_rule" "management_access_443" { + type = "ingress" + from_port = 443 + to_port = 443 + protocol = "tcp" + cidr_blocks = var.management_access_cidrs + security_group_id = aws_security_group.vpn.id +} + +resource "aws_security_group_rule" "management_access_22" { + type = "ingress" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = var.management_access_cidrs + security_group_id = aws_security_group.vpn.id +} + +resource "aws_security_group_rule" "vpn_egress" { + type = "egress" + from_port = 0 + to_port = 65535 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = aws_security_group.vpn.id +} + +resource "aws_eip" "vpn" { + vpc = true +} diff --git a/network/wireguard-as/storage.tf b/network/wireguard-as/storage.tf new file mode 100644 index 0000000..7d0bd70 --- /dev/null +++ b/network/wireguard-as/storage.tf @@ -0,0 +1,26 @@ +resource "aws_s3_bucket" "configs" { + bucket_prefix = "${var.prefix}-config-" + acl = "private" +} + +data "aws_iam_policy_document" "config_policy" { + statement { + actions = [ + "s3:*" + ] + principals { + type = "AWS" + identifiers = [ + aws_iam_role.vpn.arn + ] + } + resources = [ + "${aws_s3_bucket.configs.arn}/*" + ] + } +} + +resource "aws_s3_bucket_policy" "configs" { + bucket = aws_s3_bucket.configs.id + policy = data.aws_iam_policy_document.config_policy.json +} diff --git a/network/wireguard-as/variables.tf b/network/wireguard-as/variables.tf new file mode 100644 index 0000000..7d34dd3 --- /dev/null +++ b/network/wireguard-as/variables.tf @@ -0,0 +1,47 @@ +variable "prefix" { + default = "wireguard" + description = "String to use as a prefix when naming resources" +} + +variable "domain_name" { + description = "Domain name to use for setting up Route 53 records" +} + +variable "instance_type" { + default = "t3.small" + description = "EC2 instance type to provision for VPN server" +} + +variable "key_name" { + description = "Key pair to use for provisioning EC2 instance" +} + +variable "vpn_access_cidrs" { + default = ["0.0.0.0/0"] + description = "IP addresses which should be able to connect to the VPN" + type = list +} + +variable "management_access_cidrs" { + default = [] + description = "IP addresses which should be able to reach the administrative interfaces (web/ssh)" + type = list +} + +variable "wireguard_vpn_port" { + default = 51820 + description = "Port to use for WireGuard VPN (udp)" +} + +variable "vpc_id" { + description = "ID of the VPC to deploy network resources into" +} + +variable "public_subnets" { + description = "List of subnets for deploying WireGuard VPN servers into" +} + +variable "wireguard_interface" { + default = "10.66.66.1/24" + description = "VPN tunnel interface IP and CIDR" +}