firewalld

Overview

The venerable iptables packet filter has been and will remain the Linux firewall par excellence for many years to come. However, RHEL 7 offers another a new way of setting up packet filtering and routing rules with firewalld.

Why would anybody start using firewalld if iptables does the job perfectly?

With iptables any addition/modification/deletion of a rule needs a restart/reload for it to be enforced. Firewalld allows dynamic changes of rules (be they interim or permanent) without any service interruption. If you use iptables in a server where network and firewall change requirements are extremely rare... that might not be much of an advantage. But if network changes are common (for instance with laptops moving around), it might be very convenient. Additionally and if you are not already very familiar with iptables, the syntax firewalld uses is a bit more intuitive and verbose as we shall see a few paragraphs down.

Firewalld is not a replacement of iptables and, as a matter of fact, iptables is used by firewalld to implement the filtering/forwarding rules in the background. So it is kind of a more user-friendly layer on top of iptables.

Where iptables worked with chains and rules, firewalld works with zones and services.

A zone is a complete set of rules that apply to a network interface, source address or connection. There are a bunch of zones that come by default, namely: home, work, internal, external, public, trusted, drop, block and dmz. And we can create new zones if we need to.

Let's say that you have a laptop with an Ethernet port and a wireless card. The Ethernet port might be connected to your home network which you fully trust, so firewall rules would be pretty relaxed. You can associate the Ethernet NIC to the home zone and specify the rules that apply to incoming and outgoing connections through the Ethernet port. The same way, you might want to associate the wireless port to the external zone and be very very strict as to what connections you allow through. If you then go to the office, you can associate all NICs to the work zone to comply with company policies. Firewalld can be far more flexible & convenient than iptables!

If we intend to use iptables (see previous section) we should disable firewalld with:

# systemctl stop firewalld.service
# systemctl disable firewalld.service

The same way, if we decide to use firewalld we must start & enable it ...

# systemctl start firewalld.service
# systemctl enable firewalld.service

and we should stick to it and not use iptables at all to avoid misconfigurations and conflicts.

Firewalld has its default and fallback configuration settings stored in /usr/lib/firewalld. The contents of this directory should not be modified as:

• it might not be safe

• any changes/customisations are bound to be lost the next time firewalld is patched/updated

The system and user configurations are kept in /etc/firewalld:

# ls -l /usr/lib/firewalld
total 24
drwxr-xr-x. 2 root root 4096 Feb 26 18:27 helpers
drwxr-xr-x. 2 root root 4096 Mar  8 02:23 icmptypes
drwxr-xr-x. 2 root root 4096 Feb 26 18:27 ipsets
drwxr-xr-x. 2 root root 4096 Mar  7 19:06 services
drwxr-xr-x. 2 root root 4096 Feb 26 18:27 xmlschema
drwxr-xr-x. 2 root root 4096 Feb 26 18:27 zones
.
# ls -l /etc/firewalld
total 36
lrwxrwxrwx. 1 root root   26 Nov 15 22:42 firewalld.conf -> firewalld-workstation.conf
-rw-r--r--. 1 root root 2012 Feb 21 02:33 firewalld-server.conf
-rw-r--r--. 1 root root 2006 Feb 21 02:33 firewalld-standard.conf
-rw-r--r--. 1 root root 2017 Feb 21 02:33 firewalld-workstation.conf
drwxr-x---. 2 root root 4096 Feb 21 02:33 helpers
drwxr-x---. 2 root root 4096 Mar  6 15:20 icmptypes
drwxr-x---. 2 root root 4096 Mar  7 01:07 ipsets
-rw-r--r--. 1 root root  272 Mar  8 01:22 lockdown-whitelist.xml
drwxr-x---. 2 root root 4096 Mar  7 00:43 services
drwxr-x---. 2 root root 4096 Mar  7 00:19 zones

And the main configuration is firewalld.conf. If it cannot be found, firewalld uses the fallback configuration in /usr/lib/firewalld. Let's check the contents of this file:

root:/etc/firewalld> cat firewalld.conf
# firewalld config file
.
# default zone
# The default zone used if an empty zone string is used.
# Default: public
DefaultZone=FedoraWorkstation
.
# Minimal mark
# Marks up to this minimum are free for use for example in the direct
# interface. If more free marks are needed, increase the minimum
# Default: 100
MinimalMark=100
.
# Clean up on exit
# If set to no or false the firewall configuration will not get cleaned up
# on exit or stop of firewalld
# Default: yes
CleanupOnExit=yes
.
# Lockdown
# If set to enabled, firewall changes with the D-Bus interface will be limited
# to applications that are listed in the lockdown whitelist.
# The lockdown whitelist file is lockdown-whitelist.xml
# Default: no
Lockdown=no
.
# IPv6_rpfilter
# Performs a reverse path filter test on a packet for IPv6. If a reply to the
# packet would be sent via the same interface that the packet arrived on, the
# packet will match and be accepted, otherwise dropped.
# The rp_filter for IPv4 is controlled using sysctl.
# Default: yes
IPv6_rpfilter=yes
.
# IndividualCalls
# Do not use combined -restore calls, but individual calls. This increases the
# time that is needed to apply changes and to start the daemon, but is good for
# debugging.
# Default: no
IndividualCalls=no
.
# LogDenied
# Add logging rules right before reject and drop rules in the INPUT, FORWARD
# and OUTPUT chains for the default rules and also final reject and drop rules
# in zones. Possible values are: all, unicast, broadcast, multicast and off.
# Default: off
LogDenied=off
.
# AutomaticHelpers
# For the secure use of iptables and connection tracking helpers it is
# recommended to turn AutomaticHelpers off. But this might have side effects on
# other services using the netfilter helpers as the sysctl setting in
# /proc/sys/net/netfilter/nf_conntrack_helper will be changed.
# With the system setting, the default value set in the kernel or with sysctl
# will be used. Possible values are: yes, no and system.
# Default: system
AutomaticHelpers=system

