How to configure WireGuard VPN as gateway on Rocky Linux / CentOS 8

How to configure WireGuard VPN as gateway on Rocky Linux / CentOS 8

These instructions assume that the WireGuard interface (and configuration file) will be wg0, while the machine’s main connection is eth0. Update accordingly below if they are different. The availability of iptables instead of the default firewalld is also a prerequisite.

From WireGuard’s perspective, there is no server and client – all points in a WireGuard network are called peers, and they can connect to each other without a central point and not necessarily in a star topology. However, in this particular configuration scenario and because one peer is central to my whole infrastructure (as it’s the only one to always have a static public IP address, open ports and it can also be used to tunnel all other peers’ traffic), I’m forcing things and calling it a server – while all other peers I will consider clients (and will only connect to the same one other peer which I previously called server).

WireGuard Server

Start by installing the necessary repos

dnf install epel-release elrepo-release

And continue to install WireGuard itself

dnf install kmod-wireguard wireguard-tools

Depending on the uptime of your server, this may also trigger the install of a new kernel. In this case you’ll most likely need to reboot the machine for WireGuard to be able to load its kernel module.

Tell WireGuard to create a public and private key for the server

wg genkey | sudo tee /etc/wireguard/privatekey
cat /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey

Continue the setup by creating /etc/wireguard/wg0.conf with the following content:

[Interface]
Address = 10.0.0.1/24
# SaveConfig = true
## this line tells wireguard to save the current configuration on stop,
## and will overwrite any changes done to the configuration file in the meantime
ListenPort = 12345
PrivateKey = SERVER_PRIVATE_KEY
# PublicKey = SERVER_PUBLIC_KEY ## it would be a good idea to write down the public key for future reference as well
PreUp = iptables -I INPUT -p udp --dport 12345 -j ACCEPT; iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -D INPUT -p udp --dport 12345 -j ACCEPT; iptables -t nat -D POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE

I’m using 10.0.0.0/24 as the WireGuard address space – this can be any local CIDR block of your choosing as long as it doesn’t overlap any networks already used on any of the peers.

Adjust the listen port if desired and fill in the keys from the previously generated key files. I’m not including any example keys in this tutorial because it’s a bad idea to reuse any kind of unique identifiers (especially ones available online). Always generate your own keys pairs and remember to never disclose any private keys publicly.

Test that the configuration is correct and bring up the WireGuard interface with wg-quick up wg0. You should see something similar to:

If you get any error about

Error: Unknown device type.
Unable to access interface: Protocol not supported

make sure you have restarted the system since the last kernel package update.

If the issue persists after a restart, check that kernel-headers, dkms and their dependencies are installed:

dnf install kernel-headers dkms

then run a reinstall of the kernel module package to force a module rebuild for the current kernel:

dnf reinstall kmod-wireguard

When wg0 starts successfully, enable WireGuard at boot with systemctl enable wg-quick@wg0

For the VPN to act as a gateway to the internet remember to enable packet forwarding on the (Linux) server by editing /etc/sysctl.conf to add

net.ipv4.ip_forward = 1

Trigger the setting by calling

sysctl -p /etc/sysctl.conf

And finally set the appropriate firewall rules (these assume the machine is using iptables):

iptables -I FORWARD -i wg0 -o wg0 -j ACCEPT
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT

To also forward some ports from the public server to one of the Wireguard peers use the iptables rules below as a starting point:

# open server ports 10 to 2048
iptables -I INPUT -i eth0 -p tcp --dport 10:2048 -j ACCEPT
# forward port range 10-2048 to target peer 10.0.0.13 but exclude port 88
iptables -t nat -I PREROUTING -i eth0 -p tcp -m tcp --dport 11:87 -j DNAT --to-destination 10.0.0.13:11-87
iptables -t nat -I PREROUTING -i eth0 -p tcp -m tcp --dport 89:2048 -j DNAT --to-destination 10.0.0.13:89-2048
# take care of packet rewrites
iptables -t nat -I POSTROUTING -o wg0 -j MASQUERADE
iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE

