Adapting some nftables rules to firewalld

I’m currently trying to adapt a simple nft table for WireGuard to firewalld, but I’m new to firewalld and am uncertain about several things.

The nftables script
#!/usr/sbin/nft -f

table inet WGSERVER {
	chain FORWARD {
		type filter hook forward priority filter; policy drop;
		iifname "wg-server" accept
		oifname "wg-server" accept
	}
	
	chain NAT {
		type nat hook postrouting priority srcnat; policy accept;
		#ip saddr 10.0.0.0/24 oifname "eth0" masquerade
		iifname "wg-server" ip saddr 10.0.0.0/24 oifname "eth0" jump do_masquerade
	}
	
	# [CVE-2021-3773]
	chain do_masquerade {
		meta iif > 0 th sport < 16384 th dport >= 32768 masquerade random
		masquerade
	}
}

A bit of context:

  1. oifname "wg-server" accept is for allowing clients assigned with IPv6 addresses to be able to receive connection.
  2. The NAT rule is very specific, as I used to also have OpenVPN on the server, and the server was constantly probed/attacked, so I wanted to prevent abuse or weird things happening.

Questions:

  1. Does masquerade implicitly allow IPv4 forwarding? Is the FORWARD chain not needed if IPV4 only?
  2. Does WireGuard filter invalid packets, so that the rules don’t need to be so specific?
  3. Is working with firewalld the only option here? I tried directly adding this table, but due to the rules in firewalld table, packets accepted by this table is later rejected in firewalld table.
  4. Should WireGuard interface (wg-server) be added to the same zone as WAN (eth0)?
    If not, how to allow forwarding between wg-server and eth0?
    If yes, should I use –add-forward? Is it only between interfaces, contrary to the nft rule and the explanation on the website “if the packet is destined to the any interface in the zone home then go ahead and accept it.” I definitely do not want to allow forwarding random Internet traffic on eth0. (Also if I add more interface/source, like OpenVPN to the zone, the --add-forward option would allow forwarding between all of them.)
  5. For NAT, is the equivalence –add-masquerade? Is masquerade also cross-interface only? I.E. if peers connect to each other with their private IPs, their packets will not be masqueraded. Does this option also masquerade traffic from eth0 to wg-server, if I receive a malformed packet on eth0 that says destined to 10.0.0.2?
  6. The CVE mitigation is nice to have, any chance to replicate it?

No, but --set-target=ACCEPT on the ingress zone does for both IPv4+IPv6.

Firewalld operates in terms of zones and policies to not care about chains.
It creates and manages all the necessary chains automatically including the filter_FORWARD chain, see:

sudo nft list ruleset | nano -Y nftables -

Firewalld provides a built-in nftables-based filter for invalid packets.
WireGuard drops packets failing to pass authentication.

Assigning interfaces to zones depends on the level of trust in your threat model.
In my case, own VPN server belongs to the internal zone same as LAN.

This is the default firewalld configuration, so no extra action is required.

Using multiple interfaces with the same trust level in one zone sounds logical.
But you should certainly separate own VPN server from the external WAN zone.

Yes, for IPv4 NAT.

Firewalld applies zone-specific masquerading to any egress traffic on that zone including intra-zone traffic.
If you need a more flexible approach, disable zone-specific masquerading and enable source-specific masquerading with rich rules.

It would be ingress traffic on your external zone, so its destination depends on the target, policies and rules involving that zone as well as your routing configuration.

It should be already fixed upstream.


I have several Fedora hosts running as dual-stack routers with firewalld and WireGuard.
The firewalld configuration relies entirely on zones and policies and looks like this:

  • Zone external: interface wan0
  • Zone internal: interfaces lan0 and wg0
  • Policy allow-forward-ipv6: IPv6 port forwarding from external to internal
# Traffic forwarding from LAN to WAN
sudo firewall-cmd --permanent --zone=external --add-interface=wan0
sudo firewall-cmd --permanent --zone=internal --add-interface=lan0 --add-interface=wg0
sudo firewall-cmd --permanent --zone=internal --set-target=ACCEPT
sudo firewall-cmd --permanent --zone=internal --add-protocol=icmp --add-protocol=ipv6-icmp
sudo firewall-cmd --permanent --zone=internal --add-rich-rule="rule priority=32767 reject"

# IPv4 port forwarding
sudo firewall-cmd --permanent --zone=external --add-rich-rule="rule family=ipv4 forward-port port=51413 protocol=tcp to-addr=192.168.9.3"
sudo firewall-cmd --permanent --zone=external --add-rich-rule="rule family=ipv4 forward-port port=51413 protocol=udp to-addr=192.168.9.3"

# IPv6 port forwarding
sudo firewall-cmd --permanent --new-policy=allow-forward-ipv6
sudo firewall-cmd --permanent --policy=allow-forward-ipv6 --add-ingress-zone=external
sudo firewall-cmd --permanent --policy=allow-forward-ipv6 --add-egress-zone=internal
sudo firewall-cmd --permanent --policy=allow-forward-ipv6 --add-rich-rule="rule family=ipv6 icmp-type name=echo-request accept"
sudo firewall-cmd --permanent --policy=allow-forward-ipv6 --add-rich-rule="rule family=ipv6 service name=transmission-client accept"

# Apply changes
sudo firewall-cmd --reload

There are also source specific NAT rules to masquerade IPv4+IPv6 traffic from guest VMs running on virtualization nodes in LAN:

# NAT rules for libvirt guests
sudo firewall-cmd --permanent --zone=internal --add-rich-rule="rule family=ipv4 source address=192.168.100.0/24 masquerade"
sudo firewall-cmd --permanent --zone=internal --add-rich-rule="rule family=ipv6 source address=fd00:100::/64 masquerade"
sudo firewall-cmd --reload
2 Likes

@vgaetera Thanks! I think I now have a better understanding of firewalld. Putting WireGuard interface in another zone is a better idea.

About the configuration example:

  • Why add-protocol icmp and fallback reject, when the zone has ACCEPT target? Doesn’t ACCEPT mean accept anything from that zone?
  • The IPv6 port forwarding policy allows specified traffic from external to internal, but if using NAT6, why it doesn’t need to specify forward-port like the IPv4 policy? If not using NAT6, wan0 (external zone) is not allowing forwarding?

On my Fedora 37 installs the default is “forward: no” though.

About adapting my nft rules:

  • The oifname "wg-server" accept seems impossible to adapt. There seems to be no finer control besides --set-target=ACCEPT, which in this case means setting it on external zone. Also, Ideally I want to only allow some eth0-to-wg forwarding to let clients accept connection in some apps. I remember some other things need to be allowed as well, which means they have to be forwarded. (ICMPv6? I’m not sure.)
1 Like

I prefer to follow the zero-trust security model to a reasonable extent.
This part is implemented similarly to the libvirt zone:

The idea is to permit transit traffic, but still filter input traffic according to the allowed services/ports for the current zone.

Yes, you should use forward-port if the target host has no static IPv6 GUA prefix.
A static GUA prefix can be obtained from a VPS provider or an IPv6 tunnel broker.

Correct, forwarding from the WAN side is disabled unless allowed explicitly.

The defaults have changed a few months ago.
Your system may have inherited an earlier default configuration.

There was no until policy objects were implemented:

1 Like

Sorry about that, yes the zone config was replaced (probably by VPS provider’s bootstrap system). My workstation is indeed “forward: yes”.