Out of these 8 options, most likely we will only tweak DefaultZone, LockDown and LogDenied. We shall talk about these latter 2 options in a bit.

Firewalld comes with a list of ICMP types...

# ls -l /usr/lib/firewalld/icmptypes
total 180
-rw-r--r--. 1 root root 385 Feb 21 02:33 address-unreachable.xml
-rw-r--r--. 1 root root 258 Feb 21 02:33 bad-header.xml
-rw-r--r--. 1 root root 294 Feb 21 02:33 beyond-scope.xml
-rw-r--r--. 1 root root 279 Feb 21 02:33 communication-prohibited.xml
-rw-r--r--. 1 root root 222 Feb 21 02:33 destination-unreachable.xml
-rw-r--r--. 1 root root 173 Feb 21 02:33 echo-reply.xml
-rw-r--r--. 1 root root 210 Feb 21 02:33 echo-request.xml
-rw-r--r--. 1 root root 261 Feb 21 02:33 failed-policy.xml
-rw-r--r--. 1 root root 280 Feb 21 02:33 fragmentation-needed.xml
-rw-r--r--. 1 root root 266 Feb 21 02:33 host-precedence-violation.xml
-rw-r--r--. 1 root root 257 Feb 21 02:33 host-prohibited.xml
-rw-r--r--. 1 root root 242 Feb 21 02:33 host-redirect.xml
-rw-r--r--. 1 root root 239 Feb 21 02:33 host-unknown.xml
-rw-r--r--. 1 root root 247 Feb 21 02:33 host-unreachable.xml
-rw-r--r--. 1 root root 229 Feb 21 02:33 ip-header-bad.xml
-rw-r--r--. 1 root root 355 Feb 21 02:33 neighbour-advertisement.xml
-rw-r--r--. 1 root root 457 Feb 21 02:33 neighbour-solicitation.xml
-rw-r--r--. 1 root root 250 Feb 21 02:33 network-prohibited.xml
-rw-r--r--. 1 root root 248 Feb 21 02:33 network-redirect.xml
-rw-r--r--. 1 root root 239 Feb 21 02:33 network-unknown.xml
-rw-r--r--. 1 root root 247 Feb 21 02:33 network-unreachable.xml
-rw-r--r--. 1 root root 239 Feb 21 02:33 no-route.xml
-rw-r--r--. 1 root root 328 Feb 21 02:33 packet-too-big.xml
-rw-r--r--. 1 root root 225 Feb 21 02:33 parameter-problem.xml
-rw-r--r--. 1 root root 233 Feb 21 02:33 port-unreachable.xml
-rw-r--r--. 1 root root 256 Feb 21 02:33 precedence-cutoff.xml
-rw-r--r--. 1 root root 249 Feb 21 02:33 protocol-unreachable.xml
-rw-r--r--. 1 root root 185 Feb 21 02:33 redirect.xml
-rw-r--r--. 1 root root 244 Feb 21 02:33 reject-route.xml
-rw-r--r--. 1 root root 241 Feb 21 02:33 required-option-missing.xml
-rw-r--r--. 1 root root 227 Feb 21 02:33 router-advertisement.xml
-rw-r--r--. 1 root root 223 Feb 21 02:33 router-solicitation.xml
-rw-r--r--. 1 root root 248 Feb 21 02:33 source-quench.xml
-rw-r--r--. 1 root root 236 Feb 21 02:33 source-route-failed.xml
-rw-r--r--. 1 root root 253 Feb 21 02:33 time-exceeded.xml
-rw-r--r--. 1 root root 233 Feb 21 02:33 timestamp-reply.xml
-rw-r--r--. 1 root root 228 Feb 21 02:33 timestamp-request.xml
-rw-r--r--. 1 root root 258 Feb 21 02:33 tos-host-redirect.xml
-rw-r--r--. 1 root root 257 Feb 21 02:33 tos-host-unreachable.xml
-rw-r--r--. 1 root root 272 Feb 21 02:33 tos-network-redirect.xml
-rw-r--r--. 1 root root 269 Feb 21 02:33 tos-network-unreachable.xml
-rw-r--r--. 1 root root 293 Feb 21 02:33 ttl-zero-during-reassembly.xml
-rw-r--r--. 1 root root 256 Feb 21 02:33 ttl-zero-during-transit.xml
-rw-r--r--. 1 root root 259 Feb 21 02:33 unknown-header-type.xml
-rw-r--r--. 1 root root 249 Feb 21 02:33 unknown-option.xml

