96-fromsofia.net


4-hop Wireguard chained VPN with RHEL9 and FirewallD

Overview

A multi-node chained VPN creates a tunnel through a given number of endpoints. Packets are encrypted when they leave your local computer and then another layer of encryption is added at each next hop.

This type of setup can be useful for clients in highly restrictive environments where online access to the outside world is limited. Your ISP can see the entry node you are connecting to but not the content of your packets, nor the exit node.

Furthermore if an attacker has compromised one of the endpoints and utilized a deanonymizing software, due to the layered encryption they will not be able to see the destination or source of your packets nor will they be able to see their contents.

You will need 4 VMs running RHEL9 or one of it’s derivatives. The further away geographically these endpoints are in, will also require better and better network connection.

We will implement the following architecture: Image

To avoid confusion this article will refer to the nodes by their domain names rather than IP addresses. Nodes wg01, wg02, wg03 will have 2 wireguard interfaces. Node wg04 will have a single interface.

This article will follow the below naming convention and wireguard private addresses:

wg01.mreja
 - endpoint01:	10.88.10.1	# Used by local wireguard client (10.88.10.2) as initial entrypoint
 - client01:	10.88.20.2	# Used as client for wg02
wg02.mreja
 - endpoint02:	10.88.20.1	# Used by the wg01 client01 interface
 - client02:	10.88.30.2	# Used as client for wg03
wg03.mreja
 - endpoint03:	10.88.30.1	# Used by the wg02 client02 interface
 - client:	10.88.30.2	# Used as client for wg04
wg04.mreja
 - endpoint04:	10.88.40.1	# Used by the wg03 client03 interface

This setup has been implemented within a LAN but is also 100% applicable in the internet.

Step 1: Prepare the servers

Important

Run the following command on all 4 wg0*.mreja servers

Update the system and install the required packages:

# yum update -y ; yum install -y vim bind-utils traceroute unbound firewalld wireguard-tools

Reboot the server:

# systemctl reboot

Backup the unbound configuration:

# mv /etc/unbound/unbound.conf /etc/unbound/unbound.confBACK

Download the root.hints file for unbound:

# curl --output /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache

Create a new unbound configuration:

# vim /etc/unbound/unbound.conf

Enter the following contents and save the file:

server:
  num-threads: 4
  #Enable logs
  verbosity: 1
  # chrootdir
  chroot: ""
  #list of Root DNS Server
  root-hints: "/var/lib/unbound/root.hints"
  #Use the root servers key for DNSSEC
  auto-trust-anchor-file: "/var/lib/unbound/root.key"
  #trust-anchor-file: /etc/unbound/trusted-key.key
  #Respond to DNS requests on all interfaces
  interface: 0.0.0.0
  max-udp-size: 3072
  #Authorized IPs to access the DNS Server
  access-control: 0.0.0.0/0                 refuse
  access-control: 127.0.0.1                 allow
  access-control: 10.88.0.0/16                       allow
  #not allowed to be returned for public internet  names
  private-address: 10.88.0.0/16
  # Hide DNS Server info
  hide-identity: yes
  hide-version: yes
  #Limit DNS Fraud and use DNSSEC
  harden-glue: yes
  harden-dnssec-stripped: yes
  harden-referral-path: yes
  #Add an unwanted reply threshold to clean the cache and avoid when possiblea DNS Poisoning
  unwanted-reply-threshold: 10000000
  #Have the validator print validation failures to the log.
  val-log-level: 1
  #Minimum lifetime of cache entries in seconds
  cache-min-ttl: 1800
  #Maximum lifetime of cached entries
  cache-max-ttl: 14400
  prefetch: yes
  prefetch-key: yes

Check the unbound configuration for syntax errors:

# unbound-checkconf 

Start the unbound service:

# systemctl start unbound

Amend the name servers for the OS and save the changes:

# nmcli connection modify ens18 ipv4.ignore-auto-dns true
# nmcli connection modify ens18 ipv4.dns 127.0.0.1
# nmcli connection up ens18 

Verify that DNS is working:

# dig yahoo.com | grep "IN.*A\|SERVER"

You should see 127.0.0.1#53 in the SERVER section and also see all the yahoo.com IP addresses:

