Up to now I have always been using the Shorewall firewall on all my Linux systems. I find it very easy to configure while at the same time it’s very powerful and flexible so that you can also use it with more complicated set-ups, such as routers with multiple network interfaces, VPN’s and bridges. Unfortunately Shorewall is still based on the old xtables (iptables, ip6tables, ebtables, etc…) infrastructure. While it still works and in reality the iptables commands are actually now front-ends to the more modern nftables back-end, Shorewall development has stalled and it looks very unlikely it will ever be ported to nftables.
I started using Firewalld, a firewall which is used by default on Red Hat and Fedora based systems. However I did not like it. Configuration of Firewalld happens through the command line with firewall-cmd
, which I find much more complicated than just editing a configuration file which usually contains examples and gives you an easy overview of the configuration. Firewalld saves its configuration in XML files. You could edit these files instead of using firewall-cmd
, but that is obviously much more complicated than editing configuration files which were designed for human editing. Furthermore I found Firewalld to be very inflexible. Firewalld does not have support of filtering traffic on a bridge (layer 2 filtering), unlike Shorewall.
Recently I discovered the nftables based firewall foomuuri. It’s still a very young project but it’s actively developed, already has extensive features, is packaged in Debian and is configured through human-readable configuration files. I decided to try it on a server where I wanted to filter incoming and outgoing network traffic.
Installing Foomuuri on Debian
Foomuuri is availabe in Debian testing and unstable, but it has also been backported to Debian 12 Bookworm. To use that package, you have to enable the bookworm-backports repository first. Then install the foomuuri package
# apt install foomuuri
If you are using NetworkManager also install foomuuri-firewalld
, because it will allow NetworkManager to set the zone the network interface belongs to.
Configuring Foomuuri
Foomuuri can be configured through files in the /etc/foomuuri directory. Foomuuri will read all files which name ends with .conf, so you can split up the configuration in as many files as you want or just put everything in a single file, as you prefer. I like the split configuration files of Shorewall, so I will do something similar here.
Before activating the configuration, always run
# foomuuri check
to validate your configuration. You can start and stop the firewall by starting and stopping the systemd service, you can reload the configuration by running
# foomuuri reload
You can find the documentation of Foomuuri on the Foomuuri wiki.
Defining zones
The first ting we have to do is define the zones and set which interfaces belongs to which zone. I create /etc/foomuuri/zones.conf:
zone {
localhost
public enp1s0
}
I create the zone localhost and the zone public and add the network interface enp1s0 to it. You can add multiple interfaces to a zone by separating them by spaces. If you are using NetworkManager, you don’t have to add the interfaces here and can leave the zone empty. You can configure the firewall zone in NetworkManager and it will set it through foomuuri-firewalld.
Using macros to alias configuration options
Macros can be used to define certain configuration options you want to use multiple times without having to write them completely every time. In practice a lot of macros are already configured which define the configuration for common services. You can see all defined macros by running
# foomuuri list macro
For example the macro imap
defines the configuration tcp 143
, so that you can just write imap
instead of tcp 143
in the configuration. I added a few which were not defined by default in /etc/foomuuri/services.conf:
macro {
nrpe tcp 5666
nmb udp 137 138 139; tcp 139
}
Macros can be used to configure common subnets. For example I have a file named /etc/foomuuri/subnets.conf:
macro {
mysubnet 192.168.0.1/24
othersubnet 192.168.1.1/24
}
I also use macros to create lists of individual hosts, such as all NFS clients which need to access this NFS server in /etc/foomuuri/nfs_clients.conf
macro {
nfs_clients 192.168.0.1 # web server
nfs_clients + 192.168.0.2 # gitlab
nfs_clients + 192.168.0.3 # nextcloud
}
For easy readability, I put every host in a single line, and I add a comment for my own reference. With the +
sign I add all next hosts to the macro.
Firewall for incoming connections
To configure Foomuuri to filter incoming connections to my servers, I create a section public-localhost
which contains the firewall rules for traffic coming from the public zone to localhost. I put this in the file /etc/foomuuri/public-localhost.conf:
public-localhost {
dhcp-server
ssh
ping saddr mysubnet
nmb saddr mysubnet
smb saddr mysubnet
nfs saddr nfs_clients
nrpe saddr 192.168.0.5
drop log
}
My server is acting as a DCHP-server, so I use the dhcp-server
macro to allow all this traffic, just as I allow all incoming ssh
traffic. I allow ping
, nmb
and smb
traffic from mysubnet
. Notice that in these rules I use my custom macros nmb
and mysubnet
. Then I allow nfs
from all addresses listed in my macro nfs_clients
, and I allow nrpe
from a specific IP address. Finally I end with a rule which drops and logs all traffic which has not matched any of the rules before.
Firewall for outgoing connections
I think that filtering outgoing connections is a very effective security hardening measure. In case people with bad intentions get access to your server through a non-root user account, this will severely limit their abilities to move laterally through your network and attack other systems, to run a crypto-miner, or download malware from the Internet.
localhost-public {
dhcp-client
nmb uid root
ntp uid systemd-timesync
ping uid root daddr mysubnet # dhcpd sometimes pings
smtp daddr 192.168.0.1 uid postfix
domain daddr 192.168.0.255 192.168.0.254
uid root tcp daddr 192.168.0.5 dport 8140 # puppet agent
uid _apt tcp dport 3142 daddr 192.168.0.6
uid root ssh daddr 192.168.0.250 # backups
drop daddr 169.254.169.254 tcp dport 80 # don't fill logs with Puppetlabs facter trying to collect facts from Amazon EC2/Azure
reject log
}
I allow outgoing connections for different services, and for most services I set the user which can create that connection, and to which host I allow the connection. I explicitly drop without logging connections to 169.254.169.254 port 80, because facter tries to connect to this address every time it runs in order to get some metadata from your cloud service provider. If your system is running on Amazon or Microsoft Azure cloud services, you will probably want to allow this connection instead, so you can then just remove the drop
word.
In order to log the UID of the process which tried to establish a rejected connection, in future Foomuuri versions (starting from Foomuuri version 0.22) you can replace the last rule by
reject log log_level "level warn flags skuid"
In current version 0.21, it is possible by setting this globally for all connections. I created /etc/foormuuri/loglevel.conf:
foomuuri {
log_level "level info flags skuid"
}
Integrating Fail2ban with Foomuuri
I found inspiration for integrating Fail2ban with Foomuuri in issue 9 on the Foomuuri issue tracker.
Create /etc/fail2ban/action.d/foomuuri with these contents:
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = /usr/sbin/foomuuri iplist add fail2ban 999d <ip>
actionunban = /usr/sbin/foomuuri iplist del fail2ban <ip>
actionflush = /usr/sbin/foomuuri iplist flush fail2ban
Then set foomuri
as the default banaction
by creating /etc/fail2ban/jail.d/foomuri.conf:
[DEFAULT]
banaction = foomuuri
Then foomuuri should create the fail2ban iplist. We can configure it to so by creating /etc/foomuuri/fail2ban.conf:
iplist {
@fail2ban
}
Then I add this rule as first rule to the public-localhost
section:
saddr @fail2ban drop log fail2ban drop
This will drop all connections coming from an address in the iplist fail2ban
, and will also log them with prefix fail2ban
. If you don’t want this to be logged, just remove log fail2ban
.
To ensure that Foomuuri is started before Fail2ban, so that the fail2ban iplist exists before Fail2ban starts to use it, create
/etc/systemd/system/fail2ban.service.d/override.conf:
[Unit]
After=foomuuri.service
After making these changes, first restart Foomuuri and then Fail2ban.
Conclusion
I found Foomuuri easy to use for a system with one network interface. Configuration through the configuration files is easy, also when implementing filtering for outgoing packets. Even though Foomuuri is still a young project, it already has many features and its author is very reactive to discussions and issues on Github. I also found the documentation on the wiki very helpful
I will try to implement Foomuuri on more complex setups in the future, such as on a host for virtual machines of which the network interface is bridged to the main network interface of the host, VPN servers, routers, etc…
Finally I want to thank the Foomuri developer Kim B. Heino and the maintainer of the Debian package Romain Francoise for their work and making this available to the community.