... and each one of the XML files has a brief explanation ...

# cat host-unreachable.xml
<?xml version="1.0" encoding="utf-8"?>
<icmptype>
. <short>Host Unreachable</short>
. <description>This error message is sent if the destination host is unreachable.</description>
. <destination ipv4="yes"/>
. <destination ipv6="no"/>
</icmptype>

The same applies to services. There are a list of services available by default in /usr/lib/firewalld/services ...

# ls -m /usr/lib/firewalld/services
amanda-client.xml, amanda-k5-client.xml, bacula-client.xml, bacula.xml, bitcoin-rpc.xml, bitcoin-testnet-rpc.xml, bitcoin-testnet.xml, bitcoin.xml, ceph-mon.xml, ceph.xml, cfengine.xml, condor-collector.xml, dhcpv6-client.xml, dhcpv6.xml, dhcp.xml, dns.xml, docker-registry.xml, dropbox-lansync.xml, elasticsearch.xml, freeipa-ldaps.xml, freeipa-ldap.xml, freeipa-replication.xml, freeipa-trust.xml, ftp.xml, ganglia-client.xml, ganglia-master.xml, high-availability.xml, https.xml, http.xml, imaps.xml, imap.xml, ipp-client.xml, ipp.xml, ipsec.xml, iscsi-target.xml, kadmin.xml, kerberos.xml, kibana.xml, klogin.xml, kpasswd.xml, kshell.xml, ldaps.xml, ldap.xml, libvirt-tls.xml, libvirt.xml, managesieve.xml, mdns.xml, mosh.xml, mountd.xml, mssql.xml, ms-wbt.xml, mysql.xml, nfs.xml, ntp.xml, openvpn.xml, pmcd.xml, pmproxy.xml, pmwebapis.xml, pmwebapi.xml, pop3s.xml, pop3.xml, postgresql.xml, privoxy.xml, proxy-dhcp.xml, ptp.xml, pulseaudio.xml, puppetmaster.xml, quassel.xml, radius.xml, RH-Satellite-6.xml, rpc-bind.xml, rsh.xml, rsyncd.xml, samba-client.xml, samba.xml, sane.xml, sips.xml, sip.xml, smtp-submission.xml, smtps.xml, smtp.xml, snmptrap.xml, snmp.xml, spideroak-lansync.xml, spotify.xml, squid.xml, ssh.xml, synergy.xml, syslog-tls.xml, syslog.xml, telnet.xml, tftp-client.xml, tftp.xml, tinc.xml, tor-socks.xml, transmission-client.xml, vdsm.xml, vnc-server.xml, wbem-https.xml, xmpp-bosh.xml, xmpp-client.xml, xmpp-local.xml, xmpp-server.xml

... each with an XML configuration file ...

<?xml version="1.0" encoding="utf-8"?>
<service>
. <short>DNS</short>
. <description>The Domain Name System (DNS) is used to provide and request host and domain names. Enable this option, if you plan to provide a domain name service (e.g. with bind).</description>
. <port protocol="tcp" port="53"/>
. <port protocol="udp" port="53"/>
</service>

And there's obviously a list of pre-configured zones:

# ls /usr/lib/firewalld/zones
block.xml dmz.xml drop.xml external.xml FedoraServer.xml FedoraWorkstation.xml home.xml internal.xml public.xml trusted.xml work.xml
.
# cat /usr/lib/firewalld/zones/*xml
.
<?xml version="1.0" encoding="utf-8"?>
<zone target="%%REJECT%%">
<short>Block</short>
<description>Unsolicited incoming network packets are rejected. Incoming packets that are related to outgoing network connections are accepted. Outgoing network connections are allowed.</description>
</zone>
.
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>DMZ</short>
<description>For computers in your demilitarized zone that are publicly-accessible with limited access to your internal network. Only selected incoming connections are accepted.</description>
<service name="ssh"/>
</zone>
.
<?xml version="1.0" encoding="utf-8"?>
<zone target="DROP">
<short>Drop</short>
<description>Unsolicited incoming network packets are dropped. Incoming packets that are related to outgoing network connections are accepted. Outgoing network connections are allowed.</description>
</zone>
.
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>External</short>
<description>For use on external networks. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="ssh"/>
<masquerade/>
</zone>
.
<?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="dhcpv6-client"/>
<service name="cockpit"/>
</zone>
.
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Fedora Workstation</short>
<description>Unsolicited incoming network packets are rejected from port 1 to 1024, except for select network services. Incoming packets that are related to outgoing network connections are accepted. Outgoing network connections are allowed.</description>
<service name="dhcpv6-client"/>
<service name="ssh"/>
<service name="samba-client"/>
<port protocol="udp" port="1025-65535"/>
<port protocol="tcp" port="1025-65535"/>
</zone>
.
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Home</short>
<description>For use in home areas. You mostly 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="samba-client"/>
<service name="dhcpv6-client"/>
</zone>
.
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Internal</short>
<description>For use on internal networks. You mostly trust the other computers on the networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="ssh"/>
<service name="mdns"/>
<service name="samba-client"/>
<service name="dhcpv6-client"/>
</zone>
.
<?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"/>
</zone>
.
<?xml version="1.0" encoding="utf-8"?>
<zone target="ACCEPT">
<short>Trusted</short>
<description>All network connections are accepted.</description>
</zone>
.
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Work</short>
<description>For use in work areas. You mostly 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"/>
</zone>