;yahoo.com.			IN	A
yahoo.com.		1757	IN	A	98.137.11.163
yahoo.com.		1757	IN	A	98.137.11.164
yahoo.com.		1757	IN	A	74.6.231.20
yahoo.com.		1757	IN	A	74.6.143.25
yahoo.com.		1757	IN	A	74.6.231.21
yahoo.com.		1757	IN	A	74.6.143.26
;; SERVER: 127.0.0.1#53(127.0.0.1)

Step 2: Configure Wireguard

Important

Run the following commands on wg01, wg02, wg03. Replace endpoint01 and client01 with endpoint02/endpoint03 and client02/client03 for wg02/wg03 respectively.

Configure the wireguard directories and keys:

# cd /etc/wireguard/
# umask 0022
# mkdir endpoint01 client01
# cd endpoint01/
# wg genkey | tee private.key | wg pubkey > public.key
# cd ../client01/
# wg genkey | tee private.key | wg pubkey > public.key

Create an additional routing table.

Wireguard will use this to add routing rules after the interface is up. These rules will ensure traffic is properly routed through the wireguard tunnels.

# echo "1 wgroute" >> /etc/iproute2/rt_tables

Important

Run the following commands on wg04.

Configure the wireguard directories and keys:

# cd /etc/wireguard/
# umask 0022
# mkdir endpoint01
# cd endpoint01/
# wg genkey | tee private.key | wg pubkey > public.key

Step 3: Create the wireguard interface configurations

After creating an interface file make sure to create a symlink of in it’s parent directory. This will help you referencing the configuration file with tools such as wg-quick and systemd.

For example, after creating endpoint01 on wg01, run the following:

# ln -s /etc/wireguard/endpoint01/endpoint01.conf /etc/wireguard/endpoint01.conf

For each server create the interfaces as shown below.

wg01

/etc/wireguard/endpoint01/endpoint01.conf

[Interface]
Address = 10.88.10.1/24
SaveConfig = true
ListenPort = 51821
PrivateKey = <private key endpoint01>

[Peer]
PublicKey = <local client public key>
AllowedIPs = 10.88.10.2/32

/etc/wireguard/endpoint01/client01.conf

[Interface]
Address = 10.88.20.2/32
PrivateKey = <private key client01>
DNS=10.88.40.1
PostUp = wg set client01 peer <public key endpoint02> allowed-ips 0.0.0.0/0 && ip route add 0.0.0.0/0 dev client01 table wgroute && ip rule add from 10.88.10.2/32 lookup wgroute

[Peer]
PublicKey = <public key endpoint02>
Endpoint = wg02.mreja:51822
AllowedIPs = 10.88.20.1/32
PersistentKeepalive = 21

wg02

/etc/wireguard/endpoint01/endpoint02.conf

[Interface]
Address = 10.88.20.1/24
SaveConfig = true
ListenPort = 51822
PrivateKey = <private key endpoint02>

[Peer]
PublicKey = <public key client01>
AllowedIPs = 10.88.0.0/16

/etc/wireguard/endpoint01/client02.conf

[Interface]
Address = 10.88.30.2/32
PrivateKey = <private key client02>
DNS=10.88.40.1
PostUp = wg set client02 peer <public key endpoint03> allowed-ips 0.0.0.0/0 && ip route add 0.0.0.0/0 dev client02 table wgroute && ip rule add from 10.88.10.2/32 lookup wgroute && ip rule add from 10.88.20.2/32 lookup wgroute

[Peer]
PublicKey = <public key endpoint03>
Endpoint = wg03.mreja:51823
AllowedIPs = 10.88.30.1/32
PersistentKeepalive = 21

wg03

/etc/wireguard/endpoint01/endpoint03.conf

[Interface]
Address = 10.88.30.1/24
SaveConfig = true
ListenPort = 51823
PrivateKey = <private key endpoint03>

[Peer]
PublicKey = <public key client02>
AllowedIPs = 10.88.0.0/16

/etc/wireguard/endpoint01/client03.conf

[Interface]
Address = 10.88.40.2/32
PrivateKey = <private key client03>
DNS=10.88.40.1
PostUp = wg set client03 peer <public key endpoint04> allowed-ips 0.0.0.0/0 && ip route add 0.0.0.0/0 dev client03 table wgroute && ip rule add from 10.88.10.2/32 lookup wgroute && ip rule add from 10.88.20.2/32 lookup wgroute && ip rule add from 10.88.30.2/32 lookup wgroute

