diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..397af32
--- /dev/null
+++ b/.gitignore
@@ -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
diff --git a/README.md b/README.md
index 699802d..e3b01e3 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,76 @@
-# terraform-aws-wireguard
\ No newline at end of file
+# Terraform AWS Wireguard
+
+This module creates EC2 instance with Wireguard inside.
+
+
+How to use:
+
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 0.13.5 |
+| [aws](#requirement\_aws) | ~> 3.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | ~> 3.0 |
+| [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 |
+|------|-------------|------|---------|:--------:|
+| [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 |
+| [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 |
+| [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 |
+| [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 |
+| [aws\_region](#input\_aws\_region) | n/a | `string` | n/a | yes |
+| [env](#input\_env) | The name of environment for Wireguard. | `any` | n/a | yes |
+| [instance\_type](#input\_instance\_type) | The machine type to launch, some machines may offer higher throughput for higher use cases. | `string` | `"t2.micro"` | no |
+| [route53\_geo](#input\_route53\_geo) | Route53 Geolocation config. | `any` | `null` | no |
+| [route53\_hosted\_zone\_id](#input\_route53\_hosted\_zone\_id) | Route53 Hosted zone ID. | `string` | `null` | no |
+| [route53\_record\_name](#input\_route53\_record\_name) | Route53 Record name. | `string` | `null` | no |
+| [ssh\_key\_id](#input\_ssh\_key\_id) | A SSH public key ID to add to the VPN instance. | `any` | n/a | yes |
+| [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 |
+| [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 |
+| [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 |
+| [use\_route53](#input\_use\_route53) | Whether to use SSM to store Wireguard Server private key. | `bool` | `false` | no |
+| [vpc\_id](#input\_vpc\_id) | The VPC ID in which Terraform will launch the resources. | `any` | n/a | yes |
+| [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 |
+| [wg\_persistent\_keepalive](#input\_wg\_persistent\_keepalive) | Persistent Keepalive - useful for helping connection stability over NATs. | `number` | `25` | no |
+| [wg\_server\_interface](#input\_wg\_server\_interface) | The default interface to forward network traffic to. | `string` | `"eth0"` | no |
+| [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 |
+| [wg\_server\_port](#input\_wg\_server\_port) | Port for the vpn server. | `number` | `51820` | no |
+| [wg\_server\_private\_key](#input\_wg\_server\_private\_key) | Wireguard server private key. | `string` | `null` | no |
+
+## Outputs
+
+No outputs
diff --git a/iam.tf b/iam.tf
new file mode 100644
index 0000000..293b362
--- /dev/null
+++ b/iam.tf
@@ -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
+}
diff --git a/main.tf b/main.tf
new file mode 100644
index 0000000..08d445e
--- /dev/null
+++ b/main.tf
@@ -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
+ },
+ ]
+}
diff --git a/sg.tf b/sg.tf
new file mode 100644
index 0000000..e0f4eb9
--- /dev/null
+++ b/sg.tf
@@ -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"]
+ }
+}
diff --git a/templates/client-data.tpl b/templates/client-data.tpl
new file mode 100644
index 0000000..7177777
--- /dev/null
+++ b/templates/client-data.tpl
@@ -0,0 +1,5 @@
+[Peer]
+# friendly_name = ${friendly_name}
+PublicKey = ${client_pub_key}
+AllowedIPs = ${client_ip}
+PersistentKeepalive = ${persistent_keepalive}
\ No newline at end of file
diff --git a/templates/user-data.txt b/templates/user-data.txt
new file mode 100644
index 0000000..6f9c30a
--- /dev/null
+++ b/templates/user-data.txt
@@ -0,0 +1,64 @@
+#!/bin/bash -v
+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" && \
+unzip awscliv2.zip && \
+rm -f awscliv2.zip && \
+./aws/install
+
+cat > /etc/wireguard/wg0.conf <<- EOF
+[Interface]
+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 wg-quick@wg0.service
+systemctl start wg-quick@wg0.service
+
+until systemctl is-active --quiet wg-quick@wg0.service
+do
+ sleep 1
+done
+
+ufw allow ssh
+ufw allow ${wg_server_port}/udp
+ufw --force enable
\ No newline at end of file
diff --git a/variables.tf b/variables.tf
new file mode 100644
index 0000000..6ab250d
--- /dev/null
+++ b/variables.tf
@@ -0,0 +1,112 @@
+variable "aws_region" {
+ type = string
+}
+
+variable "env" {
+ description = "The name of environment for Wireguard."
+}
+
+variable "ssh_key_id" {
+ description = "A SSH public key ID to add to the VPN instance."
+}
+
+variable "instance_type" {
+ default = "t2.micro"
+ description = "The machine type to launch, some machines may offer higher throughput for higher use cases."
+}
+
+variable "asg_min_size" {
+ default = 1
+ description = "We may want more than one machine in a scaling group, but 1 is recommended."
+}
+
+variable "asg_desired_capacity" {
+ default = 1
+ description = "We may want more than one machine in a scaling group, but 1 is recommended."
+}
+
+variable "asg_max_size" {
+ default = 1
+ description = "We may want more than one machine in a scaling group, but 1 is recommended."
+}
+
+variable "vpc_id" {
+ description = "The VPC ID in which Terraform will launch the resources."
+}
+
+variable "subnet_ids" {
+ type = list(string)
+ description = "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."
+}
+
+variable "wg_clients" {
+ type = list(object({ friendly_name = string, public_key = string, client_ip = string }))
+ description = "List of client objects with IP and public key. See Usage in README for details."
+}
+
+variable "wg_server_net" {
+ default = "10.0.0.1/24"
+ description = "IP range for vpn server - make sure your Client ips are in this range but not the specific ip i.e. not .1"
+}
+
+variable "wg_server_port" {
+ default = 51820
+ description = "Port for the vpn server."
+}
+
+variable "wg_persistent_keepalive" {
+ default = 25
+ description = "Persistent Keepalive - useful for helping connection stability over NATs."
+}
+
+variable "use_eip" {
+ type = bool
+ default = false
+ description = "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."
+}
+
+variable "target_group_arns" {
+ type = list(string)
+ default = null
+ description = "Running a scaling group behind an LB requires this variable, default null means it won't be included if not set."
+}
+
+variable "wg_server_private_key" {
+ type = string
+ default = null
+ description = "Wireguard server private key."
+}
+
+variable "ami_id" {
+ default = null # we check for this and use a data provider since we can't use it here
+ description = "The AWS AMI to use for the Wireguard server, defaults to the latest Ubuntu 20.04 AMI if not specified."
+}
+
+variable "wg_server_interface" {
+ default = "eth0"
+ description = "The default interface to forward network traffic to."
+}
+
+variable "use_route53" {
+ type = bool
+ default = false
+ description = "Whether to use SSM to store Wireguard Server private key."
+}
+
+variable "route53_hosted_zone_id" {
+ type = string
+ default = null
+ description = "Route53 Hosted zone ID."
+}
+
+variable "route53_record_name" {
+ type = string
+ default = null
+ description = "Route53 Record name."
+}
+
+variable "route53_geo" {
+ type = any
+ default = null
+ description = "Route53 Geolocation config."
+}
diff --git a/versions.tf b/versions.tf
new file mode 100644
index 0000000..e4e790f
--- /dev/null
+++ b/versions.tf
@@ -0,0 +1,16 @@
+terraform {
+ required_version = ">= 0.13.5"
+
+ backend "s3" {}
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 3.0"
+ }
+ }
+}
+
+provider "aws" {
+ region = var.aws_region
+}