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.serviceNow 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!