Blocking incoming hostile traffic dynamically with PF and fail2ban on OS X
The bigger picture is that I am trying to dynamically block/unblock IPs using fail2ban 0.8.14. I have been following an old guide for OS 10.5 with doesn’t include PF.
I think I’ve configured everything correctly, but OS X’s PF is causing me some issues.
Talaninc has the IP 192.168.1.68 and is the “local”
Celebgul has the IP 192.168.1.50 and is the “remote”
From what I understand, I should be able to issue the following command:
user@celebgul:~$ sudo /sbin/pfctl -t fail2ban -T add 192.168.1.68/32
This will add the IP address 192.168.1.68
to the table fail2ban
, and will block incoming traffic from Talantinc, thus the following should fail:
forquare@talantinc:~$ ssh [email protected]
Password:
Last login: Mon Aug 3 12:09:05 2015 from talantinc.local
user@celebgul:~$
But as you can see, it succeeds.
From what I can see, my understanding is wrong. I’ve added 192.168.1.68
, and yet can still SSH from it to Celebgul.
I can query the table and see 192.168.1.68
in it:
user@celebgul:~$ sudo pfctl -t fail2ban -T show
No ALTQ support in kernel
ALTQ related functions disabled
192.168.1.68
I haven’t changed any of Apple’s default configuration files for PF, my understanding was that I’d only need to edit/create my own if I wanted rules that were longstanding/persisted across reboots.
How can I use PF to block incoming connections on OS X Client (not Server)?
I’m specifically looking at El Capitan, however I cannot get this to work on Mavericks either.
Solution 1:
Here is an improved walkthrough to install fail2ban on OS X 10.10 (it probably works on 10.9 also) based on the (somehow faulty) guide at forgetcomputers.zendisk.com.
The automated installer didn't work at all for me so I did it manually.
-
cd to ~/Downloads and download fail2ban-0.8.10
cd ~/Downloads curl -O https://forgetcomputers.zendesk.com/hc/en-us/article_attachments/200287990/fail2ban-0.8.10.tar.gz
-
Unpack the tar package:
tar xzf fail2ban-0.8.10.tar.gz
-
cd to fail2ban-0.8.10 and install the software:
cd fail2ban-0.8.10/ sudo python setup.py install
-
Make a file for the log:
sudo touch /var/log/fail2ban.log
-
cd back and download the modifications package:
cd ~/Downloads/ curl -O https://forgetcomputers.zendesk.com/hc/en-us/article_attachments/200287980/install_fail2ban_mods.tar.gz
-
Unpack this package:
tar xzf install_fail2ban_mods.tar.gz
-
Run the install script from the modifications package:
sudo ./fail2ban_mods/install_fail2ban_mod.sh
-
Make yourself sudo and rename /etc/fail2ban/jail.local (the file jail.local is superior to jail.conf and might break everything because the installed file contains a totally useless configuration):
sudo bash mv /etc/fail2ban/jail.local /etc/fail2ban/jail.local.bak
-
Add the following two lines to /etc/pf.conf with
nano /etc/pf.conf
:table <fail2ban> persist block drop log quick from <fail2ban> to any
-
In /etc/fail2ban/jail.conf, modify the [ssh-pf] section at the end with
nano
as follows:[ssh-pf] enabled = true filter = sshd action = pf logpath = /var/log/system.log maxretry = 3
You may enter another maxretry count or define an individual bantime or findtime.
-
In /etc/fail2ban/action.d/pf.conf, ensure that the following values are set and modify them if necessary with
nano /etc/fail2ban/action.d/pf.conf
:actionban = /sbin/pfctl -t fail2ban -T add <ip> actionunban = pfctl -t fail2ban -T delete `pfctl -t fail2ban -T show 2>/dev/null | grep <ip>` [Init] tablename = fail2ban localhost = 127.0.0.1
-
Shutdown pf, tell it to reload its configuration, and start it again:
pfctl -d pfctl -f /etc/pf.conf pfctl -e
-
Stop the fail2ban daemon if it is already running, and start it with launchctl:
fail2ban-client stop launchctl load -w /Library/LaunchDaemons/org.fail2ban.init.plist launchctl load -w /Library/LaunchDaemons/org.fail2ban.redo.plist launchctl load -w /Library/LaunchDaemons/org.fail2ban.reset.plist
Testing the System
-
Open a terminal window and watch fail2ban's log (live-update):
sudo tail -f /var/log/fail2ban.log
-
While keeping this terminal active on the server, SSH into the server from a client and watch the server's terminal output (username is arbitrary, since we are testing what will happen when an incorrect login is attempted; replace server_ip with the IP address or hostname of the server):
ssh username@server_ip
-
On the client machine, type the wrong password several times until you see a message in fail2ban's log that indicates that the client has been banned. This message will look something like this:
2015-08-04 18:56:25,001 fail2ban.actions [216]: NOTICE [ssh-pf] Ban 192.168.8.15
When you see this message, the client machine's IP has been banned. At this point, any future SSH attempts from this IP (within fail2ban's bantime period) should time-out and be unsuccessful.
To stop tail just enter ctrlC
If you want to install the latest fail2ban 0.9.1
Download manually fail2ban to your ~/Downloads folder
-
cd to ~/Downloads and unpack the tar package:
cd ~/Downloads tar xzf fail2ban-0.9.1.tar.gz
-
cd to fail2ban-0.9.1 and install the software:
cd fail2ban-0.9.1/ sudo python setup.py install
-
Make a file for the log:
sudo touch /var/log/fail2ban.log
-
cd back and download the modifications package:
cd ~/Downloads/ curl -O https://forgetcomputers.zendesk.com/hc/en-us/article_attachments/200287980/install_fail2ban_mods.tar.gz
-
Unpack this package:
tar xzf install_fail2ban_mods.tar.gz
-
Make a backup of /etc/fail2ban/filter.d/sshd.conf and copy the file from the mod_pack to the fail2ban/filter.d directory. You may copy the other filter.conf but better make a backup of the original files. I didn't test these though.
sudo bash mv /etc/fail2ban/filter.d/sshd.conf /etc/fail2ban/filter.d/sshd.conf.old cp ~/Downloads/fail2ban_mods/filter.d/sshd.conf /etc/fail2ban/filter.d/ cp ~/Downloads/fail2ban_mods/fail2ban_reset.sh /private/etc/fail2ban cp ~/Downloads/fail2ban_mods/lib-launchdaemons/org.fail2ban* /Library/LaunchDaemons
-
Add the following two lines to /etc/pf.conf with
nano /etc/pf.conf
:table <fail2ban> persist block drop log quick from <fail2ban> to any
-
In /etc/fail2ban/jail.conf, modify the [ssh-pf] section at the end with
nano
as follows:[ssh-pf] enabled = true filter = sshd action = pf logpath = /var/log/system.log maxretry = 3
You may enter another maxretry count or define an individual bantime or findtime.
-
In /etc/fail2ban/action.d/pf.conf, ensure that the following values are set and modify them if necessary with
nano /etc/fail2ban/action.d/pf.conf
:actionban = /sbin/pfctl -t fail2ban -T add <ip>/32 actionunban = /sbin/pfctl -t fail2ban -T delete <ip>/32 [Init] tablename = fail2ban
-
Shutdown pf, tell it to reload its configuration, and start it again:
pfctl -d pfctl -f /etc/pf.conf pfctl -e
-
Stop the fail2ban daemon if it is already running and start it again:
fail2ban-client stop launchctl load -w /Library/LaunchDaemons/org.fail2ban.init.plist launchctl load -w /Library/LaunchDaemons/org.fail2ban.redo.plist launchctl load -w /Library/LaunchDaemons/org.fail2ban.reset.plist
Now test your system again like described above.
Improvements
Since the implementation of the fail2ban table and rule is non-standard and real fail2ban-blocking doesn't survive a reboot, I reworked the whole pf-mechanism, created a separate anchor (inspired by IceFloor) to remove any dependency on Apple's /etc/pf.conf file and modified the ban action.
-
Starting from the fail2ban 0.9.1 install described above enter:
sudo bash nano /etc/fail2ban/action.d/pf.conf
and change in the file /etc/fail2ban/action.d/pf.conf the lines starting with
actionban = ....
to
actionban = /sbin/pfctl -a fail2ban.anchor -t fail2ban -T add <ip>/32 && /sbin/pfctl -k <ip>/32 && /sbin/pfctl -Ef /etc/fail2ban/pf/fail2ban.conf
and
actionunban = ....
to
actionunban = /sbin/pfctl -a fail2ban.anchor -t fail2ban -T delete <ip>/32
-
Create a folder pf in /etc/fail2ban/
mkdir /etc/fail2ban/pf
-
Create three files fail2ban, fail2ban.conf and fail2ban.sh in the previously made folder with the following content; afterwards make fail2ban.sh executable with chmod:
fail2ban:
table <fail2ban> persist block drop log quick from <fail2ban> to any
fail2ban.conf:
############### LOOPBACK ############### # # skip loopback (no filtering on loopback interface) set skip on lo0 scrub-anchor "com.apple/*" ############### INBOUND ############### # anchor "fail2ban.anchor" load anchor "fail2ban.anchor" from "/etc/fail2ban/pf/fail2ban"
fail2ban.sh:
#!/bin/sh # start # # We need to trap on TERM signals, according to Apple's launchd docs: # trap 'exit 1' 15 # # Use the "ipconfig waitall" command to wait for all the interfaces to come up: # ipconfig waitall sleep 5 # # System sysctl # sysctl -w net.inet6.ip6.fw.verbose=0 sysctl -w net.inet.ip.fw.verbose=0 sysctl -w net.inet.ip.fw.verbose_limit=0 # # interface forwarding enabled by default # sysctl -w net.inet.ip.forwarding=1 # enable PF and load rules from default fail2ban configuration file using tokens (apple specific PF options -E and -X) # /sbin/pfctl -e /sbin/pfctl -Ef /etc/fail2ban/pf/fail2ban.conf # Exit with a clean status exit 0 # this file is public domain and is available to everyone with no exceptions.
-
Create the file org.fail2ban.plist in /Library/LaunchDaemon with the following content:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Disabled</key> <false/> <key>ExitTimeOut</key> <integer>1</integer> <key>Label</key> <string>org.fail2ban</string> <key>Program</key> <string>/etc/fail2ban/pf/fail2ban.sh</string> <key>RunAtLoad</key> <true/> </dict> </plist>
-
Remove the following two lines from /etc/pf.conf with
nano /etc/pf.conf
:table <fail2ban> persist block drop log quick from <fail2ban> to any
-
Load the file /Library/LaunchDaemons/org.fail2ban.plist with launchctl after stopping fail2ban and pf:
fail2ban-client stop pfctl -d pfctl -f /etc/pf.conf launchctl load -w /Library/LaunchDaemons/org.fail2ban.plist fail2ban-client start
or reboot your Mac after loading org.fail2ban.plist with launchctl.
Now banning works properly even after rebooting your system. If you want to add an IP-address manually to block it, just enter:
sudo /sbin/pfctl -a fail2ban.anchor -t fail2ban -T add <ip>/32 && /sbin/pfctl -k <ip>/32 && /sbin/pfctl -Ef /etc/fail2ban/pf/fail2ban.conf
To un-ban it (it will not automatically be un-banned after the fail2ban bantime!) enter:
sudo /sbin/pfctl -a fail2ban.anchor -t fail2ban -T delete <ip>/32
So to answer your question:
If you want to use the fail2ban table to ban an IP manually after applying the improvements, you have to enter the command above. The reason for the additional parts:
-
/sbin/pfctl -k <ip>/32
is needed to kill all of the state entries originating from the specified host. -
/sbin/pfctl -Ef /etc/fail2ban/pf/fail2ban.conf
to reload the fail2ban.conf gracefully and reflect the changes you made in the table fail2ban.
Without the above improvements you may use:
sudo /sbin/pfctl -t fail2ban -T add <ip>/32 && /sbin/pfctl -k <ip>/32 && /sbin/pfctl -Ef /etc/pf.conf
Installation of fail2ban 0.9.1 on El Capitan
I got it installed on El Capitan in rootless mode (sudo nvram boot-args="rootless=0"
and reboot) after removing the doc-install part to /usr/share/docs/fail2ban (= the lines 140-143) in the setup.py of fail2ban 0.9.1.
Use the improved 0.9.1 install and config method. If the command pfctl -sA
doesn't reveal fail2ban.anchor check for the correct double-quotes (no smart-quotes!) in the file /etc/fail2ban/pf/fail2ban.conf.
Solution 2:
Installing Server.app has several benefits for traditional UNIX hands since OS X makes configuration choices on the client OS that don't work well for people that live in the command line world. (Max files per process, VM tuning, other various kernel tuning changes)
Barring an easy solution, I would recommend installing Server and then enabling OS X adaptive firewall to ensure you have the proper launchd services enabled.
- https://support.apple.com/en-us/HT200259
In a nutshell, the above article asks for:
sudo pfctl -f /etc/pf.conf
sudo /Applications/Server.app/Contents/ServerRoot/usr/sbin/serverctl enable service=com.apple.afctl
sudo /Applications/Server.app/Contents/ServerRoot/usr/libexec/afctl -c
sudo /Applications/Server.app/Contents/ServerRoot/usr/libexec/afctl -f
Followed by step 2:
sudo defaults write /System/Library/LaunchDaemons/com.apple.pfctl ProgramArguments '(pfctl, -f, /etc/pf.conf, -e)'
sudo chmod 644 /System/Library/LaunchDaemons/com.apple.pfctl.plist
sudo plutil -convert xml1 /System/Library/LaunchDaemons/com.apple.pfctl.plist
From there, you can then use the pfctl / afctl framework to block hosts and ports as needed. You can test things by trying ssh brute force attacks and seeing that the firewall is working (or is not) before trying to customize the firewall further.
Solution 3:
I was missing a piece of the puzzle that klanomath kindly filled by linking to this “Setting up Fail2ban on Mac OS X 10.7+” post, and Buscar웃 reinforced with this “Integrating PF with Fail2ban 0.9” post.
While PF was adding the IP address to the fail2ban
table, it didn’t know what to do with it. There are a couple of ways to achieve this:
The first post linked above suggests modifying /etc/pf.conf
and adding the following:
table <fail2ban> persist
block drop log quick from <fail2ban> to any
The second post suggests a more correct route, creating a new “anchor” file /etc/fail2ban/pf-anchor.conf
and inserting the following:
table <fail2ban> counters
block drop log quick from <fail2ban> to any
It then references this anchor in /etc/pf.conf
by inserting:
anchor fail2ban
I haven’t verified the second method, but my initial testing with the first method has solved my issue with PF. Thank you to klanomoath and Buscar웃 for posting links to relevant guides, either my search queries were way off or it’s the first time DuckDuckGo has let me down.