WireGuard – The quick and the dirty VPN

I have always been an ardent user of OpenVPN and StrongSWAN for the more standardised VPN solutions, but these come with their own headaches. Headaches such as client housekeeping, maintenance of a certificate authority, management of any external resources that may provide service to one of the elements, Directory Server or other LDAP Server and the numerous related protocols, Radius, TACACS, etc.

I was discussing with a friend how nice it would be to have a simple knock it up and go VPN service, something that could be deployed very quickly for small teams and only had to be set up once and then forgotten about. Then the reply came, “Oh, you mean like WireGuard”?

How I had not heard of this, there I was stuck in my routine of setting up Certificate Authorities and access services, I had completely missed the coming of WireGuard. So, without further ado I set about quickly setting up a VPN that I could attach my phones to while I was out and about.

I created an Ubuntu server specifically for the task, then had a conversation in my head about ports (for a number of reasons, but mainly because where the server was situated, who knows what stuff the connectivity supplier blocks from little people like me using), and set about starting the installation of WireGuard.

>sudo apt install wireguard

Well that was tricky, not sure even I can cope with that level of complexity to be honest (ed. Let’s be honest, some days it’s remarkable you even remember you’re own name Dave).

Then we need to have some available IP addresses for our little pool, I wrote a little script to make some fake IP ranges, well a fixed IPv4 range and a generated IPv6. I borrowed the concept from a post on Digital Ocean. It needs streamlining, but one of you lot can do that, I was just testing WireGuard done what I was imagining in my head.

Bash
#!/bin/sh
# Script to create a random IPv6 Range based upon the time and machine ID
# written by Dave Wise - 2024
today=`date +%s%N`
id=`cat /var/lib/dbus/machine-id`
sha=`printf $today$id | sha1sum`
rng=`printf $sha | cut -c 31-`
fore=`printf $rng | cut -c 1-2`
mid=`printf $rng | cut -c 3-6`
end=`printf $rng | cut -c 7-`
echo "IPv4 Network|10.8.0.0/24"
echo "Server IPv4|10.8.0.1/24"
echo "IPv6 Network|fd$fore:$mid:$end::/64"
echo "Server IPv6|fd$fore:$mid:$end::1/64"

This should give an output similar to the following

Bash
dave@royo:~# ./create-ips.sh 
IPv4 Network|10.8.0.0/24
Server IPv4|10.8.0.1/24                                                                                                                                  
IPv6 Network|fd1c:3351:fcad::/64                                                                                                                         
Server IPv6|fd1c:3351:fcad::1/64

So there, we have some numbers to work with, now all we need to do is create the config file, create some allowable connections, referred to as “Peers” in WireGuard terminology and we should be good to go. In its most basic form, it is simply a case of creating public and private keys for every device (including the server) and then saying what is and what isn’t allowed to connect.

So, lets do it. First we create the main configuration file for WireGuard on the server. For this we need to generate a Private and Public key pair

Bash
#!/bin/sh
# A simple script that creates a private and public key inside the
# WireGuard configuration directory.
#
# Author: Dave Wise (c) 2024
# This script is required to be run as root
wg genkey | tee /etc/wireguard/private.key | wg pubkey | tee /etc/wireguard/public.key &> /dev/null

Now we create our main configuration file using the information we have just created.

/etc/wireguard/wg0.conf
[Interface]
PrivateKey = qD7teL1xPmQuGFJ034TkvKqo7HNB1GGGFpsi1fgcLFE=
Address = 10.8.0.1/24, fd1c:3351:fcad::1/64
# ListenPort = 51820
ListenPort = 989
SaveConfig = true
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth1 -j MASQUERADE
DNS = 8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844 

A couple of points to note, I have changed the default port to hide behind the secure ftp port so those pesky blocking ISP’s will miss our packets, some will still be a pain, but this should get around most of them. I left the default port there so you can see it, who knows, you might want it. In theory you can also use port 123, the NTP port. I didn’t because I use mine.

We must also make sure we have turned on net forwarding in sysctl.conf

/etc/sysctl.conf
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

You should then see the following output from sysctl.

>sudo sysctl -p
net.ipv6.conf.all.forwarding = 1
net.ipv4.ip_forward = 1

