Firewall
Firewalld
Firewalld is an abstraction of nftables (which is an abstraction of netfilter). Firewalld is installed on Fedora systems by default, and it is what is used to configure the system firewall.
Firewalld rules are grouped into zones. There can be multiple zones, and you may switch between them. There can only be one active zone per network interface.
Don’t mess with it
Fedora Atomic installs a good default zone for workstation use, named
public
. Theres really nothing more for you to do here. This chapter
will mostly discuss things you should NOT change on a workstation,
but this is a general guide as to how these things work, and how to
modify the rules if the need should arise.
If you are running virtual machines, these use a different interface
virbr0
, and so they use a different zone named libvirt
. The VM
routes are configured separately. Read the chapter Public routes to
VMs.
Default Zone
Firewalld lets you configure multiple zones, which are segmented
realms for different network activity or locations (for example, there
are predefined zones for home
and work
).
Check out the default zones of the system:
firewall-cmd --get-active-zones
libvirt interfaces: virbr0 public (default) interfaces: enp4s0
This shows two active zones, one per interface:
public
, which it mentions is the default, is attached to the primary network interfaceenp4s0
.libvirt
, which is all the libvirt rules defined for VMs, and only listening on the libvirt network interfacevirbr0
.
Changing the default zone
As long as you don’t need multiple zones, it’s recommended to use the
default public
zone. But here is how you can switch zones if you
need to change it to something else (eg. work
):
ZONE=work
sudo firewall-cmd --set-default-zone ${ZONE}
This change is permanent, until you change it again.
Zone files
On Fedora Atomic, there are two directories that contain firewalld zone files:
/etc/firewalld/zones
this is the primary zone directory, and it takes precedence. The directory is empty by default./usr/lib/firewalld/zones/
this is the secondary read-only default zone directory. The files here are loaded only if the primary zone directory is missing a file with the same name.
Public zone
Look at the default public zone file:
cat /usr/lib/firewalld/zones/public.xml
<?xml version="1.0" encoding="utf-8"?> <zone> <short>Public</short> <description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description> <service name="ssh"/> <service name="mdns"/> <service name="dhcpv6-client"/> <forward/> </zone>
You can find out what these services are by grepping /etc/services
:
cat /etc/services | grep -P "^(dhcpv6-client|ssh|mdns) " | sort -u
dhcpv6-client 546/tcp dhcpv6-client 546/udp mdns 5353/tcp # Multicast DNS mdns 5353/udp # Multicast DNS ssh 22/sctp # SSH ssh 22/tcp # The Secure Shell (SSH) Protocol ssh 22/udp # The Secure Shell (SSH) Protocol
So that means that the only incoming ports that are allowed are:
- SSH (port 22, but only if you enable the SSH service.)
- multicast (broadcast) DNS (port 5353)
- IPV6 link-local DHCP (port 546)
Test blocked ports
Start a local netcat server on port 5000 (which is blocked by default):
nc -l 5000
Leave the nc
server running, and open another terminal window to
continue on. When you are done testing, press Ctrl-C
to quit nc.
From another machine, on the same network, try connecting to the blocked port 5000 on the workstations LAN ip address:
WORKSTATION=192.168.1.10; nc ${WORKSTATION} 5000
It should immediately exit (return 1), and print nothing, because the port is blocked.
Adding a temporary rule
sudo firewall-cmd --add-port=5000/tcp
From another machine, on the same network, try connecting to the now open port 5000:
WORKSTATION=192.168.1.10; nc ${WORKSTATION} 5000
This time it should sucessfully connect. You can type some message and
then press Enter
. You should see the message in the original window
running the nc
server. If you see the message, you know the port is
open.
This rule is temporary, and will go away when the system reboots.
Adding a permanent rule
sudo firewall-cmd --add-port=5000/tcp --permanent
This adds the rule to the permanent config
/etc/firewalld/zones/public.xml
, and it will survive a system
reboot:
<port port=“5000” protocol=“tcp”>
Remove a permanent rule
sudo firewall-cmd --remove-port=5000/tcp --permanent
Adding a new zone
NEW_ZONE=foo
sudo firewall-cmd --permanent --new-zone=${NEW_ZONE}
Promoting all temporary rules to permanent rules
sudo firewall-cmd --runtime-to-permanent
Showing all firewall rules
To show all the netfilter rules formatted for nftables:
sudo nft list ruleset
To show all the netfilter rules formatted for iptables:
sudo iptables-save
sudo ip6tables-save
nftables and iptables are equivalent interfaces to the same netfilter backend. They show the same rules, just in a different format, depending on your preference.
How to query nftables
Show all tables
sudo nft list tables
table inet firewalld table ip filter table ip nat table ip mangle table ip6 filter table ip6 nat table ip6 mangle
Show table chains
TABLE=filter
sudo nft list table ip ${TABLE}
Show chain
TABLE=filter
CHAIN=FORWARD
sudo nft list chain ip ${TABLE} ${CHAIN}
How to query JSON info from nftables
Get chain
CHAIN=filter_IN_public_allow
sudo nft -j list ruleset | jq '.nftables[] | select(has("chain")) | select(.chain.name == "'${CHAIN}'")'
{ "chain": { "family": "inet", "table": "firewalld", "name": "filter_IN_public_allow", "handle": 153 } }
Get rules per chain
CHAIN=filter_IN_public_allow
sudo nft -j list ruleset | jq '.nftables[] | select(has("rule")) | select(.rule.chain == "'${CHAIN}'")'
Get destination ports (DNAT) per chain
CHAIN=filter_IN_public_allow
sudo nft -j list ruleset | jq -c '.nftables[] | select(has("rule")) | select(.rule.chain == "'${CHAIN}'") | .rule.expr[0] | select(.match.left.payload.field == "dport") | .match.right'
22 5000
This might need better filtering and account for the “allow” action only.
Read firewalld script
/usr/lib/python3.12/site-packages/firewall/core/nftables.py