commit 3fcd038fdfa6b705709101173a5ea6e8bc2bb9a2 Author: Héctor Molinero Fernández Date: Sun Jan 26 11:59:13 2020 +0100 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c7778d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +**/packer_cache/* +**/packer_output/* +**/.terraform/* +*.tfstate +*.tfstate.* +override.tf +override.tf.json +*_override.tf +*_override.tf.json +crash.log diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8b9f709 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.associations": { + "meta-data": "yaml", + "user-data": "yaml" + } +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..2282cbb --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) +===================== + +Copyright © 2020 Héctor Molinero Fernández + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5cedc53 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# WireGuard + Unbound + +[WireGuard](https://www.wireguard.com) and [Unbound](https://unbound.net) setup with +[Packer](https://www.packer.io) and [Terraform](https://www.terraform.io) ready for deployment in +[Hetzner Cloud](https://www.hetzner.com). diff --git a/http/seed/meta-data b/http/seed/meta-data new file mode 100644 index 0000000..2fd109e --- /dev/null +++ b/http/seed/meta-data @@ -0,0 +1,2 @@ +instance-id: "instance0" +local-hostname: "localhost" diff --git a/http/seed/user-data b/http/seed/user-data new file mode 100644 index 0000000..0513619 --- /dev/null +++ b/http/seed/user-data @@ -0,0 +1,9 @@ +#cloud-config + +ssh_pwauth: true +disable_root: false +chpasswd: { list: ["root:toor"], expire: false } + +runcmd: + - printf '%s\n' 'PermitRootLogin yes' >> /etc/ssh/sshd_config + - systemctl restart ssh.service diff --git a/rootfs/etc/apt/apt.conf.d/20auto-upgrades b/rootfs/etc/apt/apt.conf.d/20auto-upgrades new file mode 100644 index 0000000..ed820d9 --- /dev/null +++ b/rootfs/etc/apt/apt.conf.d/20auto-upgrades @@ -0,0 +1,4 @@ +APT::Periodic::Enable "1"; +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Unattended-Upgrade "1"; +APT::Periodic::AutocleanInterval "7"; diff --git a/rootfs/etc/apt/apt.conf.d/50unattended-upgrades b/rootfs/etc/apt/apt.conf.d/50unattended-upgrades new file mode 100644 index 0000000..3092c04 --- /dev/null +++ b/rootfs/etc/apt/apt.conf.d/50unattended-upgrades @@ -0,0 +1,9 @@ +Unattended-Upgrade::Origins-Pattern { "origin=*"; }; +Unattended-Upgrade::AutoFixInterruptedDpkg "true"; +Unattended-Upgrade::MinimalSteps "true"; +Unattended-Upgrade::InstallOnShutdown "false"; +Unattended-Upgrade::Mail "root"; +Unattended-Upgrade::MailOnlyOnError "false"; +Unattended-Upgrade::Remove-Unused-Dependencies "true"; +Unattended-Upgrade::Automatic-Reboot "true"; +Unattended-Upgrade::Automatic-Reboot-Time "05:30"; diff --git a/rootfs/etc/fail2ban/jail.d/sshd.conf b/rootfs/etc/fail2ban/jail.d/sshd.conf new file mode 100644 index 0000000..9db1af0 --- /dev/null +++ b/rootfs/etc/fail2ban/jail.d/sshd.conf @@ -0,0 +1,9 @@ +[sshd] +enabled = true +filter = sshd +banaction = ufw +backend = systemd +maxretry = 5 +findtime = 10m +bantime = 10m +ignoreip = 127.0.0.1/8 ::1 diff --git a/rootfs/etc/ssh/sshd_config b/rootfs/etc/ssh/sshd_config new file mode 100644 index 0000000..6b959a9 --- /dev/null +++ b/rootfs/etc/ssh/sshd_config @@ -0,0 +1,26 @@ +Protocol 2 +HostKey /etc/ssh/ssh_host_ed25519_key +HostKey /etc/ssh/ssh_host_rsa_key +ListenAddress 0.0.0.0 +Port 22 +UseDNS no +UsePAM yes +X11Forwarding no +AllowTcpForwarding yes +AllowGroups ssh-user +PermitRootLogin without-password +PermitEmptyPasswords no +PermitUserEnvironment no +PubkeyAuthentication yes +PasswordAuthentication no +ChallengeResponseAuthentication no +GSSAPIAuthentication no +Subsystem sftp internal-sftp +LoginGraceTime 30 +TCPKeepAlive yes +ClientAliveInterval 60 +ClientAliveCountMax 5 +PrintMotd yes +PrintLastLog yes +SyslogFacility AUTH +LogLevel INFO diff --git a/rootfs/etc/unbound/unbound.conf b/rootfs/etc/unbound/unbound.conf new file mode 100644 index 0000000..31b7db3 --- /dev/null +++ b/rootfs/etc/unbound/unbound.conf @@ -0,0 +1,38 @@ +server: + interface: 0.0.0.0 + port: 53 + root-hints: "/usr/share/dns/root.hints" + auto-trust-anchor-file: "/var/lib/unbound/root.key" + access-control: 0.0.0.0/0 refuse + access-control: 127.0.0.0/8 allow + access-control: 10.10.10.0/24 allow + private-address: 0.0.0.0/8 + private-address: ::ffff:0.0.0.0/104 + private-address: 10.0.0.0/8 + private-address: ::ffff:10.0.0.0/104 + private-address: 100.64.0.0/10 + private-address: ::ffff:100.64.0.0/106 + private-address: 127.0.0.0/8 + private-address: ::ffff:127.0.0.0/104 + private-address: 169.254.0.0/16 + private-address: ::ffff:169.254.0.0/112 + private-address: 172.16.0.0/12 + private-address: ::ffff:172.16.0.0/108 + private-address: 192.168.0.0/16 + private-address: ::ffff:192.168.0.0/112 + private-address: ::/128 + private-address: ::1/128 + private-address: fc00::/7 + private-address: fd00::/8 + private-address: fe80::/10 + hide-identity: yes + hide-version: yes + qname-minimisation: yes + cache-min-ttl: 60 + cache-max-ttl: 3600 + prefetch: yes + prefetch-key: yes + verbosity: 1 + val-log-level: 1 + +#include: "/etc/unbound/unbound.conf.d/*.conf" diff --git a/rootfs/etc/wireguard/client-sample.conf b/rootfs/etc/wireguard/client-sample.conf new file mode 100644 index 0000000..d53b470 --- /dev/null +++ b/rootfs/etc/wireguard/client-sample.conf @@ -0,0 +1,9 @@ +[Interface] +PrivateKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +Address = 10.10.10.2/32 +DNS = 10.10.10.1 + +[Peer] +PublicKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +AllowedIPs = 0.0.0.0/0, ::/0 +Endpoint = XXX.XXX.XXX.XXX:80 diff --git a/rootfs/etc/wireguard/wg0-peers.conf b/rootfs/etc/wireguard/wg0-peers.conf new file mode 100644 index 0000000..f506490 --- /dev/null +++ b/rootfs/etc/wireguard/wg0-peers.conf @@ -0,0 +1,3 @@ +#[Peer] +#PublicKey = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +#AllowedIPs = 10.10.10.2/32 diff --git a/rootfs/etc/wireguard/wg0.conf b/rootfs/etc/wireguard/wg0.conf new file mode 100644 index 0000000..3c3a2fe --- /dev/null +++ b/rootfs/etc/wireguard/wg0.conf @@ -0,0 +1,25 @@ +[Interface] +Address = 10.10.10.1/24 +ListenPort = 80 +# Load keys +PostUp = [ -e '/etc/wireguard/%i-privatekey' ] || (umask 077 && wg genkey > '/etc/wireguard/%i-privatekey') +PostUp = [ -e '/etc/wireguard/%i-publickey' ] || (umask 022 && wg pubkey < '/etc/wireguard/%i-privatekey' > '/etc/wireguard/%i-publickey') +PostUp = wg set '%i' private-key '/etc/wireguard/%i-privatekey' +# Load peers +PostUp = [ -e '/etc/wireguard/%i-peers.conf' ] && wg addconf '%i' '/etc/wireguard/%i-peers.conf' +# Store the internet-facing interface in a file for later use +PostUp = ip route show default | awk '/^default/{print $5}' > '/etc/wireguard/%i-iface' +# Enable IPv4/IPv6 forwarding +PostUp = grep -Fxq '1' /proc/sys/net/ipv4/ip_forward || printf 1 > /proc/sys/net/ipv4/ip_forward +PostUp = grep -Fxq '1' /proc/sys/net/ipv6/conf/all/forwarding || printf 1 > /proc/sys/net/ipv6/conf/all/forwarding +PostUp = grep -Fxq '1' /proc/sys/net/ipv6/conf/default/forwarding || printf 1 > /proc/sys/net/ipv6/conf/default/forwarding +# Allow packet forwarding on the WireGuard interface +PostUp = iptables -A FORWARD -i '%i' -j ACCEPT && iptables -t nat -A POSTROUTING -o "$(cat '/etc/wireguard/%i-iface')" -j MASQUERADE +PostUp = ip6tables -A FORWARD -i '%i' -j ACCEPT && ip6tables -t nat -A POSTROUTING -o "$(cat '/etc/wireguard/%i-iface')" -j MASQUERADE +PostDown = iptables -D FORWARD -i '%i' -j ACCEPT && iptables -t nat -D POSTROUTING -o "$(cat '/etc/wireguard/%i-iface')" -j MASQUERADE +PostDown = ip6tables -D FORWARD -i '%i' -j ACCEPT && ip6tables -t nat -D POSTROUTING -o "$(cat '/etc/wireguard/%i-iface')" -j MASQUERADE +# Allow access to the local DNS server on the WireGuard interface +PostUp = iptables -A INPUT -i '%i' -p tcp --dport 53 -j ACCEPT && iptables -A INPUT -i '%i' -p udp --dport 53 -j ACCEPT +PostUp = ip6tables -A INPUT -i '%i' -p tcp --dport 53 -j ACCEPT && ip6tables -A INPUT -i '%i' -p udp --dport 53 -j ACCEPT +PostDown = iptables -D INPUT -i '%i' -p tcp --dport 53 -j ACCEPT && iptables -D INPUT -i '%i' -p udp --dport 53 -j ACCEPT +PostDown = ip6tables -D INPUT -i '%i' -p tcp --dport 53 -j ACCEPT && ip6tables -D INPUT -i '%i' -p udp --dport 53 -j ACCEPT diff --git a/start-qemu.sh b/start-qemu.sh new file mode 100755 index 0000000..74dd448 --- /dev/null +++ b/start-qemu.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +set -eu +export LC_ALL=C + +SRC_DIR=$(dirname "$(readlink -f "$0")") +TMP_DIR=$(mktemp -d) +trap 'rm -rf "${TMP_DIR:?}"' EXIT + +CLOUDIMG_DISK=${SRC_DIR:?}/packer_output/wireguard.qcow2 +SNAPSHOT_DISK=${TMP_DIR:?}/cloudinit-snapshot.qcow2 +USERDATA_DISK=${TMP_DIR:?}/cloudinit-seed.img +USERDATA_YAML=${TMP_DIR:?}/user-data + +# Create a snapshot image to preserve the original cloud-image +qemu-img create -b "${CLOUDIMG_DISK:?}" -f qcow2 "${SNAPSHOT_DISK:?}" +qemu-img resize "${SNAPSHOT_DISK:?}" +2G + +# Create a seed image with metadata using cloud-localds +printf '%s\n' '#cloud-config' 'runcmd: ["ssh-import-id gh:hectorm"]' > "${USERDATA_YAML:?}" +cloud-localds "${USERDATA_DISK:?}" "${USERDATA_YAML:?}" + +# Remove keys from the known_hosts file +ssh-keygen -R '[127.0.0.1]:2222' +ssh-keygen -R '[localhost]:2222' + +# Launch VM +kvm \ + -smp 1 -m 512 \ + -nographic -serial mon:stdio \ + -device e1000,netdev=n0 \ + -netdev user,id=n0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \ + -drive file="${SNAPSHOT_DISK:?}",if=virtio,format=qcow2 \ + -drive file="${USERDATA_DISK:?}",if=virtio,format=raw diff --git a/wireguard.pkr.hcl b/wireguard.pkr.hcl new file mode 100644 index 0000000..32dfebc --- /dev/null +++ b/wireguard.pkr.hcl @@ -0,0 +1,131 @@ +source "hcloud" "main" { + image = "ubuntu-18.04" + server_type = "cx11" + location = "fsn1" + + server_name = "wireguard-{{timestamp}}" + snapshot_name = "wireguard-{{timestamp}}" + snapshot_labels { + service = "wireguard" + } + + ssh_port = "22" + ssh_username = "root" + ssh_timeout = "10m" +} + +source "qemu" "main" { + iso_url = "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img" + iso_checksum_url = "https://cloud-images.ubuntu.com/bionic/current/SHA256SUMS" + iso_checksum_type = "sha256" + disk_image = true + + vm_name = "wireguard.qcow2" + http_directory = "./http/" + output_directory = "./packer_output/" + + accelerator = "kvm" + cpus = 1 + memory = 512 + headless = true + qemuargs { + qemuargs = ["-smbios", "type=1,serial=ds=nocloud-net;s=http://{{.HTTPIP}}:{{.HTTPPort}}/seed/"] + } + + net_device = "virtio-net" + + format = "qcow2" + disk_size = "4G" + disk_interface = "virtio" + disk_compression = false + + ssh_port = "22" + ssh_username = "root" + ssh_password = "toor" + ssh_timeout = "10m" + + shutdown_command = "shutdown -P now" +} + +build { + sources = [ + "source.hcloud.main" + ] + + provisioner "file" { + direction = "upload" + source = "./rootfs/" + destination = "/" + } + + provisioner "shell" { + inline = [ + "chmod 644 /etc/apt/apt.conf.d/20auto-upgrades", + "chmod 644 /etc/apt/apt.conf.d/50unattended-upgrades", + "chmod 644 /etc/fail2ban/jail.d/sshd.conf", + "chmod 644 /etc/ssh/sshd_config", + "chmod 644 /etc/unbound/unbound.conf", + "chmod 644 /etc/wireguard/client-sample.conf", + "chmod 644 /etc/wireguard/wg0-peers.conf", + "chmod 600 /etc/wireguard/wg0.conf" + ] + } + + provisioner "shell" { + environment_vars = [ + "DEBIAN_FRONTEND=noninteractive" + ] + inline = [ + "printf 'deb http://ppa.launchpad.net/wireguard/wireguard/ubuntu/ bionic main\n' > /etc/apt/sources.list.d/wireguard.list", + "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E1B39B6EF6DDB96564797591AE33835F504A1A25", + "apt-get update", + "apt-get upgrade -yo DPkg::options::=--force-confold", + "apt-get install -yo DPkg::options::=--force-confold dns-root-data fail2ban ufw unattended-upgrades unbound", + "apt-get install -yo DPkg::options::=--force-confold linux-headers-$(uname -r) openresolv wireguard", + "apt-get install -yo DPkg::options::=--force-confold htop iperf3 qrencode nano ssh-import-id", + "apt-get autoremove -y" + ] + } + + provisioner "shell" { + inline = [ + "systemctl disable --now systemd-resolved.service", + "unlink /etc/resolv.conf && printf 'nameserver 127.0.0.1\n' > /etc/resolv.conf", + "systemctl enable --now unbound.service unbound-resolvconf.service", + ] + } + + provisioner "shell" { + inline = [ + "systemctl enable --now fail2ban.service ufw.service unattended-upgrades.service", + "systemctl enable --now wg-quick@wg0.service" + ] + } + + provisioner "shell" { + inline = [ + "ufw --force enable", + "ufw default deny incoming", + "ufw default allow outgoing", + "ufw allow from any to any port 22 proto tcp", + "ufw allow from any to any port 80 proto udp" + ] + } + + provisioner "shell" { + inline = [ + "groupadd -r ssh-user", + "usermod -aG ssh-user root", + "passwd -d root" + ] + } + + provisioner "shell" { + inline = [ + "rm -f /etc/ssh/ssh_host_*key*", + "rm -f /etc/wireguard/*-*key", + "rm -f /etc/wireguard/*-iface", + "rm -rf /var/lib/apt/lists/*" + ] + } +} diff --git a/wireguard.tf b/wireguard.tf new file mode 100644 index 0000000..1fa7bb5 --- /dev/null +++ b/wireguard.tf @@ -0,0 +1,35 @@ +variable "hcloud_token" { + type = string +} + +provider "hcloud" { + token = var.hcloud_token +} + +data "hcloud_image" "wireguard" { + with_selector = "service=wireguard" + most_recent = true +} + +data "hcloud_ssh_key" "hectorm" { + fingerprint = "a1:92:f2:2b:57:5e:cc:9c:5a:0c:f4:33:79:db:b6:56" +} + +resource "hcloud_server" "wireguard" { + name = "wireguard" + image = data.hcloud_image.wireguard.id + server_type = "cx11" + location = "fsn1" + keep_disk = true + backups = false + labels = { + service = "wireguard" + } + ssh_keys = [ + data.hcloud_ssh_key.hectorm.id + ] +} + +output "wireguard_server_ipv4_address" { + value = hcloud_server.wireguard.ipv4_address +}