diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 1105403..e28f130 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ terraform.rc .env export.zip .infracost +/.direnv +terraform/.terraform.lock.hcl diff --git a/flake.nix b/flake.nix index 03d2c98..a3b7004 100644 --- a/flake.nix +++ b/flake.nix @@ -26,6 +26,7 @@ terraform terraform-ls go-task + ssm-session-manager-plugin ]; }; } diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl deleted file mode 100644 index 2da3500..0000000 --- a/terraform/.terraform.lock.hcl +++ /dev/null @@ -1,43 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/aws" { - version = "5.86.0" - hashes = [ - "h1:dVxrQ67Ikqv/1/rfopK/wvCdETlUbQ6ZFuNOH+vEWqs=", - "zh:1587c6a0199dc33d066c13e1628bc0dd966d7d6740cb2007b636524a3ec99430", - "zh:15af46cc5bb43a37c24438cb3a36d44209a89d923ea4d4d631b56b1a89717b26", - "zh:166902101ac1cc8ec4f53e3bdcbab2eac7eb448b1c428c2e622adbf9ce1a679c", - "zh:284d116ac9d4a4de74cd1f52486f00e10bc400d9654f92a8990ea0093c43ff78", - "zh:4135e928f20d456172c8ab4ae3d4d8e411b6feddc94aaa1347c92469d52f1e61", - "zh:72b317d17182c3e0ee72f2851d25565d369cb6ee803b12adc9b6c6d3dbfca8d7", - "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", - "zh:9dd0e80964e215ff658b708be72ccda8a20f63af7eaebdd6f11eb0461633bb03", - "zh:a18e502c16b7b6b216b888eab9a5c66b1ed103847fce6985850e4fc9e364a3e8", - "zh:c239f12648d7f7bbadbf5db0b57aaa9429abe70b574975b581784b4f17b7ed79", - "zh:c5164ca8254b9973ee985a3841a4b1f776844c7dcbc112ab3a88a0096e7e2198", - "zh:d93ac58092c3fffc5ddc688b39721fbfacc353e8965001060a5a1ce934d97246", - "zh:e877f1be2ebe67a2d163b7488f47cff4c95aca9c541ddfa25ad16c6ecc98f6a8", - "zh:eb71af6dfdd2b5670b5b957397a576d6053587c75750c17acc105fb44ed806eb", - "zh:ff6aa4f88f8e789375391bc8c886c636fb3e4a45a3fd7dc291bca17c2b8d4184", - ] -} - -provider "registry.terraform.io/hashicorp/random" { - version = "3.6.3" - hashes = [ - "h1:Fnaec9vA8sZ8BXVlN3Xn9Jz3zghSETIKg7ch8oXhxno=", - "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", - "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", - "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", - "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", - "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", - "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", - "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", - "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", - "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", - "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", - ] -} diff --git a/terraform/lab-relay.tf b/terraform/lab-relay.tf new file mode 100644 index 0000000..3edccfc --- /dev/null +++ b/terraform/lab-relay.tf @@ -0,0 +1,168 @@ +resource "aws_eip" "lab_relay" { + instance = aws_instance.lab_relay.id + domain = "vpc" + depends_on = [aws_internet_gateway.default] + + tags = { + Name = "${local.name}-lab-relay" + } +} + +resource "aws_instance" "lab_relay" { + ami = data.aws_ami.minimal-arm64.id + availability_zone = local.availability_zone + instance_type = "t4g.nano" + subnet_id = aws_subnet.public.id + iam_instance_profile = aws_iam_instance_profile.ecs_instance.name + vpc_security_group_ids = [aws_security_group.lab_relay.id] + source_dest_check = false + associate_public_ip_address = true + user_data_replace_on_change = true + + root_block_device { + volume_type = "gp3" + volume_size = 4 + } + + user_data = base64encode(<<-INIT + #!/bin/bash + yum install -y amazon-ssm-agent + systemctl enable amazon-ssm-agent + systemctl start amazon-ssm-agent + + amazon-linux-extras install epel -y + yum install -y openvpn easy-rsa + + echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf + sysctl -p + + # Set up PKI using elliptic curve (avoids slow DH param generation) + EASYRSA_SRC=$(find /usr/share/easy-rsa -name "easyrsa" | head -1 | xargs dirname) + mkdir -p /etc/openvpn/easy-rsa + cp -r $EASYRSA_SRC/* /etc/openvpn/easy-rsa/ + cd /etc/openvpn/easy-rsa + + export EASYRSA_BATCH=1 + export EASYRSA_ALGO=ec + export EASYRSA_CURVE=prime256v1 + + ./easyrsa init-pki + ./easyrsa build-ca nopass + ./easyrsa build-server-full server nopass + ./easyrsa build-client-full client nopass + ./easyrsa build-client-full chr nopass + + # CCD for CHR: route lab subnets through CHR + mkdir -p /etc/openvpn/ccd + cat > /etc/openvpn/ccd/chr <<'CCD' + iroute 192.168.88.0 255.255.255.0 + iroute 192.168.1.0 255.255.255.0 + CCD + + # Server config + cat > /etc/openvpn/server/server.conf <<'CONF' + port 1194 + proto udp + dev tun + ca /etc/openvpn/easy-rsa/pki/ca.crt + cert /etc/openvpn/easy-rsa/pki/issued/server.crt + key /etc/openvpn/easy-rsa/pki/private/server.key + dh none + cipher AES-256-GCM + auth SHA256 + server 10.8.0.0 255.255.255.0 + client-config-dir /etc/openvpn/ccd + route 192.168.88.0 255.255.255.0 + route 192.168.1.0 255.255.255.0 + push "route 192.168.88.0 255.255.255.0" + push "route 192.168.1.0 255.255.255.0" + push "dhcp-option DNS 1.1.1.1" + keepalive 10 120 + persist-key + persist-tun + verb 3 + CONF + + # NAT for VPN clients + cat < /etc/systemd/system/openvpn-nat.service + [Unit] + Description=OpenVPN NAT + After=network.target + + [Service] + Type=oneshot + ExecStart=/usr/sbin/iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE + ExecStop=/usr/sbin/iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE + RemainAfterExit=yes + + [Install] + WantedBy=multi-user.target + EOF + + systemctl daemon-reload + systemctl enable openvpn-nat + systemctl start openvpn-nat + + systemctl enable openvpn-server@server + systemctl start openvpn-server@server + + # Build the shareable client config with all certs embedded + cat > /etc/openvpn/client.ovpn <<'OVPN' + client + dev tun + proto udp + remote vpn.${local.domain} 1194 + resolv-retry infinite + nobind + persist-key + persist-tun + remote-cert-tls server + cipher AES-256-GCM + auth SHA256 + verb 3 + + OVPN + cat /etc/openvpn/easy-rsa/pki/ca.crt >> /etc/openvpn/client.ovpn + echo "" >> /etc/openvpn/client.ovpn + echo "" >> /etc/openvpn/client.ovpn + sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' /etc/openvpn/easy-rsa/pki/issued/client.crt >> /etc/openvpn/client.ovpn + echo "" >> /etc/openvpn/client.ovpn + echo "" >> /etc/openvpn/client.ovpn + cat /etc/openvpn/easy-rsa/pki/private/client.key >> /etc/openvpn/client.ovpn + echo "" >> /etc/openvpn/client.ovpn + INIT + ) + + tags = { + Name = "${local.name}-lab-relay" + } +} + +resource "aws_security_group" "lab_relay" { + name = "${local.name}-lab-relay" + vpc_id = aws_vpc.default.id + + ingress { + from_port = 1194 + to_port = 1194 + protocol = "udp" + 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"] + } +} + +resource "aws_route53_record" "wireguard" { + zone_id = local.domain_zone_id + name = "vpn.${local.domain}" + type = "A" + ttl = 300 + records = [aws_eip.lab_relay.public_ip] +}