paulgorman.org/technical

Linux firewalld

(May 2019)

firewalld is Linux firewall configuration software that acts as a higher-level wrapper over netfilter tools like iptables and nftables. This abstraction keeps firewall rules and tools consistent, even as preferred lower-level technologies compete and evolve (i.e., will nftables or bpfilter succeed iptables?). Changes to the running rules can be applied without disrupting existing flows (assuming NetworkManager is in use). Arguably, firewalld also offers friendlier tools than, for example, nft.

RHEL 8 uses firewalld by default, and Debian-based distributions offer it as a package.

The firewalld documentation is good. The following notes largely summarize those docs.

The /usr/lib/firewalld/ directory holds firewalld’s default and fallback XML configuration files. Don’t modify them in place, because they’ll be overwritten by package upgrades. Store local changes in /etc/firewalld/; files there override the default configuration. The base config file is /etc/firewalld/firewalld.conf.

firewalld differentiates between the runtime and permanent configuration. The configuration presently in effect in the kernel is the runtime configuration. A system reboot or restart/reload of the firewalld service restores the permanent configuration from saved config files, discarding any differences from the previous runtime configuration.

Save the current runtime config as the permanent config with:

#  firewall-cmd --runtime-to-permanent

firewall-cmd is the main tool to control firewalld. See the firewalld-cms(1) man page.

#  firewall-cmd --help
#  firewall-cmd --state
#  firewall-cmd --get-active-zones
#  firewall-cmd --get-zone-of-interface=br0
#  firewall-cmd --reload

A firewalld zone group together a set of rules/policies (i.e., a “trust level”). That zone can be applied to an interface, connection, or source address. A connection/interface/source belongs to only one zone, but a zone can be applied to multiple connections. By default, firewalld defines these zones, sorted from untrusted to trusted:

  1. drop silently discards all incoming packets, but allows outgoing connections.
  2. block rejects incoming connections with an ICMP prohibited response, but allows outgoing connections.
  3. public rejects connections except for those explicitly allowed. For use on untrusted public networks (SSH, DHCP).
  4. external for external networks, with masquerading. Other nodes in the external zone are not trusted, and only approved incoming connections are allowed (SSH).
  5. dmz demilitarized zone for our publicly accessible servers with limited access to our internal network.
  6. work for networks we mostly trust not to do intentionally malicious stuff. Specific incoming connections are accepted.
  7. home for networks we mostly trust not to do intentionally malicious stuff. Specific incoming connections are accepted (SSH, MDNS, Samba, DHCP).
  8. internal for networks we mostly trust not to do intentionally malicious stuff. Specific incoming connections are accepted.
  9. trusted allows all network connections.

A default zone includes any connections not belonging to another zone.

See the firewall.zone(5) and firewall.zones(5) manual pages for more information.

firewalld links services to zones. A service definition includes a list of ports, and sometimes other information, like firewall helper modules (e.g., an FTP connection tracking helper). See the firewalld.service(5) man page.

firewalld uses IPSets to group lists of IP or MAC addresses. firewalld can use an IPSet in a black- or whitelist rule, for example.

The direct interface feature allows literal iptable rules to be passed through firewalld (though this defeats some of the purpose).

Examples

$  systemctl status firewalld
$  firewall-cmd --state
#  firewall-cmd --reload
#  firewall-cmd --complete-reload

--complete-reload kills all current states, whereas --reload preserves existing flows.

#  firewall-cmd --zone=public --add-port=80/tcp
#  firewall-cmd --permanent --zone=public --add-port=80/tcp
#  firewall-cmd --zone=public --add-service=http
#  firewall-cmd --permanent --zone=public --add-service=http
#  firewall-cmd --permanent --new-service=myservice
#  firewall-cmd --permanent --service=myservice --set-description=description
#  firewall-cmd --permanent --service=myservice --set-short=description
#  firewall-cmd --permanent --service=myservice --add-port=portid[-portid]/protocol
#  firewall-cmd --permanent --service=myservice --add-protocol=protocol
#  firewall-cmd --permanent --service=myservice --add-source-port=portid[-portid]/protocol
#  firewall-cmd --permanent --service=myservice --add-module=module
#  firewall-cmd --permanent --service=myservice --set-destination=ipv:address[/mask]
#  firewall-cmd [ --zone=<zone> ] --list-services
#  firewall-cmd --get-active-zones
#  firewall-cmd --get-zones
#  firewall-cmd --get-services
#  firewall-cmd --get-icmptypes
#  firewall-cmd --list-all-zones
#  firewall-cmd --get-zone-of-interface=<interface>
#  firewall-cmd [--zone=<zone>] --change-interface=<interface>
#  firewall-cmd [--zone=<zone>] --remove-interface=<interface>
#  firewall-cmd --permanent [--zone=<zone>] --add-forward-port=port=<port>[-<port>]:proto=<protocol> \
    { :toport=<port>[-<port>] | :toaddr=<address> | :toport=<port>[-<port>]:toaddr=<address> }
#  firewall-cmd --permanent [--zone=<zone>] --remove-forward-port=port=<port>[-<port>]:proto=<protocol> \
    { :toport=<port>[-<port>] | :toaddr=<address> | :toport=<port>[-<port>]:toaddr=<address> }
#  firewall-cmd --permanent [--zone=<zone>] --query-forward-port=port=<port>[-<port>]:proto=<protocol> \
    { :toport=<port>[-<port>] | :toaddr=<address> | :toport=<port>[-<port>]:toaddr=<address> }<Paste>