| LICENSE | ||
| README.md | ||
| wg-lockdown.nft | ||
| wg-lockdown.service | ||
Wireguard Lockdown Mode
If you use a wireguard VPN to proxy all internet traffic, this is a simple firewall to block traffic from leaking outside the tunnel (otherwise known as a kill switch). Works on Linux, tested on Fedora Workstation, Fedora Atomic distros (including Universal Blue distros), Debian, and Linux Mint.
Disclaimer: I am not an expert at networking, this was just something I put together because I could not find anything similar at the time. I mainly followed Mullvad VPN for my implementation, looking at the nftables rules that the official Mullvad Linux client uses, and also their document here: https://github.com/mullvad/mullvadvpn-app/blob/main/docs/security.md.
Install
First make sure your wireguard VPN interface name starts with "wg", and that FwMark (firewall mark) is enabled and set to 51820.
Then:
- download or clone this repo
- edit the variables at the top of
wg-lockdown.nftand then copy the file to/etc/nftables- if you don't want to specify all VPN server IPs, read the section below titled "Dynamic VPN Server IPs"
- copy
wg-lockdown.serviceto/etc/systemd/system - temporarily enable the firewall using
sudo systemctl start wg-lockdown - make sure everything still works (if not, you can turn it off using
sudo systemctl stop wg-lockdown, or restart your PC and your firewalls will be back to normal) - permanently enable the firewall using
sudo systemctl enable --now wg-lockdown
Uninstall
- run
sudo systemctl disable --now wg-lockdown - remove files
/etc/systemd/system/wg-lockdown.serviceand/etc/nftables/wg-lockdown.nft
Troubleshooting
The firewall includes special chains that allow you to add logging for dropped packets. For example to print packets dropped from the output chain you would add to the logging-output chain using something like:
sudo nft 'add rule inet wg-lockdown logging-output log prefix "wg-lockdown: rejected output: "'
This will log dropped packets to your kernel logs, which you can view live using journalctl -k -f. To erase all changes and reset your firewall simply use systemctl restart wg-lockdown.
You can also use the logging-forward and logging-input for the forward and input chains.
If you don't want to log to your kernel logs you can also use nftrace: https://wiki.nftables.org/wiki-nftables/index.php/Ruleset_debug/tracing.
Warnings and Tips
-
we don't fully support ipv6 (like DHCPv6 and NDP), since I am not very familiar with ipv6
-
related/established traffic outside the tunnel will be dropped once wg-lockdown is activated, so be careful that your ssh connections might get dropped once you enable the kill switch
- this is another reason why you should test the firewall using
systemctl startbefore callingsystemctl enable, so that worst case you can force reboot the system to restore it to its original state
- this is another reason why you should test the firewall using
-
we do not block incoming traffic
- if you want to do so, I recommend using firewalld or firewall-cmd, which is better at handling connection states than nftables (for example, if you want to block incoming connections from the LAN, but still want to be able to make and establish connections towards the LAN)
- better yet, disable services that you don't need
-
unlike Mullvad, we allow LAN traffic (including all standard local network ranges) by default
- this is to prevent networking issues when running VMs or rootful containers on your system
- we use the same local network ranges as Mullvad, and we use a static list for security reasons
- to disable LAN access, remove all addresses from the
LANandLAN6variables inwg-lockdown.nft
-
we allow LAN traffic to go through the tunnel, since this can be useful in some point-to-site or site-to-site configurations
- though whether or not your OS actually sends LAN traffic through the tunnel, depends on your routing tables and your wireguard VPN's
AllowedIPsconfiguration- for example, if your
AllowedIPsis0.0.0.0and your home network is 192.168.0.0/24, then most likely scenario is that any requests to 192.168.0.0/24 will go out your eth/wifi directly, while requests to other standard local network ranges (like 10.0.0.0/8) will be sent through the tunnel
- for example, if your
- to block LAN traffic from going through the tunnel (but still allow LAN traffic outside of the tunnel), you can change the rule
ip daddr $LAN accepttooifname != "wg*" ip daddr $LAN acceptand similarly for LAN6 rules, though I haven't tested this
- though whether or not your OS actually sends LAN traffic through the tunnel, depends on your routing tables and your wireguard VPN's
-
we allow forward traffic to LAN addresses, which is often needed for VMs or rootful containers running on your system
- TODO: block access to VMs or rootful containers from the outside, while allowing them to reach each other
- see https://github.com/chaifeng/ufw-docker#how-it-works for an example of how to do this
- libvirt should already add firewall rules to block access to VMs by default
- but be wary of using docker https://github.com/moby/moby/issues/4737
Dynamic VPN Server IPs
Right now you have to specify all your VPN server IPs at the top of the wg-lockdown.nft file. This might be cumbersome if the VPN server IPs are dynamic.
If you don't want to specify a static set of VPN server IPs inside wg-lockdown.nft, you can change the rule ip daddr $VPN_SERVERS meta mark 51820 accept to just meta mark 51820 accept. This will allow a connection to any server, but only if it has a firewall mark of 51820. So assuming that your wireguard VPN's FwMark is set to 51820, and no other application on your system is also setting those firewall marks, then this should only allow wireguard traffic out. I'm not sure though, so by default we also check the destination server just to be safe.
I checked to see what other people were doing and found that:
- the Mullvad Linux client checks both the firewall mark and the destination server
- if you download wireguard configs from mullvad.net with the "kill switch" option enabled, the included "kill switch" only checks the firewall mark
- the iVPN's docs on making a custom wireguard kill switch only checks the firewall mark
Further Reading
- https://www.ivpn.net/knowledgebase/linux/linux-how-do-i-prevent-vpn-leaks-using-nftables-and-openvpn/
- https://meow464.neocities.org/blog/firewalld-vpn-killswitch
- https://github.com/mullvad/mullvadvpn-app
- https://github.com/mullvad/mullvadvpn-app/blob/main/docs/security.md
- https://mullvad.net/en/blog/closer-look-vpn-vulnerability-cve-2019-14899
- https://mullvad.net/en/blog/response-to-tunnelcrack-vulnerability-disclosure
- https://github.com/corrad1nho/qomui