We are going to use policies and zones to create a VPN kill switch and tell NetworkManager to automatically activate it for certain interfaces.
Firewalld 1.0.0 supports filtering outgoing traffic with policies. It became much more powerful and I might end up liking it better than UFW now.
Quick introduction to firewalld:
Zones: firewalld objects that can have interfaces assigned to it.
Policies: firewalld objects that regulate traffic flowing between zones. Policies have ingress zones (where the traffic is coming from) and egress zones (where the traffic is going to).
Rich rules: complicated rules that almost look like IPtables rules but still simpler.
+----------+ ESTABLISHED +----------+
| | <------------- | |
| HOST | | VPN-Only |
| | -------------> | |
+----------+ VPN-Killswitch +----------+
Here HOST is a symbolic zone representing traffic coming from this machine. VPN-Only is a zone we will create, it will contain the internet facing network cards. VPN-Killswitch is a policy that only allows outgoing traffic to the VPN servers and local network. ESTABLISHED is the default firewalld policy of allowing traffic back in.
Create the VPN-Only zone:
firewall-cmd --permanent --new-zone VPN-Only
Default target for the Zone (DROP means ignore everything we don’t explicit allow, the default is REJECT which rejects with a polite message):
firewall-cmd --permanent --zone VPN-Only --set-target DROP
Create the VPN-Killswtich policy:
firewall-cmd --permanent --new-policy VPN-Killswitch
Default target for the policy to DROP:
firewall-cmd --permanent --policy VPN-Killswitch --set-target DROP
Reload to apply the changes:
firewall-cmd --reload
Now let’s add the policy rules that allow outbound traffic to the VPN IPs. Repeat this rule for every region
replacing 1.2.3.4 with your VPN’s IP. Change openvpn
to wireguard
if that’s what
you are using.
firewall-cmd --policy VPN-Killswitch --add-rich-rule='rule family="ipv4" destination address="1.2.3.4" service name="openvpn" accept'
Allow outgoing traffic to the local network:
firewall-cmd --policy VPN-Killswitch --add-rich-rule='rule family="ipv4" destination address="192.168.1.0/24" accept'
Set the ingress zone:
firewall-cmd --policy VPN-Killswitch --add-ingress-zone HOST
And the egress zone:
firewall-cmd --policy VPN-Killswitch --add-egress-zone VPN-Only
Now we only need to add our networks cards to the VPN-Only zone. This can be done through the NetworkManager GUI (or CLI) or through firewalld.
With NetworkManager open the properties window for the connection you want to filter and on the tab “General configuration” set “Firewall zone” to “VPN-Only”. If you want to turn off the killswitch simply change the connection zone to “Public” or something else.
Or with firewalld:
firewall-cmd --permanent --zone VPN-Only --add-interface=enp0s8
Testing the setup
Activate the connection you associated with the “VPN-Only” zone and disable the VPN. Try to ping any IP outside the local network and it won’t work.
Make the changes permanent:
firewall-cmd --runtime-to-permanent
Notes from the future
If the IP of your VPN ever change, remove the old rich rule with:
firewall-cmd --policy VPN-Killswitch --remove-rich-rule='rule family="ipv4" destination address="1.2.3.4" service name="openvpn" accept'
References: