2024-08-19T00:23:40,900200090+00:00
On a cloud vps, access to manage the instance is provided via
network. It is essential to make sure that access to the server is
always possible, without the risk of being locked out because of network
misconfiguration. For example, the first ufw rule to add
before enabling the firewall is ufw allow ssh so that ssh
service is accessible after enabling the firewall. There is also
netplan --debug try to test netplan
configuration before making permanent changes.
One of the high risk network configuration is setting up a VPN tunnel
on a cloud server. The chance of misconfiguration is higher when
specifying a tunnel as route for accessing the internet, i.e. setting up
a VPN as gateway. A misconfiguration can cause the vps instance to be
locked from the outside, which needs some form of physical
access via serial console or ipmi interface to recover from network
misconfiguration.
A route is created when specifying AllowedIPs in the
wireguard configuration. By default, the route is added to the
main route table, so all traffic except to the wireguard
gateway peer will be routed through wireguard interface, which is not
always the desired outcome when activating wireguard tunnel on a cloud
vps instance.
To override this behavior, there is a Table field which
can be used to specify a routing table associated with a specific
wireguard interface. With Table field configured, the usual
traffic is not modified so one will not lose access to the cloud vps
instance, especially when specifying a catch-all ip range as
AllowedIPs, i.e. 0.0.0.0/0 for ipv4 and
::/0 for ipv6.
Here is an example of /etc/wireguard/wireguard0.conf
with Table field configured.
[Interface]
PrivateKey = hidden
Address = 10.0.0.2/32
DNS = 10.0.0.1
Table = 51820
[Peer]
PublicKey = redacted
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820
On the configuration above, the Table field is
configured with value of 51820. So, the usual traffic will
follow the default gateway on the main routing table. If
VPN use is desired, a policy routing has to be configured.
In the above configuration, the wireguard0 interface
will only route traffic through the tunnel if the packet flows through
51820 routing table. To redirect the flow of the packet, a
specific rule based on firewall mark or based on owner can be
enforced.
If a user with uid of 1001 wants to use the
vpn route for the traffic owned by that user, a rule can be specified
easily using ip rule tool.
# Activate the vpn tunnel
systemctl start wg-quick@wireguard0.service
# Route traffic owned by user with uid of 1001 via the vpn
ip rule add uidrange 1001-1001 table 51820
# Since the vpn doesn't provide ipv6, disable routing of ipv6 packets for uid 1001
ip -6 rule add uidrange 1001-1001 unreachable
# Otherwise, add the same rule for ipv6
# ip -6 rule add uidrange 1001-1001 table 51820Other methods of policy routing, such as marking packet using
iptables via fwmark can also be used. The
setup involves firewall (iptables or nft) in
addition to ip-rule. Read the documentation of the specific
tools such as iptables and ip-rule for more
in-depth understanding about policy based routing (i.e. splitting
traffic via several interfaces).
A vps is configured as seedbox to a private tracker, but the vps
provider prohibits the use of vps for this specific purpose. So a
wireguard based VPN is set up to route transmission traffic
through wireguard0 interface.
# On debian, the transmission-daemon package comes with a user configured for the installed service: debian-transmission
$ id debian-transmission
uid=111(debian-transmission) gid=116(debian-transmission) groups=116(debian-transmission)
$ ip route show table 51820
default dev wireguard0 scope link
# First, a rule as kill switch to prevent traffic leaking via the main route if the tunnel is down
$ sudo ip rule add uidrange 111-111 unreachable
# enforce packet owned by uid 111 to be routed via table 51820
$ sudo ip rule add uidrange 111-111 lookup 51820
$ sudo ip -6 rule add uidrange 111-111 unreachable
# Allow traffic via ipv6 if the tunnel supports ipv6 (optional)
# $ sudo ip -6 rule add uidrange 111-111 lookup 51820This setup can be automated during tunnel bring-up via
PostUp field in the
/etc/wireguard/wireguard0.conf file.
[Interface]
PrivateKey = hidden
# If the tunnel provider supports IPv6, there will be IPv6 address specified here, such as ffdc:17ba:8192::0dbf/128 or 2001:db8:f4c3::1337/128
Address = 10.0.0.2/32
DNS = 10.0.0.1
Table = 51820
PostUp = ip rule add uidrange 111-111 unreachable
PostUp = ip rule add uidrange 111-111 lookup 51820
PostUp = ip -6 rule add uidrange 111-111 unreachable
# Uncomment if IPv6 will be used via the tunnel (if the tunnel provider supports it)
# PostUp = ip -6 rule add uidrange 111-111 lookup 51820
PreDown = ip rule del uidrange 111-111 unreachable
PreDown = ip rule del uidrange 111-111 lookup 51820
PreDown = ip -6 rule del uidrange 111-111 unreachable
# Uncomment if IPv6 routing was enabled before (look at previous comment)
# PreDown = ip rule del uidrange 111-111 lookup 51820
[Peer]
PublicKey = redacted
# If IPv6 will be used, add ::/0 to the AllowedIPs
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820To verify that the setup is working, check the public ip address returned when using the specific uid. Verify that the returned address is indeed the vpn public ip address instead of the address of the cloud vps instance.
sudo -u debian-transmission curl https://icanhazip.comThe entire transmission-daemon service can be modified
to require wg-quick@wireguard0.service to make this fully
automated.
sudo systemctl edit transmission-daemon.serviceAdd this snippet to the text editor provided by systemctl edit
command. Put the snippet inside the space between ### as
instructed.
[Unit]
Requires=wg-quick@wireguard0.service
[Service]
ExecStartPre=curl https://icanhazip.comWith the above setups, only packets owned by uid 111 will be routed
through wireguard0 interface. Other packets will use the
main routing table.
The above setup is my practical use of split tunneling, inspired by Android which containerized each app in its own uid.