The /usr/lib/firewalld/helpers and /usr/lib/firewalld/ipsets are usually empty.

In /etc/firewalld the directories helpers, icmptypes, ipsets, services and zones will be empty unless we create a new item or customise an existing one in either category. We shall see later how to go about it.

Basics

Let's stop talking and start throwing examples to make sense of how firewalld works using firewall-cmd:

The firewalld service has been enabled and started. Are we really sure it is running?

# firewall-cmd --state
running

It is indeed. Let's see what zones are available:

# firewall-cmd --get-zones
FedoraServer FedoraWorkstation block dmz drop external home internal public trusted work

Fair enough. And what is the default zone?

firewall-cmd --get-default-zone
FedoraWorkstation

What zones are in use now?

# firewall-cmd --get-active-zones
FedoraWorkstation
.  interfaces: eno1 wlo1
.
firewall-cmd --get-zone-of-interface=eno1
FedoraWorkstation
.
firewall-cmd --get-zone-of-interface=wlo1
FedoraWorkstation

OK. I do not think I will ever need the FedoraServer zone in my laptop so I'll get rid of it:

firewall-cmd --delete-zone=FedoraServer
usage: see firewall-cmd man page
Option can be used only with --permanent.
.
# firewall-cmd --delete-zone=FedoraServer --permanent
Error: BUILTIN_ZONE: 'FedoraServer' is built-in zone

Bugger, I cannot. So let's move on. I wonder what services are defined by default in firewalld...

firewall-cmd --get-services
RH-Satellite-6 amanda-client amanda-k5-client bacula bacula-client bitcoin bitcoin-rpc bitcoin-testnet bitcoin-testnet-rpc ceph ceph-mon cfengine condor-collector dhcp dhcpv6 dhcpv6-client dns docker-registry dropbox-lansync elasticsearch freeipa-ldap freeipa-ldaps freeipa-replication freeipa-trust ftp ganglia-client ganglia-master high-availability http https imap imaps ipp ipp-client ipsec iscsi-target kadmin kerberos kibana klogin kpasswd kshell ldap ldaps libvirt libvirt-tls managesieve mdns mosh mountd ms-wbt mssql mysql nfs ntp openvpn pmcd pmproxy pmwebapi pmwebapis pop3 pop3s postgresql privoxy proxy-dhcp ptp pulseaudio puppetmaster quassel radius rpc-bind rsh rsyncd samba samba-client sane sip sips smtp smtp-submission smtps snmp snmptrap spideroak-lansync spotify squid ssh synergy syslog syslog-tls telnet tftp tftp-client tinc tor-socks transmission-client vdsm vnc-server wbem-https xmpp-bosh xmpp-client xmpp-local xmpp-server

... and what services are currently open to remote hosts ...

firewall-cmd --list-services
dhcpv6-client samba-client ssh mdns
.
firewall-cmd --info-service=dhcpv6-client
dhcpv6-client
ports: 546/udp
protocols:
source-ports:
modules:
destination: ipv6:fe80::/64
.
firewall-cmd --info-service=samba-client
samba-client
ports: 137/udp 138/udp
protocols:
source-ports:
modules: netbios-ns
destination:
.
# firewall-cmd --info-service=ssh
ssh
ports: 22/tcp
protocols:
source-ports:
modules:
destination:
.
firewall-cmd --info-service=mdns
mdns
ports: 5353/udp
protocols:
source-ports:
modules:
destination: ipv4:224.0.0.251 ipv6:ff02::fb

OK. Any open ports beyond the services above?

firewall-cmd --list-ports
1025-65535/tcp 1025-65535/udp

So all non-privileged ports are wide-open to the world. Let's check the configuration of the current zone:

# firewall-cmd --list-all
FedoraWorkstation (active)
. target: default
. icmp-block-inversion: no
. interfaces: eno1 wlo1
. sources:
. services: dhcpv6-client samba-client ssh mdns
. ports: 1025-65535/tcp 1025-65535/udp
. protocols:
. masquerade: no
. forward-ports:
. source-ports:
. icmp-blocks:
. rich rules:

Now let's check what the configuration would be if I associated the NICs to the work zone:

# firewall-cmd --list-all --zone=work
work
. target: default
. icmp-block-inversion: no
. interfaces:
. sources:
. services: ssh mdns dhcpv6-client
. ports:
. protocols:
. masquerade: no
. forward-ports:
. source-ports:
. icmp-blocks:
. rich rules:

