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

Warning

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:

[bash]: Run this on your workstation:
firewall-cmd --get-active-zones
(stdout)
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 interface enp4s0.
  • libvirt, which is all the libvirt rules defined for VMs, and only listening on the libvirt network interface virbr0.

Changing the default zone

Tip

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):

[bash]: Run this on your workstation:
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:

[bash]: Run this on your workstation:
cat /usr/lib/firewalld/zones/public.xml
(stdout)
<?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:

[bash]: Run this on your workstation:
cat /etc/services | grep -P "^(dhcpv6-client|ssh|mdns) " | sort -u
(stdout)
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):

[bash]: Run this on your workstation:
nc -l 5000
Tip

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:

Run this on another machine on the same network
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

[bash]: Run this on your workstation:
sudo firewall-cmd --add-port=5000/tcp

From another machine, on the same network, try connecting to the now open port 5000:

Run this on another machine on the same network
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

[bash]: Run this on your workstation:
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:

Excerpt from /etc/firewalld/zones/public.xml

<port port=“5000” protocol=“tcp”>

Remove a permanent rule

[bash]: Run this on your workstation:
sudo firewall-cmd --remove-port=5000/tcp --permanent

Adding a new zone

[bash]: Run this on your workstation:
NEW_ZONE=foo
sudo firewall-cmd --permanent --new-zone=${NEW_ZONE}

Promoting all temporary rules to permanent rules

[bash]: Run this on your workstation:
sudo firewall-cmd --runtime-to-permanent

Showing all firewall rules

To show all the netfilter rules formatted for nftables:

[bash]: Run this on your workstation:
sudo nft list ruleset

To show all the netfilter rules formatted for iptables:

[bash]: Run this on your workstation:
sudo iptables-save
sudo ip6tables-save
Tip

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

[bash]: Run this on your workstation:
sudo nft list tables
(stdout)
table inet firewalld
table ip filter
table ip nat
table ip mangle
table ip6 filter
table ip6 nat
table ip6 mangle

Show table chains

[bash]: Run this on your workstation:
TABLE=filter
sudo nft list table ip ${TABLE}

Show chain

[bash]: Run this on your workstation:
TABLE=filter
CHAIN=FORWARD
sudo nft list chain ip ${TABLE} ${CHAIN}

How to query JSON info from nftables

Get chain

[bash]: Run this on your workstation:
CHAIN=filter_IN_public_allow
sudo nft -j list ruleset | jq '.nftables[] | select(has("chain")) | select(.chain.name == "'${CHAIN}'")'
(stdout)
{
  "chain": {
    "family": "inet",
    "table": "firewalld",
    "name": "filter_IN_public_allow",
    "handle": 153
  }
}

Get rules per chain

[bash]: Run this on your workstation:
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

[bash]: Run this on your workstation:
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'
(stdout)
22
5000
Warning

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