How to setup fwknop to work with Mail-in-a-Box

Mail-in-a-Box provides a robust network security with fail2ban and ufw out of the box.

However I do not like multiple sshd entries in my LogWatch (yes! SourceForge is alive in 2025!), so I decided to protect SSH with fwknop - a free and open source software that supports Single Packet Authorization.

During the client and server setup I mostly followed the fwknop tutorial.

Few notes related to Mail-in-a-Box and MacOS

Mail-in-a-Box

Mail-in-a-Box uses Ubuntu, the name of the fwknop package there is fwknop-server.

So sudo apt-get installfwknop-server and sudo systemctl enablefwknop-server.service should do it for you.

MacOS

fwknop is available in HomeBrew: brew install fwknop

 

Testing

After your client and server are set up, before limiting access to port 22, verify that the setup is working: fwknop -n box.example.com --wget-cmd /opt/homebrew/bin/wget

The client will not tell you anything but on the server in the FWKNOP_INPUT table you should be able to see the result: sudo iptables -L FWKNOP_INPUT -n -v

Chain FWKNOP_INPUT (1 reference)
pkts bytes target     prot opt in     out     source               destination        
    0     0 ACCEPT     tcp  --  *      *       1.2.3.4       0.0.0.0/0            tcp dpt:22 /* _exp_<timestamp> */

If you see the above result (in the example 1.2.3.4 is your IP address), you are ready to limit connectivity on the server side.

Making it work together

To have fwknop in parallel and in collaboration with Mail-in-a-Box rules, we need to setup fwknop outside of Mail-in-a-Box configs, which are overwritten during updates.

We can inject iptables rules after all relevant Mail-in-a-Box components have started: sudo vim /usr/local/sbin/fwknop-iptables.sh

#!/bin/bash

# Ensure FWKNOP_INPUT exists
iptables -L FWKNOP_INPUT -n || iptables -N FWKNOP_INPUT

# First check if rules exist, then add if they don't exist

# Allow established connections to live:
iptables -C FWKNOP_INPUT -p tcp --dport 22 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT || \
        iptables -A FWKNOP_INPUT -p tcp --dport 22 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Jump all SSH traffic to FWKNOP_INPUT table first:
iptables -C INPUT -p tcp --dport 22 -j FWKNOP_INPUT || \
        iptables -A INPUT 1 -p tcp --dport 22 -j FWKNOP_INPUT

# Allow server to connect to iself (Mail-in-a-Box does some checks)
iptables -C FWKNOP_INPUT -s 127.0.0.1/32 -p tcp --dport 22 -j ACCEPT || \        iptables -I FWKNOP_INPUT -s 127.0.0.1/32 -p tcp --dport 22 -j ACCEPT

# Drop any SSH connections that were not allowed in FWKNOP_INPUT table,
# we need this to override other instructions that are added to iptables:
iptables -C INPUT -p tcp --dport 22 -j DROP || \
        iptables -I INPUT 2 -p tcp --dport 22 -j DROP

Don't forget to make the file executable: sudo chmod +x /usr/local/sbin/fwknop-iptables.sh

Caution! You might lock yourself out of the machine, so make sure that you have other means to access it if anything goes wrong!

Now you can test the rules: sudo /usr/local/sbin/fwknop-iptables.sh

First run (means that the FWKNOP_INPUT table is not activated yet and the rules do not exist):

Chain FWKNOP_INPUT (1 reference)
pkts bytes target     prot opt in     out     source               destination        
iptables: Bad rule (does a matching rule exist in that chain?).

iptables: Bad rule (does a matching rule exist in that chain?).
iptables: Bad rule (does a matching rule exist in that chain?).

On consecutive runs the output will be the following, which means that all rules are populated:

Chain FWKNOP_INPUT (2 references)
target     prot opt source               destination        
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:22 ctstate RELATED,ESTABLISHED
ACCEPT  tcp opt -- in * out *  0.0.0.0/0  -> 0.0.0.0/0   tcp dpt:22 ctstate RELATED,ESTABLISHED
FWKNOP_INPUT  tcp opt -- in * out *  0.0.0.0/0  -> 0.0.0.0/0   tcp dpt:22
DROP  tcp opt -- in * out *  0.0.0.0/0  -> 0.0.0.0/0   tcp dpt:22

Automatic execution on system boot

To make Ubuntu run the script for us we can create a service for systemd: sudo vim /etc/systemd/system/fwknop-iptables.service

[Unit]
Description=Apply custom iptables rules for FWKNOP for compatibility with Mail-in-a-Box system
After=fwknop-server.service fail2ban.service ufw.service
Requires=fwknop-server.service fail2ban.service ufw.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/fwknop-iptables.sh

[Install]
WantedBy=multi-user.target

To apply it to the system, run 2 commands: 

sudo systemctl daemon-reload

sudo systemctl enable fwknop-iptables.service

Created symlink /etc/systemd/system/multi-user.target.wants/fwknop-iptables.service → /etc/systemd/system/fwknop-iptables.service

Now you can open the SSH port for your IP address with the following command:

fwknop -n box.example.com --wget-cmd /opt/homebrew/bin/wget

Enjoy!