Configure internal groups with Wireguard VPN

Solution 1:

In order to isolate your groups, you need to configure multiple instance of wireguard with multiple routing tables.

First, create A and AAAA DNS entry for vpn.example.com resolving to your server public IPv4 and IPv6.

You can then install wireguard package. (eg. dnf install wireguard-tools), then generate the private and public key:

# Configure Wireguard folder
mkdir /etc/wireguard
cd /etc/wireguard
wg genkey | tee /etc/wireguard/privatekey | wg pubkey > /etc/wireguard/publickey
chmod 600 /etc/wireguard/*

Next, enable forwarding on your VPN server:

# Enable forwarding
echo "net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1" > /etc/sysctl.d/89-forward.conf
sysctl --system

You can now configure your group A. Most of the configuration is here, in the PostUp commands. We are refreshing nftables once the wg2 interface is created, then we are specifying that incoming traffic from wg2 ips goes to groupa routing table, and we are populating groupa routing table with only the groupa network (no default route, no groupb network), so groupa originated traffic can only access groupa machines.

PreDown deconfigure what PostUp did, all [Interface] section is the server configuration, the [Peer] ones are every single VPN client.

The Server private key is here, and the public on is in the client configuration file (later on). The client private key is in the client configuration file, and the public one is here.

# Configure groupA
echo "[Interface]
Address = 192.168.2.1/24, fd00:2::1/48
PrivateKey = [/etc/wireguard/privatekey value here]
PostUp=nft -f /etc/nftables.conf && ip rule add from 192.168.2.0/24 lookup groupa && ip -6 rule add from fd00:2::/48 lookup groupa && ip route add 192.168.2.0/24 dev wg2 proto kernel scope link src 192.168.2.1 table groupa && ip route add fd00:2::/48 dev wg2 proto kernel metric 256 pref medium table groupa
PreDown=nft -f /etc/nftables.conf && ip route del 192.168.2.0/24 dev wg2 proto kernel scope link src 192.168.2.1 table groupa && ip route del fd00:2::/48 dev wg2 proto kernel metric 256 pref medium table groupa
ListenPort = 51820
[Peer]
PublicKey = bbb
PresharedKey = ccc
AllowedIPs = 192.168.2.2/32, fd00:2::2/128
[Peer]
PublicKey = ddd
PresharedKey = eee
AllowedIPs = 192.168.2.3/32, fd00:2::3/128" > /etc/wireguard/wg2.conf

The group B, with a new routing table, a new wg3 interface, new listening port, and new networks:

# Configure groupB
echo "[Interface]
Address = 192.168.3.1/24, fd00:3::1/48
PrivateKey = [/etc/wireguard/privatekey value here]
PostUp=nft -f /etc/nftables.conf && ip rule add from 192.168.3.0/24 lookup groupb && ip -6 rule add from fd00:3::/48 lookup groupb && ip route add 192.168.3.0/24 dev wg3 proto kernel scope link src 192.168.3.1 table groupb && ip route add fd00:3::/48 dev wg3 proto kernel metric 256 pref medium table groupb
PreDown=nft -f /etc/nftables.conf && ip route del 192.168.3.0/24 dev wg3 proto kernel scope link src 192.168.2.1 table groupb && ip route del fd00:3::/48 dev wg3 proto kernel metric 256 pref medium table groupb
ListenPort = 51821
[Peer]
PublicKey = ggg
PresharedKey = hhh
AllowedIPs = 192.168.2.2/32, fd00:2::2/128
[Peer]
PublicKey = iii
PresharedKey = jjj
AllowedIPs = 192.168.2.3/32, fd00:2::3/128" > /etc/wireguard/wg3.conf

I'm not using iptables anymore, here is the nftables version so I don't do stupid mistakes. You can use it or convert it to iptables. It's a simple configuration : icmp, ssh, and wireguard accepted in input, any in output, and the forwarding section to allow wireguard clients to communicate. You can be more specific in your filtering if you want to allow only some ports.

# Configure nftables
echo 'flush ruleset
define wan = eth0
define groupa = wg2
define groupb = wg3
table inet x {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related counter packets 0 bytes 0 accept
iifname "lo" accept
icmp type echo-request accept
icmpv6 type { nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, echo-request } accept
tcp dport 22 ct state new,untracked accept
udp dport { 51820, 51821 } iif $wan ct state new,untracked accept
ip6 daddr fe80::/64 udp dport 546 ct state new,untracked accept
}
chain output {
type filter hook output priority filter; policy accept;
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related counter packets 0 bytes 0 accept
ip protocol { icmp, tcp, udp } iif $groupa oif $groupa accept
ip6 nexthdr { icmpv6, tcp, udp } iif $groupa oif $groupa accept
ip protocol { icmp, tcp, udp } iif $groupb oif $groupb accept
ip6 nexthdr { icmpv6, tcp, udp } iif $groupb oif $groupb accept
}
}
' > /etc/nftables.conf

Here a script to create a new client in the group A. You have to change the FQDNSERVER value by the dns name you did set in the start, or set the server IP address instead.

You are the generating a private key for the client config file, and the associated public key for the server config file. The CLIENT variable just catch the last IP used, so if it is 192.168.2.10, the next client will use 192.168.2.11. Since we are using the previous IP to generate the next one, don't delete the examples address in the server config files.

In the [Peer] section, AllowedIPs is an ACL. Server side, we are only authorizing the client IP. Client side, we are authorizing the groupa network.

echo '#!/bin/bash
FQDNSERVER="vpn.example.com"
PRIVATE=`wg genkey`
PUBLIC=`echo ${PRIVATE} | wg pubkey`
PSK=`wg genpsk`
SRVPUBLICKEY=`cat /etc/wireguard/publickey` 
CLIENT="$((`cat wg2.conf | tail -n 1 | cut -d . -f 4 | cut -d / -f 1` + 1))"
echo "[Peer]
PublicKey = ${PUBLIC}
PresharedKey = ${PSK}
AllowedIPs = 192.168.2.${CLIENT}/32, fd00:2::${CLIENT}/128" >> wg2.conf
echo "[Interface]
Address = 192.168.2.${CLIENT}/32, fd00:2::${CLIENT}/48
PrivateKey = ${PRIVATE}' > /etc/wireguard/newgroupa.sh
echo "[Peer]
PublicKey = ${SRVPUBLICKEY}
PresharedKey = ${PSK}
AllowedIPs = 192.168.2.0/24, fd00:2::/48
Endpoint = ${FQDNSERVER}:51820" >> /etc/wireguard/newgroupa.sh
echo 'PersistentKeepalive = 30" > client${CLIENT}.conf
cat clienta${CLIENT}.conf
systemctl restart [email protected]' >> /etc/wireguard/newgroupa.sh 
chmod +x newgroupa.sh

The script for group B:

echo '#!/bin/bash
FQDNSERVER="vpn.example.com"
PRIVATE=`wg genkey`
PUBLIC=`echo ${PRIVATE} | wg pubkey`
PSK=`wg genpsk`
SRVPUBLICKEY=`cat /etc/wireguard/publickey` 
CLIENT="$((`cat wg3.conf | tail -n 1 | cut -d . -f 4 | cut -d / -f 1` + 1))"
echo "[Peer]
PublicKey = ${PUBLIC}
PresharedKey = ${PSK}
AllowedIPs = 192.168.3.${CLIENT}/32, fd00:3::${CLIENT}/128" >> wg3.conf
echo "[Interface]
Address = 192.168.3.${CLIENT}/32, fd00:3::${CLIENT}/48
PrivateKey = ${PRIVATE}' > /etc/wireguard/newgroupb.sh
echo "[Peer]
PublicKey = ${SRVPUBLICKEY}
PresharedKey = ${PSK}
AllowedIPs = 192.168.3.0/24, fd00:3::/48
Endpoint = ${FQDNSERVER}:51821" >> /etc/wireguard/newgroupb.sh
echo 'PersistentKeepalive = 30" > client${CLIENT}.conf
cat clientb${CLIENT}.conf
systemctl restart [email protected]' >> /etc/wireguard/newgroupb.sh 
chmod +x newgroupb.sh

Here, we add the new routing tables.

# Configure table
echo '2 groupa' >> /etc/iproute2/rt_tables 
echo '3 groupb' >> /etc/iproute2/rt_tables 

Since I'm using nftables here, I'm disabling any other firewall and be sure it is enabled. Also, we can enable the wireguard services.

# Disable firewalld (or ufw or else)
systemctl disable firewalld
systemctl stop firewalld

# Enable services on boot
systemctl enable nftables --now
systemctl enable [email protected] [email protected] --now

Now you can see your interfaces, addresses, routing tables, rules to direct to routing tables, and wireguard clients.

ip l
ip a 
ip r 
ip r show table groupa 
ip r show table groupb 
ip rule 
wg show

Configurations and scripts are based on Wireguard I'm using in production, although I did rewrite them for you without testing, so everything might not be working by just copy / pasting. It can be a good starting point though, and I'll answer your questions if you have some.

When you use newgroupa.sh or newgroupb.sh, a config file is generated. You can copy it to your client machine, and import it in the wireguard client.

If the client is Windows / MacOS / Android / iOS, you have a graphical interface to import it. If it's linux, you can install wireguard, copy the clientaxx.conf to /etc/wireguard, then systemctl enable [email protected] --now to enable and launch it.