Wireguard With Systemd And Nftables
March 3rd, 2020, 01:29pmWireGuard Installation
Most of my machines/vms are currently running linux-hardened which is not yet up to a version that includes wireguard in the kernel. As such, the kernel module will need to be installed.
pikaur -S wireguard-dkms wireguard-tools
sudo modprobe wireguard
Run this on all machines that will be connected (servers and clients).
Note: Due to the use of Systemd, installation of wireguard-tools
will only be
required on a single machine in order to generate keys.
VPN Design
Next thing to determine is what the VPN will be used for, there are several guides already out there in order to setup WireGuard as a simple VPN for the forwarding of internet traffic. What I wanted to accomplish however was to use one of my VPS servers that I own (one with multiple public-facing IPs) as a server and all of my local VMs as clients and then forward the incoming traffic from specific ports from the VPS to my internal VMs.
In other words I want people to connect to my VPS address and yet be able to hit my local web server and BBS running on my personal internet connection. I wanted to do this in a way that completely hides my actual IP from the internet. Sure you can use something like Cloudflare for this, but I wanted it to be something that I 100% controlled.
Key Generation
Given that in this setup we have a main server and a bunch of clients that are allowed to connect to said server, the clients themselves do not need to talk to one another on the VPN as they can talk locally with each other on the LAN. This somewhat simplifies the management of keys.
Each machine (server & client included) will need to have a private and public key generated for it.
wg genkey | tee privatekey | wg pubkey > publickey
Finally each server + client connection needs a preshared key.
wg genpsd > preshared-clientX
Server Setup
With the keys all generated we can move on to setting up the VPS that will act as the WireGuard server. Begin by enabling forwarding on the machine.
sysctl -w net.ipv4.ip_forward=1
You will want to set this permanently by adding net.ipv4.ip_forward=1
to any
config file in /etc/sysctl.d/
so that it will be applied on boot.
Systemd-Networkd
With forwarding enabled and the keys generated it is time to create the wireguard
interface with systemd-networkd. For my setup I currently have the server and
then two clients. One client is my web server and the other client is a linux
machine I am using to port forward the telnet port to my Windows XP VM (WinXP
does not support WireGuard). Your IPs will vary but for my setup I am using the
network 10.42.1.0/24
for my wireguard network, with the server taking .1
, the
web server vm using .10
and my telnet redirector using .2
.
/etc/systemd/network/wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
[WireGuard]
ListenPort=51820
PrivateKey=<PRIVATE_KEY_SERVER>
[WireGuardPeer]
PublicKey=<PUBLIC_KEY_CLIENT1>
PresharedKey=<PRESHARED_KEY_CLIENT1>
AllowedIPs=10.42.1.2/32
[WireGuardPeer]
PublicKey=<PUBLIC_KEY_CLIENT2>
PresharedKey=<PRESHARED_KEY_CLIENT2>
AllowedIPs=10.42.1.10/32
/etc/systemd/network/wg0.network
[Match]
Name=wg0
[Network]
Address=10.42.1.1/32
[Route]
Gateway=10.42.1.1
Destination=10.42.1.0/24
Do not forget, since these files contain the private / preshared keys, you will not want other users to be able to view these.
sudo chmod 640 /etc/systemd/network/*
sudo chown root:systemd-network /etc/systemd/network/*
Finally go ahead and restart systemd-networkd in order to create the interface.
sudo systemctl restart systemd-networkd
Nftables
With the WireGuard interface now online and accepting connections we need to setup the firewall to: a. accept connections from wireguard over udp; b. setup the port forwarding to forward specific ports down the wireguard interface.
Add a line to the chain input
for your ipv4 filter to accept connections on
the wireguard udp port.
# allow wireguard traffic
udp dport 51820 accept
Next add the ports you wish to forward to the chain forward
for your ipv4
filter, also allow connections to and from the wireguard interface to the regular
ethernet interface.
chain forward {
type filter hook forward priority 0;
iifname "eth0" oifname "wg0" tcp dport 23 tcp flags & (fin|syn|rst|ack) \
== syn ct state new counter accept;
iifname "eth0" oifname "wg0" tcp dport 80 tcp flags & (fin|syn|rst|ack) \
== syn ct state new counter accept;
iifname "eth0" oifname "wg0" tcp dport 443 tcp flags & (fin|syn|rst|ack) \
== syn ct state new counter accept;
iifname "eth0" oifname "wg0" ct state related,established counter accept;
iifname "wg0" oifname "eth0" ct state related,established counter accept;
drop
}
Finally, create a nat section to finish the routing. Keep in mind the IPs that I listed above and be sure to adjust yours accordingly.
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iifname "eth0" tcp dport 23 counter dnat to 10.42.1.2;
iifname "eth0" tcp dport 80 counter dnat to 10.42.1.10;
iifname "eth0" tcp dport 443 counter dnat to 10.42.1.10;
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "wg0" ip daddr 10.42.1.2 tcp dport 23 counter snat to \
10.42.1.1;
oifname "wg0" ip daddr 10.42.1.10 tcp dport 80 counter snat to \
10.42.1.1;
oifname "wg0" ip daddr 10.42.1.10 tcp dport 443 counter snat to \
10.42.1.1;
oifname "eth0" masquerade
}
}
Do not forget to restart the nftables service to reload the new ruleset.
sudo systemctl restart nftables
Client Setup
With the wireguard server now setup we can move onto the clients. Both of these clients are setup the exact same way, the difference comes after setup with what each is implementing.
Systemd-Networkd
The clients are setup very similar to the server, except since the clients only
connect to the server they each only need a single WireGuardPeer
. Do not
forget that the PresharedKey
will be the same string used on the server side
for each client. In the following example I will only show CLIENT2
/etc/systemd/networkd/wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
[WireGuard]
PrivateKey=<PRIVATE_KEY_CLIENT2>
[WireGuardPeer]
PublicKey=<PUBLIC_KEY_SERVER>
PresharedKey=<PRESHARED_KEY_CLIENT2>
AllowedIPs=10.42.1.0/24
Endpoint=vps.hostname.com:51820
PersistentKeepalive=25
/etc/systemd/networkd/wg0.network
[Match]
Name=wg0
[Network]
Address=10.42.1.10/32
[Route]
Gateway=10.42.1.1
Destination=10.42.1.0/24
GatewayOnLink=true
Upon restarting systemd-networkd on the client, it will now connect to the server via wireguard and the port forwarding that goes along with it should be correctly setup.
Forwarding to Unsupported OS
In order to get the telnet port forwarded to my WinXP machine that runs my BBS I instead setup a minimal linux vm which connects as CLIENT1 to my wireguard server. The telnet port is forwarded from the server to this vm. On this VM I run an old-school ftp bouncer (setup without ident) in order to forward the port.
git clone https://github.com/glftpd/f-ftpbnc
cd f-ftpbnc; make
If you are unsure how to setup f-ftpbnc you can refer to the following config.
Configuration Summary:
Name: voidbbs
Local IP and Port: *:23
Destination: 10.0.10.29:23
Destination Bind IP: *
Destination DNS Resolve Time: 3600
Hammer Protection: 0:0
Scrambling configuration with random key.
With 10.0.10.29
being the IP of my WinXP BBS VM. I then created a
systemd.service file for it.
/usr/lib/systemd/system/f-ftpbnc.service
[Unit]
After=network.target network-online.target nss-lookup.target
[Service]
Type=forking
PIDFile=/run/f-ftpbnc.pid
SyslogLevel=err
ExecStart=/home/kyau/f-ftpbnc/f-ftpbnc -pidfile /run/f-ftpbnc.pid
KillMode=mixed
[Install]
WantedBy=multi-user.target
And upon starting the service, people could once again connect to my BBS.
sudo systemctl enable --now f-ftpbnc.service