Thanks for the guide @solene. I have made some improvements. Notably, my version runs before network-pre.target, which prevents any traffic from leaking. I have also structured it such that if something goes wrong with the nftables rules, no uplink will be brought up. There was existing support for doing this with Qubes-managed (virtual) uplinks, but the support for doing this with NetworkManager-managed (physical) interfaces was missing. I’ve now added it. Finally, this allows loopback communication.
My implementation follows @rustybird suggestion from Preventing non-Tor connections - #31 by rustybird
These files should be created in a template VM that is specifically for sys-net/sys-firewall.
/lib/systemd/system/external-output-firewall.service
[Unit]
Description=External output firewall
Before=network-pre.target
# Arbitrary order, but keeps it consistent.
Before=qubes-antispoof.service
[Service]
Type=oneshot
ExecStart=/usr/sbin/nft -f /etc/qubes/external-output-firewall.nft
# So that it never runs more than once.
RemainAfterExit=yes
[Install]
# Qubes does not follow systemd.special man page, where network-pre.target is specified passive. Instead, network-pre
# pulls units in (qubes-antispoof, qubes-iptables) and fails if they fail. This failure is then used to stop
# qubes-network-uplink@eth0.service from activating, which stops eth0 from being brought up (unless NetworkManager is
# managing the interface).
RequiredBy=network-pre.target
/etc/qubes/external-output-firewall.nft
#!/usr/sbin/nft -f
table inet external-output-firewall {
chain output {
type filter hook output priority filter; policy drop;
oifname "lo" accept
}
}
/lib/systemd/system/NetworkManager.service.d/50_user.conf
[Unit]
# Refer to /lib/systemd/system/external-output-firewall.service. NetworkManager will configure/up interfaces that it
# manages, so only start it if network-pre succeeded.
Requires=network-pre.target
Run sudo systemctl enable external-output-firewall.service