Arch Linux-based Home Router, Part III (firewalld configuration)
Configuring the firewall is of utmost importance in protecting your network! There are many options for setting up the firewall. The venerable iptables is now deprecated by the newer nftables framework. Both of these directly configure the kernel's netfilter subsystem, and they have many CLI and GUI frontends. Using iptables
or nft
directly can be quite difficult to get right, so using a frontend or some kind of abstraction is recommended (unless your goal is to become a netfilter expert). I used to use fwbuilder to graphically configure my Debian firewall which would then compile and install to iptables rules automatically.
I decided to use firewalld because my employer's new on-premises systems use it on their Red Hat Enterprise Linux (RHEL7) appliances. Installing it at home forced me to become more familiar with it. Now that I have it installed, I quite like the firewall abstraction firewalld provides.
The rest of these instructions assume that firewalld.service
is enabled and started. A quick way to do that is:
# systemctl enable --now firewalld.service
Alternatively, you could use the firewall-offline-cmd
instead of firewall-cmd
for the next few sections, to configure the firewall while firewalld isn't running. However, if this router is plugged up directly to the WAN, I recommend running firewalld, as it has sane defaults, and your network will be protected even if you haven't fully configured it yet.
Note, while you're configuring firewalld you may accidentally allow traffic that you do not want to allow. You can shut down all traffic by issuing the following command:
# firewall-cmd --panic-on
To disable panic mode, and restore the permanent configuration, use the --panic-off
option.
firewalld Zones
firewalld introduces the concept of "zones," which can be used to logically separate parts of your network. firewalld comes with a set of zones by default, you can see these zones with the firewall-cmd --get-zones
command:
# firewall-cmd --get-zones
block dmz drop external home internal iot public trusted work
Most of these are default zones, except for iot
. I only use the home
, iot
, and public
zones, so that's all I will configure here. If you wanted to have a proper DMZ you can use the dmz
zone, or if you want to add specific hosts or IP addresses to your trusted
zone, you could do so.
Note, when specifying commands with firewall-cmd
, if you do not specify a zone it will be assumed you are operating on the default zone, which starts out as the public
zone. You can use the --get-default-zone
to determine the default zone.
Also, firewalld has the concept of runtime versus permanent configuration. When you use firewall-cmd
, it configures the runtime configuration unless you use the --permanent
flag. Note that if you use the --permanent
flag, the configuration is NOT active until you reload firewalld (i.e., firewall-cmd --reload
). Alternatively, you can load the configuration only in runtime, and use the --runtime-to-permanent
flag to store the current runtime configuration to disk so it will survive a reload, restart, or reboot.
For these instructions I will only configure the permanent configuration, and finally load them into runtime with firewall-cmd --reload
. Again, this uses the --permanent
flag. Some commands can only be applied to the permanent configuration, so in my opinion it is wise to make every change permanent, then reload as necessary. If you only apply the changes in runtime (i.e., without the --permanent
flag), you run the risk of losing some of these permanent-only options if you use the flag --runtime-to-permanent
.
public Zone
To display the current configuration, use the --info-zone=public
option.
# firewall-cmd --info-zone=public
public (active)
target: default
icmp-block-inversion: no
interfaces: enp3s0
sources:
services:
ports:
protocols:
forward: no
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
The main thing to pay attention to starting out is the interfaces:
section. If this section does not list only your WAN interface (in my case enp3s0
), you can delete the interface listed and add the WAN interface (replace enp4s0
with the incorrect interface):
# firewall-cmd --zone=public --remove-interface=enp4s0 --permanent
# firewall-cmd --zone=public --add-interface=enp3s0 --permanent
# firewall-cmd --reload
If these commands are successful, the only output you will see is success
. Otherwise it will display an error. You may also see some services that are open by default. You can remove the default services using the following command:
# firewall-cmd --zone=public --remove-service=<service> --permanent
# firewall-cmd --reload
You can see which services are available by default, by using the --get-services
option. To see the services that are loaded, use --list-services
.
Next, we need to enable the masquerade options. This will enable NAT (Network Address Translation) between the WAN and LAN:
# firewall-cmd --zone=public --add-masquerade --permanent
# firewall-cmd --reload
One final part of the public
zone, the target is set as default
, but could be ACCEPT
, DROP
, or REJECT
. The default
target is similar to REJECT
, except it implicitly allows ICMP traffic (ping, traceroute, etc.). The target is the rule that applies when no other service, port, rich rule, or direct rule applies. For the public zone, this means if a port or service is not explicitly allowed, the router will respond with connection refused. You may want to set this to DROP
, using the following command:
# firewall-cmd --zone=public --set-target=DROP --permanent
# firewall-cmd --reload
Note, setting the target can only be done to the permanent configuration. The above example also calls firewall-cmd --reload
, so runtime and permanent are in sync. If you don't use the --permanent
flag and then --reload
, be careful that --runtime-to-permanent
doesn't lose the desired target!
You may consider adding some port forwarding rules, so devices outside your network can access devices within your network. For instance, I set up 61987/tcp
to forward to 10.20.30.87
on 22/tcp
(the standard SSH port). First, I use the --add-port=61987/tcp
, and then set a forward port rule with --add-forward-port
:
# firewall-cmd --add-port=61987/tcp --add-forward-port=port=61987:proto=tcp:toport=22:toaddr=10.20.30.87 --permanent
# firewall-cmd --reload
I did the same for 32400/tcp
, for Plex Media Server. I also added the 31987/udp
for WireGuard. By this point, the public
zone should look something like this:
# firewall-cmd --info-zone=public
public (active)
target: default
icmp-block-inversion: no
interfaces: enp3s0
sources:
services:
ports: 61987/tcp 32400/tcp 31987/udp
protocols:
forward: no
masquerade: yes
forward-ports:
port=61987:proto=tcp:toport=22:toaddr=10.20.30.87
port=32400:proto=tcp:toport=32400:toaddr=10.20.30.87
source-ports:
icmp-blocks:
rich rules:
home Zone
I've set the home
zone as my primary LAN. To display the setup of this zone, use the following command:
# firewall-cmd --info-zone=home
home (active)
target: ACCEPT
icmp-block-inversion: no
interfaces: enp4s0
sources:
services: dhcpv6-client dns mdns samba-client ssh
ports:
protocols:
forward: no
masquerade: yes
forward-ports:
source-ports:
icmp-blocks:
rich rules:
The main differences between the home
zone and the public
zone are the target
(ACCEPT
rather than default
), the interfaces
(enp4s0
rather than enp3s0
), and the list of predefined services which are explicitly allowed. I want to be able to SSH into my router directly from home
, so the ssh
service explicitly allowed. Use the following command to enable this:
# firewall-cmd --zone=home --add-service=ssh --permanent
# firewall-cmd --reload
SSH listens on TCP port 22 by default, and enabling the ssh
service this port will be allowed. You can do the same for the other services, but it's not strictly necessary for the home
zone as the target
(default rule) is to allow connections if not explicitly defined. This will allow other services (such as dhcp
) work even though they're not explicitly allowed. You can set the default target to DROP
or REJECT
to add an extra layer of security, but then you'd have to list all router services explicitly.
iot Zone
The final zone that I set up is the iot
zone, which is not a predefined zone that ships with firewalld. First, I create the zone:
# firewall-cmd --permanent --new-zone=iot
It is an error not to use the --permanent
flag with --new-zone
. To make the zone active, you will need to add an interface (or a source) before reloading the firewalld configuration. Do this with --add-interface
, as above. I also added the source network for my iot
zone (172.19.87.0/24
). Next I added the dns
and dhcp
services, since most IoT devices will need to access these on the router. Finally, set the masquerade option to allow hosts in the iot
zone to access the Intenet
# firewall-cmd --zone=iot --add-interface=enp4s0.66 --permanent
# firewall-cmd --zone=iot --add-source=172.19.87.0/24 --permanent
# firewall-cmd --zone=iot --add-service={dhcp,dns} --permanent
# firewall-cmd --zone=iot --add-masquerade --permanent
# firewall-cmd --reload
The final step is to ensure the iot
zone cannot access the home
zone. This is achievable by using a firewalld policy. Before I learned about firewalld policies, I had used a direct rule, which requires an understanding of the underlying netfilter subsystem (think iptables
or the newer nft
). Direct rules aren't usually necessary, because rich rules can cover most situations. Direct rules require knowledge of the underlying netfilter subsystem, and its tables, chains and rules. Thus knowledge of iptables or nftables is a requirement for direct rules. Direct rules have been deprecated upstream by the firewalld maintainers (likely Red Hat). So using direct rules may no longer be an option with the lates releases of firewalld. Originally I tried to do this with rich rules, but that didn't quite work. When I was troubleshooting after my initial draft of this article, I learned that firewalld policies are the way to go.
Setting up a firewalld policy is fairly straightforward. One thing to note, firewalld zones and policies share the same namespace, so the policy cannot have the same name as a zone. Sine my iot
zone was already used (with a direct rule), I named the new policy iot-policy
. Policies have a concept of ingress (or input) zones, and egress (or output) zones. The ingress and egress zones can be named, non-symbolic zones (in my case such as home
and iot
), or special symbolic zones (such as HOST
[meaning the router running firewalld], or ANY
, matching any zone). More information can be found on the firewalld.policies(5) manual page. I also had to set the target of the iot
zone to ACCEPT
, or else hosts on the IoT subnet would not have Internet access. If you want tighter security, you can set this to DROP
or REJECT
.
# firewall-cmd --permanent --new-policy=iot-policy
# firewall-cmd --permanent --policy=iot-policy --add-ingress-zone=iot
# firewall-cmd --permanent --policy=iot-policy --add-egress-zone=home
# firewall-cmd --permanent --policy=iot-policy --set-target=DROP
# firewall-cmd --permanent --zone=iot --set-target=ACCEPT
# firewall-cmd --reload
The IoT zone should now look something like this:
# firewall-cmd --info-zone=iot
iot (active)
target: ACCEPT
icmp-block-inversion: no
interfaces: enp4s0.66
sources: 172.19.87.0/24
services: dhcp dns
ports:
protocols:
forward: no
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
And anything on the IoT subnet should not be able to reach anything on my LAN, unless it also has an interface on the IoT subnet.
firewalld conclusion
firewalld is highly configurable, I've only just scratched the surface with its capabilities. The permanent configuration is stored on disk in /etc/firewalld/
, and the configuration is stored in XML files. That's basically how I initially set up firewalld on the new Arch Linux OS, I simply copied over this directory from my backup of Debian, and then restarted firewalld.
NEXT STEPS
The following articles continue this series on setting up an Arch Linux-based Home Router:
- Arch Linux-based Home Router, Part I
- Arch Linux-based Home Router, Part II (systemd-networkd and sysctl/kernel)
- Arch Linux-based Home Router, Part III (firewalld configuration) (This article)
- Arch Linux-based Home Router, Part IV (dhcpd configuration)
- Arch Linux-based Home Router, Part V (bind)
- Arch Linux-based Home Router, Part VI (DDNS)
Comments ()