#cloud-config package_update: true package_upgrade: true packages: - openssh-server - wireguard-tools - fail2ban - dnf-automatic users: - name: root ssh_authorized_keys: - ${ssh_public_key} write_files: - path: /etc/sysctl.d/99-forwarding.conf content: | net.ipv4.ip_forward = 1 net.ipv6.conf.all.forwarding = 1 # Force accept RA even when forwarding is enabled (Critical for Hetzner IPv6) net.ipv6.conf.eth0.accept_ra = 2 permissions: '0644' - path: /etc/wireguard/wg0.conf content: | [Interface] Address = ${wg_server_ip}/24, ${wg_server_ipv6}/64 ListenPort = 51820 PrivateKey = ${wg_server_private_key} MTU = ${wg_mtu} Table = off [Peer] PublicKey = ${wg_peer_public_key} AllowedIPs = 0.0.0.0/0, ${wg_peer_ipv6}/128 permissions: '0600' owner: root:root - path: /etc/sysconfig/nftables.conf content: | #!/usr/sbin/nft -f flush ruleset table inet filter { chain input { type filter hook input priority filter; policy drop; # Allow loopback iif "lo" accept # Allow established/related connections ct state established,related accept # Allow ICMP ip protocol icmp accept ip6 nexthdr ipv6-icmp accept # Allow SSH (Management) iif "eth0" tcp dport ${ssh_port} accept # Allow WireGuard (Tunnel) iif "eth0" udp dport 51820 accept # Git SSH is DNATed to klops; do not accept it locally here. # Allow HTTP (80) for ACME HTTP-01 challenge iif "eth0" tcp dport 80 accept } chain forward { type filter hook forward priority filter; policy drop; # Allow HTTP/HTTPS, Git SSH, and Luci SSH from internet to WireGuard peer iif "eth0" oif "wg0" tcp dport { 80, 443, ${vcs_ssh_peer_port}, ${luci_ssh_peer_port} } accept # Allow return traffic from WireGuard peer to internet iif "wg0" oif "eth0" ct state established,related accept # Allow established/related ct state established,related accept } chain output { type filter hook output priority filter; policy accept; } } table ip nat { chain prerouting { type nat hook prerouting priority dstnat; policy accept; # DNAT HTTP/HTTPS to WireGuard peer iif "eth0" tcp dport { 80, 443 } dnat to ${wg_peer_ip} # DNAT public Git SSH to Soft Serve on WireGuard peer iif "eth0" tcp dport ${vcs_ssh_public_port} dnat to ${wg_peer_ip}:${vcs_ssh_peer_port} # DNAT public Luci SSH to Luci on WireGuard peer iif "eth0" tcp dport ${luci_ssh_public_port} dnat to ${wg_peer_ip}:${luci_ssh_peer_port} } chain postrouting { type nat hook postrouting priority srcnat; policy accept; # Masquerade traffic from WireGuard peer going to internet ip saddr ${wg_peer_ip} oif "eth0" masquerade } } table ip6 nat { chain prerouting { type nat hook prerouting priority dstnat; policy accept; # DNAT HTTP/HTTPS to WireGuard peer IPv6 ULA iif "eth0" tcp dport { 80, 443 } dnat to ${wg_peer_ipv6} # DNAT public Git SSH to Soft Serve on WireGuard peer IPv6 ULA iif "eth0" tcp dport ${vcs_ssh_public_port} dnat to [${wg_peer_ipv6}]:${vcs_ssh_peer_port} # DNAT public Luci SSH to Luci on WireGuard peer IPv6 ULA iif "eth0" tcp dport ${luci_ssh_public_port} dnat to [${wg_peer_ipv6}]:${luci_ssh_peer_port} } } permissions: '0755' - path: /etc/fail2ban/jail.local content: | [DEFAULT] banaction = nftables-multiport banaction_allports = nftables-allports chain = input [sshd] enabled = true mode = aggressive port = ${ssh_port} logpath = %(sshd_log)s backend = systemd maxretry = 3 bantime = 1h permissions: '0644' - path: /etc/dnf/automatic.conf content: | [commands] upgrade_type = security random_sleep = 0 network_online_timeout = 60 download_updates = yes apply_updates = yes [emitters] emit_via = stdio [base] debuglevel = 1 permissions: '0644' - path: /etc/ssh/sshd_config.d/port.conf content: | Port ${ssh_port} permissions: '0644' - path: /etc/systemd/system/nftables.service.d/after-wg.conf content: | [Unit] After=wg-quick@wg0.service permissions: '0644' runcmd: - sysctl -p /etc/sysctl.d/99-forwarding.conf - systemctl daemon-reload - systemctl enable --now wg-quick@wg0 - systemctl enable --now nftables - systemctl enable --now fail2ban - systemctl enable --now dnf-automatic.timer - systemctl restart sshd