If the zone we are inquiring on is not the default, we can get the same output typing a bit less with:

# firewall-cmd --info-zone=work
work
. target: default
. icmp-block-inversion: no
. interfaces:
. sources:
. services: ssh mdns dhcpv6-client
. ports:
. protocols:
. masquerade: no
. forward-ports:
. source-ports:
. icmp-blocks:
. rich rules:

And we can get the same output for all zones in one go with:

firewall-cmd --list-all-zones

At home I mostly use the wireless interface (wlo1) whereas the Ethernet port (eno1) is mostly used in the office. So let's move eno1 to the work zone:

# firewall-cmd --zone=work --change-interface=eno1
The interface is under control of NetworkManager, setting zone to 'work'.
success
.
firewall-cmd --get-active-zones
work
. interfaces: eno1
FedoraWorkstation
. interfaces: wlo1

Changing the zone for eno1 will have immediate effect but the change won't be permanent (it won't survive a reboot). If we rebooted now we would see eno1 falling again in the default zone (FedoraWorkstation). How then do we make the change permanent? There are actually 2 ways:

firewall-cmd --zone=work --change-interface=eno1 --permanent
# echo "ZONE=work" >> /etc/sysconfig/network-scripts/ifcfg-eno1

Logically either should work but in actual fact and because of bugs they might not. So the safest option is to append "--permanent" option to the firewall-cmd command and then add (if it isn't there yet) the ZONE parameter to the ifcfg file.

Let's say we don't like the default zone set to FedoraWorkstation and we want to set it to home. How do we do that?

# firewall-cmd --set-default-zone=home

Let's review the settings for this zone:

# firewall-cmd --list-all --zone=home
home
. target: default
. icmp-block-inversion: no
. interfaces:
. sources:
. services: ssh mdns samba-client dhcpv6-client
. ports:
. protocols:
. masquerade: no
. forward-ports:
. source-ports:
. icmp-blocks:
. rich rules:

The settings are the same as they were in the FedoraWorkstation zone save for the open ports (1025-65535 tcp & udp) which are no longer open. We do not want to rock the boat just yet and we want to open the ports so that the configuration is the same it was:

# firewall-cmd --zone=home --add-port=1025-65535/tcp --add-port=1025-65535/udp
success

Excellent but... the change above won't survive a reboot. For a change to be permanent we need to add the "--permanent" option:

# firewall-cmd --zone=home --add-port=1025-65535/tcp --add-port=1025-65535/udp --permanent
success

We do not really need a Samba client for the foreseeable future so we'll remove it:

firewall-cmd --zone=home --remove-service=samba-client --permanent
success

And we want to add HTTP for our web development environment:

firewall-cmd --zone=home --add-service=http --add-service=https --permanent
success

The wlo1 interface is not explicitly associated to any zone yet, so it fall on the new default zone of home. To avoid mistakes, we will explicitly place wlo1 in the home zone.

firewall-cmd --zone=home --add-interface=wlo1 --permanent

All packets not matching any of the rules laid out before should be dropped. Let's make sure that's the case:

firewall-cmd --zone=home --set-target=DROP --permanent
success

The changes we have done were mostly permanent and we explicitly indicated that. But we could have left out the "--permanent" option (making those changes interim) and run the following at the end:

firewall-cmd --runtime-to-permanent
success

Permanent rules are saved to disk whereas interim ones are just kept in memory. Thus, a reboot or restart of the firewalld daemon will lose all runtime/interim changes.

If we made a mistake or for any reason we want to rollback any interim changes performed, we can run ...

firewall-cmd --reload

... so as to discard any non-permanent change and reload the rules from disk.

In extreme cases where the firewall is not responsive or behaves in erratic ways (e.g. connection state info is corrupted), we can run ...

firewall-cmd --complete-reload

... bearing in mind that some/many/all established connections might be terminated!

A bit more advanced

We have setup the home zone with the wireless interface wlo1 and we feel safer now. But what if we take the laptop out and about and connect to an Open Wireless network in a cafe, library or airport? We can indeed change the default zone from home to something else with tighter security settings but it is a matter of time before we forget to do that. We'll do something a bit more elaborate instead.

First we copy the home.xml file, rename it and place it in the right place...

# cd /usr/lib/firewalld/zones
# cp home.xml /tmp/home-net.xml

... and then we edit home-net.xml so that it looks like this...

# cat /tmp/home-net.xml
<?xml version="1.0" encoding="utf-8"?>
<zone>
. <short>home-net</short>
. <description>For use at home with the 192.168.8.0/24 network!</description>
. <service name="ssh"/>
. <service name="mdns"/>
. <service name="dhcpv6-client"/>
</zone>

... and finally we import it...

firewall-cmd --new-zone-from-file=/tmp/home-net.xml --permanent
success
.
ls /etc/firewalld/zones
FedoraServer.xml FedoraWorkstation.xml home-net.xml home.xml trusted.xml
.
firewall-cmd --list-all-zones --permanent | grep "^[a-z]"
block
dmz
drop
external
home (active)
home-net
internal
public
trusted (active)
work
.
firewall-cmd --info-zone=home-net --permanent
home-net
. target: default
. icmp-block-inversion: no
. interfaces:
. sources:
. services: ssh mdns dhcpv6-client
. ports:
. protocols:
. masquerade: no
. forward-ports:
. source-ports:
. icmp-blocks:
. rich rules:

We could also have just created a properly formatted file in /etc/firewalld/zones and restart firewalld with systemctl or reload the configuration with firewall-cmd --reload. If done properly, the new zone would be available but adding the new zone on-the-fly with --new-zone-from-file is the least disruptive way of achieving our aim.

Now we specify that our home network will fall in this zone:

firewall-cmd --zone=home-net --permanent --add-source=192.168.8.0/24
success
.
firewall-cmd --zone=home-net --list-sources --permanent
192.168.122.0/24 192.168.123.0/24
.
# firewall-cmd --get-zone-of-source=192.168.122.0/24
home-net
.
# firewall-cmd --get-zone-of-source=192.168.123.0/24
home-net

We have done quite a lot of customisation but can do quite a bit more. Let's now create a service called Oracle meant for the TCP listener running on port 1521. When we created the new zone home-net before we did it with the --new-zone-from-file option. Now we shall do it the other way. First we create the new XML service file in /etc/firewalld/services...

# cat /etc/firewalld/services/oracle1521.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
. <short>oracle1521</short>
. <description>Oracle listener on 1521</description>
. <port port="1521" protocol="tcp"/>
</service>

... and then we simply reload the firewalld settings so that the new service is seen and available:

firewall-cmd --reload
success
.
# firewall-cmd --get-services | grep oracle1521 | wc -l
1
.
# firewall-cmd --zone=home-net --add-service=oracle1521 --permanent
success

Voila! We are almost there. Now however we want to get rid of the home network 192.168.123.0/24 and replace it by a list of specific IP addresses. If they are just a few, we could just add them with the --add-source option. But if there are dozens... it would be laborious, inconvenient and plain ugly. That is when ipsets step in to save the day. As the name implies, an ipset is simply a list of IPs, networks or MACs that can be used for its convenience.

Let's create a new ipset XML file to see how it works...

# cat /etc/firewalld/ipsets/allow-ssh.xml
<?xml version="1.0" encoding="utf-8"?>
<ipset version="allow-ssh" type="hash:net">
. <option name="hashsize" value="64"/>
. <option name="maxelem" value="64"/>
. <short>allow-ssh</short>
. <description>IP addresses allowed access to SSH port 22</description>
. <entry>192.168.123.10</entry>
. <entry>192.168.123.15</entry>
. <entry>192.168.123.16</entry>
. <entry>192.168.123.32/28</entry>
</ipset>

There are 11 types of hashes but most of the time we can get by with either net (for IPs & networks) or mac (for MAC addresses). The hashsize & maxelem parameters default to a value of 1024 & 65536 (should be a power of 2!) and can be left out if those values suffice. The "<entry>" can only contain IPs or networks as determined by the hash type.

Now we can remove network 192.168.123.0/24 from the home-net zone and add instead the newly created ipset:

firewall-cmd --zone=home-net --remove-source=192.168.123.0/24 --permanent
success
.
firewall-cmd --zone=home-net --add-source=ipset:allow-ssh --permanent
success

Unless a network protocol is explicitly allowed through a service, open port or listed in the protocols list, its packets should be dropped/rejected by default. The behaviour with the ICMP protocol is different though: all ICMP packets are let in by default! Let's corroborate that:

# firewall-cmd --info-zone=home-net --permanent
home-net (active)
. target: default
. icmp-block-inversion: no
. interfaces:
. sources: 192.168.122.0/24 ipset:allow-ssh
. services: ssh mdns dhcpv6-client oracle1521
. ports:
. protocols:
. masquerade: no
. forward-ports:
. source-ports:
. icmp-blocks:
. rich rules:

Can we ping the localhost from an IP in the 192.168.122.0/24 network?

[root@fed25]# ip a s team0
6: team0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
.    link/ether 52:54:00:0c:13:fe brd ff:ff:ff:ff:ff:ff
.    inet 192.168.122.100/24 brd 192.168.122.255 scope global team0
.       valid_lft forever preferred_lft forever
.
[root@fed25]# ping 192.168.122.1 -I team0 -c 1
PING 192.168.122.1 (192.168.122.1) from 192.168.122.100 team0: 56(84) bytes of data.
64 bytes from 192.168.122.1: icmp_seq=1 ttl=64 time=0.216 ms
--- 192.168.122.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.216/0.216/0.216/0.000 ms

We can indeed. For the home-net network that might not be an issue but ICMP packets coming from external networks are another matter. They might be perfectly legit and necessary, but they might also be used to probe our network with ill-intent. So we want to block most incoming ICMP types.

What ICMP types are blocked in the home-net zone?

firewall-cmd --zone=home-net --list-icmp-blocks
.

None! Let's try to block ICMP ping requests...

firewall-cmd --zone=home-net --add-icmp-block=echo-request

... and test whether or not it works...

[root@fed25 ~]# ping 192.168.122.1 -I team0 -c 1
PING 192.168.122.1 (192.168.122.1) from 192.168.122.100 team0: 56(84) bytes of data.
From 192.168.122.1 icmp_seq=1 Destination Host Prohibited
--- 192.168.122.1 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

ICMP ping requests are definitely blocked now! Let's proceed to block the rest of ICMP types that are most likely to be used with nefarious intent (see this article on ICMP types that can be used for reconnoissance):

firewall-cmd --zone=home-net --permanent --add-icmp-block=time-exceeded 
success
.
firewall-cmd --zone=home-net --permanent --add-icmp-block=timestamp-reply 
success
.
firewall-cmd --zone=home-net --permanent --add-icmp-block=timestamp-request 
success
.
firewall-cmd --zone=home-net --permanent --add-icmp-block=protocol-unreachable
success
.
firewall-cmd --zone=home-net --permanent --add-icmp-block=port-unreachable
success
.
firewall-cmd --zone=home-net --permanent --add-icmp-block=ip-header-bad
success
.
firewall-cmd --zone=home-net --permanent --add-icmp-block=parameter-problem
success
.
firewall-cmd --runtime-to-permanent
success

Now that we know how ICMP blocking works, we can proceed to implement that filtering in external networks.

Masquerading, port forwarding and source ports

Every now and then I need SSH access an isolated server in a remote office. As I might connect from any IP, the SSH port needs to be wide open despite of the security risks. There are a few things I can do to tighten up security a notch or two, but 2 of of them would be:

• change the SSH port from 22 to another number (i.e. 16478)

• setup the firewall in the remote server so that it only allows SSH connections from a given source port

Those 2 actions might only slow down a sophisticated and determined intruder, but they might get rid of many automated bots and script kiddies.

To change the SSH listening port from the default 22 to something else, check the instructions in the SSH section ... and don't forget about informing SELinux about it!

To setup the port forward with firewalld (we can also use SSH port forward) we first enable masquerading for the given zone...

firewall-cmd --zone=home --add-masquerade
success

... and then configure the actual port forward...

# firewall-cmd --zone=home --add-forward-port=port=16789:proto=tcp:toaddr=5.168.2.55:toport=16789

On the remote server we should configure SSH to listen on port 16789 instead of 22. How do we do that?

• go to /etc/sshsshd_config

• replace the line with the string "Port 22" (usually commented out) with "Port 16789" (no "#" in front!)

• execute "semanage port -a -t ssh_port_t -p tcp 16789" to inform SELinux about the change

• execute: "systemctl restart sshd.service" to restart SSH daemon with new listening port

• execute: "netstat -ltnp | grep 16789" to make sure the port 16789 is listening SSH

Then we create a spanky new zone called allow-ssh by creating the file below...

# cat /tmp/allow-ssh.xml
<?xml version="1.0" encoding="utf-8"?>
<zone>
. <short>allow-ssh</short>
. <description>Allow SSH only from a given source port.</description>
</zone>

... and then importing it ...

firewall-cmd --new-zone-from-file=/tmp/allow-ssh.xml --permanent

We now have an inactive empty zone not associated to anything...

# firewall-cmd --info-zone=allow-ssh --permanent
allow-ssh
. target: default
. icmp-block-inversion: no
. interfaces:
. sources:
. services:
. ports:
. protocols:
. masquerade: no
. forward-ports:
. sourceports:
. icmp-blocks:
. rich rules:

... but we associate it to source port 16789 and the TCP protocol:

[root@fed25 ~]# firewall-cmd --zone=allow-ssh --add-source-port=16789/tcp --permanent

From now on, every time we want to open an SSH session in the remote server we should connect to the machine with the port forward, and then run:

ssh user@localhost -p 16789

Connections to port 16789 will be forwarded to 5.168.2.55:16789 and should be let in by the remote firewall. Direct connections to the remote server on port 22 will obviously fail because there's no service listening on it (that might mislead some port scans). And direct connections to 5.168.2.55:16789 are bound to fail because the source port should never be 16789 (the ephemeral port range starts at 32768 and is rarely changed).

Logging

Dropping and rejecting packets that should not go through the firewall is all fair and good but ... wouldn't you want to know what forbidden traffic is hitting the firewall? If somebody tries to knock down your front-door and fails, you might want to know it. Wouldn't you? And better if that knowledge was immediate so you could take action on the spot. That's what logging is for ... if you monitor it the right way!

With firewalld we can easy enable logging of all dropped & rejected packets with a simple command:

firewall-cmd --set-log-denied=all --zone=home

We should run the command above for all zones connected to external networks as log-denied is by default set to off. And we should bear in mind that running this command calls a "firewall-cmd --reload" in the background so all interim changes should be persisted before-hand!

With logging enabled, we can keep track of dropped/rejected packets with dmesg or journalctl.

Lockdown

Local applications and services are allowed to change firewalld configuration if they are running as root or authenticated with PolicyKit. For example, when running virtual machines with QEMU/KVM, libvirt needs to change filtering policies, enable masquerading, establish port forwards, etc. This is the status when lockdown is off (default).

When we switch lockdown-on ...

firewall-cmd --lockdown-on

... only those applications listed in the lockdown-whitelist.xml file are allowed to change firewalld settings. Let's have a look at a typical such file:

cat /etc/firewalld/lockdown-whitelist.xml
<?xml version="1.0" encoding="utf-8"?>
<whitelist>
. <command name="/usr/bin/python3 -Es /usr/bin/firewall-config"/>
. <user id="0"/>
. <selinux context="system_u:system_r:NetworkManager_t:s0"/>
. <selinux context="system_u:system_r:virtd_t:s0-s0:c0.c1023"/>
</whitelist>

We see that binaries on the NetworkManager_t and virtd_t SELinux contexts are whitelisted as well as the firewall-config GUI tool and any other binary being run as root. If we are happy with that, we can issue a lockdown or edit /etc/firewalld/firewalld.conf and change Lockdown=yes.

To query whether or not lockdown is enabled we can use:

firewall-cmd --query-lockdown

Rich rules

The standard rules firewalld offers are sometimes too coarse for our needs. For instance, with the normal rules we cannot filter by IP protocol (4 | 6), limit the connections per minute, log messages related to specific rules with custom prefixes or levels (emerg, crit, info, etc). Rich rules can do that.

The syntax of rich rules is pretty straight-forward and is made up of:

• family: ipv4 | ipv6
• source address
• destination address
• service | port | protocol | masquerade | icmp block | port forward
• log
• audit
• accept | reject | drop

Let's throw a bunch of examples so that its use is clear. This first example applies only to IPv6 connections with the given source address, it lets through all SCTP protocol packets, logging messages with the prefix "sctp" at a max rate of 1 message per second.

firewall-cmd --zone=work --add-rich-rule='rule family="ipv6" source address="1:2:3:4:5:6::" protocol value="sctp" log prefix="sctp" level="info" limit value="1/s" accept'

The next example applies to both IPv4 & IPv6 (no family specified), allows access to the SSH service for the given zone, auditing and limiting their rate at 3 new connections per minute.

firewall-cmd --zone=work --add-rich-rule='rule destination address="10.25.0.0/16" service="ssh" audit limit value="3/m" accept'

The next example whitelists the given source allowing all incoming connections:

firewall-cmd --zone=internal --add-rich-rule='rule source address="192.168.125.0/24" accept'

The next example drops all packets coming from the given source:

firewall-cmd --zone=external --add-rich-rule='rule source address="51.105.0.0/16" drop'

Finally, this last example forwards connections from the given source:

firewall-cmd --zone=forward --add-rich-rule='rule source address="172.150.0.0/16" forward-port port="16789" protocol="tcp" to-addr="175.76.76.8" to-port="16789"'

As seen in the examples above, rich rules come in particularly handy for IP version filtering, logging and rate limiting.

Direct rules

With complex firewall rulesets we might realise that firewalld is still way behind iptables in terms of power & flexibility. If that is your case, do not despair as you can use iptables rules from within firewalld as in the example below:

firewall-cmd --direct --add-rule ipv4 filter INPUT 1 -p tcp -m state --state NEW --dport 22 -j ACCEPT

For more info on how to write direct rules, you can check the "Direct Options" section in man firewall-cmd or man firewalld.direct.

Having said that, if you need to use the direct interface any often... maybe you should drop firewalld and fall back to iptables. 🙂

 

Zone selection and rule order

A given connection is coming from an interface allocated to zone external, but with a source that is listed in zones ssh-allow and forward. Which one will be used to determine what happens to it? The order will always be:

1- direct rules

2- source address

3- interface

4- current default zone

Direct rules are always processed in sequential order (they are iptables rules and order matters!) ahead of any zone rule.

If no direct rules match the given packet, then the source address will be checked in the different zones. Let's say that the packet comes from 177.77.77.77 and we have two zones with sources set to 177.77.0.0/16 and 177.77.77.0/24. The zone with the most fine-grained network specification will win. If there was a third network with a source set to 177.77.77.77, then this 3rd one would be used.

If the source of the packet is not specified in any zone, then the interface will be the determining factor.

And if the interface is not associated to any zone, then the default zone will process the packet.

Utilities 

As we have seen above the main tool of firewalld is firewall-cmd but there are 3 other tools that come in handy in certain situations.

The firewall-offline-cmd is meant to be used when firewalld is not running, i.e. when migrating configurations or installing Linux with Kickstarter. The syntax is obviously very similar to that of firewall-cmd so you should get a grip on it almost immediately.

The firewallctl has exactly the same purpose as firewall-cmd but its syntax differs somewhat. Using one or the other is a matter of personal preference.

The firewall-config is a GUI with which we can do most of the stuff we can with firewall-cmd. It might not come by default with your installation so you would need to install it with:

dnf -y install firewall-config 

<< iptables           LUKS encrypted file systems >>