If you’re using csf, add all these rules in /etc/csf/csfpre.sh

To make changes to the wg0.conf configuration file first stop the interface with wg-quick down wg0, make the edits and then restart it with wg-quick start wg0

To check the status of the connection, use wg show wg0

Continue by creating key sets for the clients – this can be done using the wg genkey command above (remember to output them to different files) or using an online configuration generator services – (1), (2) (take security considerations into account in this case).
Add the peer details to the wg0.conf configuration file, save and restart the WireGuard interface as previously described:

[Interface]
...existing configuration...

[Peer]
PublicKey = PEER1_PUBLIC_KEY
PresharedKey = PEER1_PRESHARED_KEY
AllowedIPs = 10.0.0.2/32

[Peer]
PublicKey = PEER2_PUBLIC_KEY
PresharedKey = PEER2_PRESHARED_KEY
AllowedIPs = 10.0.0.3/32

[Peer]
PublicKey = PEER3_PUBLIC_KEY
PresharedKey = PEER3_PRESHARED_KEY
AllowedIPs = 10.0.0.4/32

Write down the public/private/preshared key sets, as those will be required later.  The AllowedIPs parameter dictates which (unique) IP address each client (peer) is allowed to use in the virtual network. This includes both the client’s WireGuard interface IP address and any other networks that should be routed through the VPN (I’ll detail this part in the example at the end, for basic tunneling the configuration above is sufficient).

Client(s) configuration

On Windows, install the WireGuard Windows client, for Android install the app from the Play Store or F-Droid (currently unavailable, alternative repo for F-droid). For other OSes, check out the WireGuard installation page.

As I have mentioned at the beginning, there are no clients and servers in the WireGuard topology, all endpoints are equal. This is reflected in how the configuration is done, and we’ll see that the peers I have designated as clients use almost identically structured configurations. For example, the first client’s configuration file:

[Interface]
Address = 10.0.0.2/24
PrivateKey = PEER1_PRIVATE_KEY
DNS = 1.2.3.4  ## optional

[Peer]
Endpoint = 1.2.3.4:12345
PublicKey = PEER1_PUBLIC_KEY
PresharedKey = PEER1_PRESHARED_KEY
AllowedIPs = 0.0.0.0/0, ::/0  ## Use this to route all traffic through the server, or
AllowedIPs = 10.0.0.0/24      ## Use this to access only specific networks on the server

The DNS parameter instructs WireGuard to ignore local DNS configuration and use the set IP for DNS queries. This can be the peer (server) itself (if configured appropriately) or any other (public) DNS resolver.

The Endpoint parameter defines the other peer’s (server’s) IP address and port this client should connect to.
AllowedIPs tells WireGuard for which spaces should network traffic be routed through its interface for. The AllowedIPs setting should be either set to the address / addresses lists WireGuard expects to find on the other peer (namely its local network(s)), or instead use a special value of 0.0.0.0/0, ::/0 which instructs WireGuard to route all network traffic through the VPN.

When a DNS parameter is included, Wireguard creates firewall rules to block any DNS traffic beside the indicated endpoint(s), which combined with incorrect AllowedIPs rules can cause traffic to fail.

For example, using and AllowedIPs value of  0.0.0.0/0 activates these firewall rules and blocks any traffic (including DNS queries) outside the WireGuard tunnel (even to the locally configured DNSes).  If this is not desirable, using the value of 0.0.0.0/1, 128.0.0.0/1 instead will be more appropriate as it allows local DNS queries. This second option also permits access to any local resources in both the local LAN and the server’s own network(s).

The AllowedIPs value an also exclude (or include) specific IP addresses or ranges for when tunnelling needs to be selective. There are online calculators for when network setups become too complex.

Time to test if we’ve managed to not make a mess of all the necessary unique identifiers:

 

Example case

Consider the network setup below.

Continue reading »

Sources: (1), (2), (3), (4)

Leave a Reply