You can also see I added the masquerading to the WireGuard configuration which makes sure traffic gets routed when it needs to get routed. I don’t use UFW for anything, so I haven’t included any config info for it here. I will let you all do some more googling if you want to add support for it.

Now to enable the service and run it, yes, I know there are currently no peers, but I like to know stuff loads and runs.

Bash
sudo systemctl enable wg-quick@wg0.service
Bash
sudo systemctl start wg-quick@wg0.service

Now to check whether it is running and doing its thing.

sudo systemctl status wg-quick@wg0.service
wg-quick@wg0.service - WireGuard via wg-quick(8) for wg0
     Loaded: loaded (/lib/systemd/system/wg-quick@.service; enabled; vendor preset: enabled)
     Active: active (exited) since Wed 2021-08-25 15:24:14 UTC; 5s ago
       Docs: man:wg-quick(8)
             man:wg(8)
             https://www.wireguard.com/
             https://www.wireguard.com/quickstart/
             https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8
             https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8
    Process: 3245 ExecStart=/usr/bin/wg-quick up wg0 (code=exited, status=0/SUCCESS)
   Main PID: 3245 (code=exited, status=0/SUCCESS)

Aug 25 15:24:14 wg0 wg-quick[3245]: [#] wg setconf wg0 /dev/fd/63
Aug 25 15:24:14 wg0 wg-quick[3245]: [#] ip -4 address add 10.8.0.1/24 dev wg0
Aug 25 15:24:14 wg0 wg-quick[3245]: [#] ip -6 address add fd1c:3351:fcad::1/64 dev wg0
Aug 25 15:24:14 wg0 wg-quick[3245]: [#] ip link set mtu 1420 up dev wg0
Aug 25 15:24:14 wg0 wg-quick[3279]: Rule added (v6)
Aug 25 15:24:14 wg0 wg-quick[3245]: [#] iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eno1 -j MASQUERADE
Aug 25 15:24:14 wg0 wg-quick[3245]: [#] ip6tables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eno1 -j MASQUERADE
Aug 25 15:24:14 wg0 systemd[1]: Finished WireGuard via wg-quick(8) for wg0.

So everything on the server is running, a quick check to make sure it is actually listening to the real world for connections using netstat.

netstat -tunlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:6010          0.0.0.0:*               LISTEN      6346/sshd: dave@pts 
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      695/systemd-resolve 
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      983/sshd: /usr/sbin 
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      786/mariadbd        
tcp6       0      0 :::22                   :::*                    LISTEN      983/sshd: /usr/sbin 
tcp6       0      0 :::80                   :::*                    LISTEN      1017/apache2        
tcp6       0      0 ::1:6010                :::*                    LISTEN      6346/sshd: dave@pts 
tcp6       0      0 :::443                  :::*                    LISTEN      1017/apache2        
udp        0      0 0.0.0.0:989             0.0.0.0:*                           -                           
udp        0      0 127.0.0.53:53           0.0.0.0:*                           695/systemd-resolve 
udp6       0      0 :::989                  :::*                                -                   

There it is listening on UDP/UDP6 port 989, obviously the “nosey” among you can see I am also running Apache and MariaDB on there, but that’s for something else which I may or more likely, may not document.

Now we have to move onto creating our clients or Peers, our peers also require key pairs, so we extend out tiny little script a little to create specific pairs in the current directory by adding a parameter to the call, otherwise it does the default (original) behaviour of creating a key pair in the WireGuard config directory. (ed. You should probably do more safety checks here Dave, you don’t want to accidentally overwrite something like a muppet!)

Bash
#!/bin/sh
# A simple script that creates a private and public key inside the
# WireGuard configuration directory.
#
# Author: Dave Wise (c) 2024
# This script is required to be run as root

if [ -n "$1" ]; then
  wg genkey | tee "wg-client-$1-private.key" | wg pubkey | tee "wg-client-$1-public.key" &> /dev/null
else
  wg genkey | tee /etc/wireguard/private.key | wg pubkey | tee /etc/wireguard/public.key &> /dev/null
fi

For the purposes of this exercise, we will create a test client key pair and then add them to the WireGuard configuration.

Bash
> create-keys test
> ls -l
-rw------- 1 root root   90 May  6  2024 wg-client-test-private.key
-rw-r--r-- 1 root root   90 May  6  2024 wg-client-test-public.key

I should probably create a script for creating a client that adds it to the WireGuard peer configuration, should be a simple process really, it should create the keys, add them to the wg0.conf, create a config file for the client end, and if you’re using a phone to connect (as I am in this case) create a QR Code for the WireGuard phone app to read the configuration from. The Linux app QR Encode should do that. Obviously this becomes a major software project to keep track of allocated IP addresses etc etc, which is not what this exercise is about. It’s mainly to prove it works, start the process of automating stuff with scripts. Tell everyone about it and let them use whatever they need from my ramblings and findings to extend their own tools and projects. But remember people, I do love to hear from everyone that finds this stuff interesting and useful. I just created a client directory to run everything in and then keep a track of the last number added. Some housekeeping would obviously be required, but really, how complicated do I want to make it? (ed. Seriously? Half a job Dave pfff)

Bash
#!/bin/sh
# Create WireGuard Client script
# Author: Dave Wise © 2024
#
# Edit the variables in the environment section to suit your own requirements
# for your endpoints, servers and available IP ranges.
#

# [Environment Section] --------
endpoint="a.b.c.d:989"
dns="8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844"
ip4prefix="10.8.0."
ip6prefix="fd1c:3351:fcad::"
serverkey="91UNV/Xq7r801XzrWIhUsLuwWLIJn4kS6svnax9fRGw="
# [/Environment Section] -------

if [ ! -f ./.lastadded ]; then
  # Never start at 1 .. always assume the server is at 1 
  toadd=2
else
  lastadded=`cat ./.lastadded`
  toadd=$((lastadded + 1))
fi

ip4="$ip4prefix$toadd"
ip6="$ip6prefix$toadd"

if [ -n "$1" ]; then
  wg genkey | tee "wg-client-$1-private.key" | wg pubkey | tee "wg-client-$1-public.key" > /dev/null
  private=`cat wg-client-$1-private.key`
  public=`cat wg-client-$1-public.key`
  if [ $(id -u) -ne 0 ]
  then echo "Please run as root"
  else
    echo "Adding $1 to WireGuard"
    echo "wg set wg0 peer $public allowed-ips $ip4 $ip6"
    # wg set wg0 peer $public allowed-ips $ip4 $ip6

    # Create config file
    echo "Creating Config file for $1"
    printf "[Interface]\nAddress = $ip4/32\nAddress = $ip6/64\nPrivateKey = $private\nDNS = $dns\n\n[Peer]\nPublicKey = $serverkey\nEndpoint = $endpoint\nAllowedIPs = 0.0.0.0/0, ::/0\n" > wg-client-$1-config.conf

    # IF there is a config file, then create the QR Code for it
    configfile="wg-client-$1-config.conf"
    if [ -f "$configfile" ]; then
      echo "Creating QR Code for $1"
      qrcodefile="wg-client-$1-qrcode.png"
      qrencode --read-from="$configfile" -o $qrcodefile
      if [ ! -f "$qrcodefile" ]; then
        echo "[Failed] Could not create QR Code $qrcodefile"
      fi
    else
      echo "[Failed] Could not create config file $configfile"
    fi

    echo $toadd > .lastadded
  exit
fi
else
  echo "You must supply a client name as the parameter, otherwise, whats the point?"
fi

That’s everything on the server side done, now to install WireGuard on the phones, scan the QR Codes and connect and that is pretty much all there is to it. So there you have it, it really is a quick and simple VPN implementation that is still encrypted, has a low impact and works. I connect to mine via iPhone, Android Phone (gotta have something test stuff with), Linux VM and Windows 11 without issue.

If you haven’t got qrencode on your system, then simply add it.

>sudo apt install qrencode

You really can’t screw that one up either. Here’s some iphone screenshots I have found online because I forgot to grab mine as I added it.

I hope this helps someone, I hope it inspires someone to create their own simple VPN’s with WireGuard. It really is a cool thing.

#peaceout


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.