[Peer]
PublicKey = <public key endpoint04>
Endpoint = wg04.mreja:51824
AllowedIPs = 10.88.40.1/32
PersistentKeepalive = 21

wg04

/etc/wireguard/endpoint01/endpoint04.conf

[Interface]
Address = 10.88.40.1/24
SaveConfig = true
ListenPort = 51824
PrivateKey = <private key endpoint04>

[Peer]
PublicKey = <public key client03>
AllowedIPs = 10.88.0.0/16

local client endpoint

/etc/wireguard/client-name.conf

[Interface]
Address = 10.88.10.2/24
DNS = 10.88.40.1
PrivateKey = <local client private key>
PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT

[Peer]
PublicKey = <public key endpoint01>
AllowedIPs = 0.0.0.0/0
Endpoint = wg01.mreja:51821

Step 4: Configure FirewallD

Important

Add the following firewall rule on wg01, wg02, wg03. Replace endpoint01 and client01 with endpoint02/endpoint03 and client02/client03 for wg02/wg03 respectively:

# firewall-cmd --add-interface=endpoint01 --add-interface=client01 --permanent

Add the below firewall rule wg04:

# firewall-cmd --add-interface=endpoint04 --permanent

Add the following firewall rules on all 4 wireguard nodes. Make sure to replace WGPORT with each of the ports chosen within the endpoint0*.conf files!

# firewall-cmd --add-port=WGPORT/udp --permanent
# firewall-cmd --add-service=dns --add-service=ssh --permanent
# firewall-cmd --add-masquerade --permanent
# firewall-cmd --add-forward --permanent
# firewall-cmd --reload

Step 5: Start all the things

Start the wireguard interfaces in the following order:

wg04

# wg-quick up endpoint04

wg03

# wg-quick up endpoint03
# wg-quick up client03

wg02

# wg-quick up endpoint02
# wg-quick up client02

wg01

# wg-quick up endpoint01
# wg-quick up client01

local client

# wg-quick up client-name

To ensure the wireguard interfaces are started on boot use systemd to enable each interface on each node. For example to enable endpoint01 for wg01 run:

# systemctl enable wg-quick@endpoint01.service

Once the all interfaces have been started and you have connected to the wireguard VPN from your local client check if everything works as expected.

Verify you can ping wg01, wg02, wg03, wg04:

# ping -c2 10.88.10.1
# ping -c2 10.88.20.1
# ping -c2 10.88.30.1
# ping -c2 10.88.40.1

Verify you can reach the internet:

# ping 1.1.1.1

Confirm a traceroute to the internet goes through all 4 nodes:

# traceroute 1.1.1.1

The beginning of the output should look as such:

traceroute to 1.1.1.1 (1.1.1.1), 30 hops max, 60 byte packets
 1  10.88.10.1 (10.88.10.1)  2.762 ms  2.730 ms  2.719 ms
 2  10.88.20.1 (10.88.20.1)  2.707 ms  2.696 ms  2.670 ms
 3  10.88.30.1 (10.88.30.1)  4.186 ms  4.182 ms  4.171 ms
 4  10.88.40.1 (10.88.40.1)  4.162 ms  4.151 ms  4.148 ms
...

Check if DNS resolution is working and going through the wg04 interface:

# dig a yahoo.com | grep "IN.*A\|SERVER"

You should see the yahoo.com ip addresses and the SERVER line should contain: 10.88.40.1#53.

;yahoo.com.			IN	A
yahoo.com.		1795	IN	A	98.137.11.164
yahoo.com.		1795	IN	A	74.6.231.20
yahoo.com.		1795	IN	A	74.6.231.21
yahoo.com.		1795	IN	A	74.6.143.25
yahoo.com.		1795	IN	A	74.6.143.26
yahoo.com.		1795	IN	A	98.137.11.163
;; SERVER: 10.88.40.1#53(10.88.40.1)

If you check your public IP while connected to the tunnel you should see the public IP of the wg04 node:

# curl ifconfig.me

The End

If you have followed this article you should have successfully set up a 4 node multi-hop chained VPN. Traffic routed through the tunnel will be layered encryption. Before your request reach the internet they should be routed through all 4 hops.

If you’ve enjoyed this, make sure to go ahead and look at the Articles section. My personal projects you can find on my git server. If you have a question or want to get in touch, feel free to email me.

Thank you for reading and have a good night!