Building on the Shoulders of Giants
My inspiration for using Fail2ban on OpenBSD was this post from Ismael J. Tisminetzky for OpenBSD 6.9. I was using sshguard, but I also wanted to protect other services like http and https, and sshguard is just not designed for that. I have been using fail2ban for a few years, and I just find it the right tool for the task of delaying script-kiddies-like attacks.
Thank you Ismael J. Tisminetzky!
Starting Point
The system is running OpenBSD 7.1, with nginx serving a typo3 site. with 4Gb RAM, 2 vCPUs. Not a great machine, but more than enough to play around. It's publicly accessible server, with the default ssh service which is constantly being hammered with all kinds of login attempts.
Fail2Ban Requirements
The current version hosted at Github is 1.0.2, and it requires:
- Python2 >= 2.7 or Python >= 3.2 or PyPy
- python-setuptools, python-distutils or python3-setuptools for installation from source
Optional:
- pyinotify >= 0.8.3, may require:
- Linux >= 2.6.13
- gamin >= 0.0.21
- systemd >= 204 and python bindings:
- dnspython
We first try it with python-3.10.8 but, after an unsuccessful attempt, I decide to try with python-3.9.15.
Bash should also be included in the requirements as most, if not all non-python scripts are for bash. We run, as root:
# pkg_add python-3.9.15
# ln -s /usr/local/bin/python3.9 /usr/local/bin/python
# git clone https://github.com/fail2ban/fail2ban.git
/usr/bin/bash
We need to add a link to bash on /usr/bin because the scripts we need to run expect it there.
# ln -s /usr/local/bin/bash /usr/bin/
Note: This may not be necessary, as we run all bash scripts by preceding the name of the script with "bash".
Locale
In one of my failed attempts, I observed some warnings about the locale. Unfortunately, the locale "en_IE.UTF-8" is not yet available for OpenBSD, so we set it to "en_GB.UTF-8" before running the setup script.
export LANG="en_GB.UTF-8"
Installing Fail2Ban
As recommended in multiple github issues, we better run the fail2ban-2to3 script from the cloned git repository:
# cd fail2ban
# bash fail2ban-2to3
RefactoringTool: Skipping optional fixer: buffer
RefactoringTool: Skipping optional fixer: idioms
RefactoringTool: Skipping optional fixer: set_literal
RefactoringTool: Skipping optional fixer: ws_comma
RefactoringTool: No changes to bin/fail2ban-client
RefactoringTool: No changes to bin/fail2ban-regex
RefactoringTool: No changes to bin/fail2ban-server
RefactoringTool: No changes to bin/fail2ban-testcases
RefactoringTool: No changes to fail2ban/__init__.py
RefactoringTool: No changes to fail2ban/exceptions.py
RefactoringTool: Refactored fail2ban/helpers.py
RefactoringTool: No changes to fail2ban/protocol.py
RefactoringTool: No changes to fail2ban/setup.py
RefactoringTool: No changes to fail2ban/version.py
RefactoringTool: No changes to fail2ban/client/__init__.py
RefactoringTool: Refactored fail2ban/client/actionreader.py
RefactoringTool: No changes to fail2ban/client/beautifier.py
RefactoringTool: Refactored fail2ban/client/configparserinc.py
RefactoringTool: Refactored fail2ban/client/configreader.py
RefactoringTool: No changes to fail2ban/client/configurator.py
RefactoringTool: Refactored fail2ban/client/csocket.py
RefactoringTool: Refactored fail2ban/client/fail2banclient.py
RefactoringTool: No changes to fail2ban/client/fail2bancmdline.py
RefactoringTool: No changes to fail2ban/client/fail2banreader.py
RefactoringTool: Refactored fail2ban/client/fail2banregex.py
RefactoringTool: No changes to fail2ban/client/fail2banserver.py
RefactoringTool: Refactored fail2ban/client/filterreader.py
RefactoringTool: Refactored fail2ban/client/jailreader.py
RefactoringTool: No changes to fail2ban/client/jailsreader.py
RefactoringTool: No changes to fail2ban/server/__init__.py
RefactoringTool: Refactored fail2ban/server/action.py
RefactoringTool: Refactored fail2ban/server/actions.py
RefactoringTool: Refactored fail2ban/server/asyncserver.py
RefactoringTool: Refactored fail2ban/server/banmanager.py
RefactoringTool: Refactored fail2ban/server/database.py
RefactoringTool: No changes to fail2ban/server/datedetector.py
RefactoringTool: No changes to fail2ban/server/datetemplate.py
RefactoringTool: Refactored fail2ban/server/failmanager.py
RefactoringTool: Refactored fail2ban/server/failregex.py
RefactoringTool: Refactored fail2ban/server/filter.py
RefactoringTool: No changes to fail2ban/server/filtergamin.py
RefactoringTool: Refactored fail2ban/server/filterpoll.py
RefactoringTool: Refactored fail2ban/server/filterpyinotify.py
RefactoringTool: Refactored fail2ban/server/filtersystemd.py
RefactoringTool: Refactored fail2ban/server/ipdns.py
RefactoringTool: Refactored fail2ban/server/jail.py
RefactoringTool: No changes to fail2ban/server/jails.py
RefactoringTool: No changes to fail2ban/server/jailthread.py
RefactoringTool: Refactored fail2ban/server/mytime.py
RefactoringTool: No changes to fail2ban/server/observer.py
RefactoringTool: Refactored fail2ban/server/server.py
RefactoringTool: Refactored fail2ban/server/strptime.py
RefactoringTool: Refactored fail2ban/server/ticket.py
RefactoringTool: Refactored fail2ban/server/transmitter.py
RefactoringTool: Refactored fail2ban/server/utils.py
RefactoringTool: No changes to fail2ban/tests/__init__.py
RefactoringTool: No changes to fail2ban/tests/actionstestcase.py
RefactoringTool: Refactored fail2ban/tests/actiontestcase.py
RefactoringTool: Refactored fail2ban/tests/banmanagertestcase.py
RefactoringTool: No changes to fail2ban/tests/clientbeautifiertestcase.py
RefactoringTool: Refactored fail2ban/tests/clientreadertestcase.py
RefactoringTool: Refactored fail2ban/tests/databasetestcase.py
RefactoringTool: Refactored fail2ban/tests/datedetectortestcase.py
RefactoringTool: No changes to fail2ban/tests/dummyjail.py
RefactoringTool: Refactored fail2ban/tests/fail2banclienttestcase.py
RefactoringTool: Refactored fail2ban/tests/fail2banregextestcase.py
RefactoringTool: Refactored fail2ban/tests/failmanagertestcase.py
RefactoringTool: Refactored fail2ban/tests/filtertestcase.py
RefactoringTool: Refactored fail2ban/tests/misctestcase.py
RefactoringTool: Refactored fail2ban/tests/observertestcase.py
RefactoringTool: Refactored fail2ban/tests/samplestestcase.py
RefactoringTool: Refactored fail2ban/tests/servertestcase.py
RefactoringTool: Refactored fail2ban/tests/sockettestcase.py
RefactoringTool: No changes to fail2ban/tests/tickettestcase.py
RefactoringTool: Refactored fail2ban/tests/utils.py
RefactoringTool: No changes to fail2ban/tests/action_d/__init__.py
RefactoringTool: No changes to fail2ban/tests/action_d/test_smtp.py
RefactoringTool: No changes to fail2ban/tests/files/ignorecommand.py
RefactoringTool: No changes to fail2ban/tests/files/action.d/action.py
RefactoringTool: No changes to fail2ban/tests/files/action.d/action_checkainfo.py
RefactoringTool: No changes to fail2ban/tests/files/action.d/action_errors.py
RefactoringTool: No changes to fail2ban/tests/files/action.d/action_modifyainfo.py
RefactoringTool: Refactored fail2ban/tests/files/config/apache-auth/digest.py
RefactoringTool: Files that were modified:
RefactoringTool: bin/fail2ban-client
RefactoringTool: bin/fail2ban-regex
RefactoringTool: bin/fail2ban-server
RefactoringTool: bin/fail2ban-testcases
RefactoringTool: fail2ban/__init__.py
RefactoringTool: fail2ban/exceptions.py
RefactoringTool: fail2ban/helpers.py
RefactoringTool: fail2ban/protocol.py
RefactoringTool: fail2ban/setup.py
RefactoringTool: fail2ban/version.py
RefactoringTool: fail2ban/client/__init__.py
RefactoringTool: fail2ban/client/actionreader.py
RefactoringTool: fail2ban/client/beautifier.py
RefactoringTool: fail2ban/client/configparserinc.py
RefactoringTool: fail2ban/client/configreader.py
RefactoringTool: fail2ban/client/configurator.py
RefactoringTool: fail2ban/client/csocket.py
RefactoringTool: fail2ban/client/fail2banclient.py
RefactoringTool: fail2ban/client/fail2bancmdline.py
RefactoringTool: fail2ban/client/fail2banreader.py
RefactoringTool: fail2ban/client/fail2banregex.py
RefactoringTool: fail2ban/client/fail2banserver.py
RefactoringTool: fail2ban/client/filterreader.py
RefactoringTool: fail2ban/client/jailreader.py
RefactoringTool: fail2ban/client/jailsreader.py
RefactoringTool: fail2ban/server/__init__.py
RefactoringTool: fail2ban/server/action.py
RefactoringTool: fail2ban/server/actions.py
RefactoringTool: fail2ban/server/asyncserver.py
RefactoringTool: fail2ban/server/banmanager.py
RefactoringTool: fail2ban/server/database.py
RefactoringTool: fail2ban/server/datedetector.py
RefactoringTool: fail2ban/server/datetemplate.py
RefactoringTool: fail2ban/server/failmanager.py
RefactoringTool: fail2ban/server/failregex.py
RefactoringTool: fail2ban/server/filter.py
RefactoringTool: fail2ban/server/filtergamin.py
RefactoringTool: fail2ban/server/filterpoll.py
RefactoringTool: fail2ban/server/filterpyinotify.py
RefactoringTool: fail2ban/server/filtersystemd.py
RefactoringTool: fail2ban/server/ipdns.py
RefactoringTool: fail2ban/server/jail.py
RefactoringTool: fail2ban/server/jails.py
RefactoringTool: fail2ban/server/jailthread.py
RefactoringTool: fail2ban/server/mytime.py
RefactoringTool: fail2ban/server/observer.py
RefactoringTool: fail2ban/server/server.py
RefactoringTool: fail2ban/server/strptime.py
RefactoringTool: fail2ban/server/ticket.py
RefactoringTool: fail2ban/server/transmitter.py
RefactoringTool: fail2ban/server/utils.py
RefactoringTool: fail2ban/tests/__init__.py
RefactoringTool: fail2ban/tests/actionstestcase.py
RefactoringTool: fail2ban/tests/actiontestcase.py
RefactoringTool: fail2ban/tests/banmanagertestcase.py
RefactoringTool: fail2ban/tests/clientbeautifiertestcase.py
RefactoringTool: fail2ban/tests/clientreadertestcase.py
RefactoringTool: fail2ban/tests/databasetestcase.py
RefactoringTool: fail2ban/tests/datedetectortestcase.py
RefactoringTool: fail2ban/tests/dummyjail.py
RefactoringTool: fail2ban/tests/fail2banclienttestcase.py
RefactoringTool: fail2ban/tests/fail2banregextestcase.py
RefactoringTool: fail2ban/tests/failmanagertestcase.py
RefactoringTool: fail2ban/tests/filtertestcase.py
RefactoringTool: fail2ban/tests/misctestcase.py
RefactoringTool: fail2ban/tests/observertestcase.py
RefactoringTool: fail2ban/tests/samplestestcase.py
RefactoringTool: fail2ban/tests/servertestcase.py
RefactoringTool: fail2ban/tests/sockettestcase.py
RefactoringTool: fail2ban/tests/tickettestcase.py
RefactoringTool: fail2ban/tests/utils.py
RefactoringTool: fail2ban/tests/action_d/__init__.py
RefactoringTool: fail2ban/tests/action_d/test_smtp.py
RefactoringTool: fail2ban/tests/files/ignorecommand.py
RefactoringTool: fail2ban/tests/files/action.d/action.py
RefactoringTool: fail2ban/tests/files/action.d/action_checkainfo.py
RefactoringTool: fail2ban/tests/files/action.d/action_errors.py
RefactoringTool: fail2ban/tests/files/action.d/action_modifyainfo.py
RefactoringTool: fail2ban/tests/files/config/apache-auth/digest.py
Success!
# bash fail2ban-testcases-all-python3
Testing using /usr/local/bin/python3.9
Fail2ban 1.0.3.dev1 test suite. Python 3.9.15 (main, Oct 15 2022, 00:33:51) [Clang 13.0.0 ]. Please wait...
.............................ss.........................................FF....................................................................s.............................s....................................................................F................................................................FFF............................................................................................................................
.F=== Catch an exception: '\\125-000-001 1;\n' was not found in '%s 1;\n%s 1;\n%s 1;\n%s 1;\n%s 1;\n'
=== Error of server, log: ===
/tmp/f2b-temp9z9pqg_s/blck-failures.log has been modified
Processing line with time:1671495573.0 and ip:125-000-001
[nginx-blck-lst] Found 125-000-001 - 2022-12-20 01:19:33
Total # of detected failures: 1. Current failures from 1 IPs (IP:count): 125-000-001:1
...
Ran 503 tests in 64.270s
FAILED (failures=11, skipped=5)
E: Failed with /usr/local/bin/python3.9
The failed tests are:
- testExecuteTimeout (fail2ban.tests.actiontestcase.CommandActionTest)
- testExecuteTimeoutWithNastyChildren (fail2ban.tests.actiontestcase.CommandActionTest)
- testIgnoreIPNOK (fail2ban.tests.filtertestcase.IgnoreIP)
- testIpToIp (fail2ban.tests.filtertestcase.DNSUtilsNetworkTests)
- testIpToName (fail2ban.tests.filtertestcase.DNSUtilsNetworkTests)
- testTextToIp (fail2ban.tests.filtertestcase.DNSUtilsNetworkTests)
- testKillAfterStart (fail2ban.tests.fail2banclienttestcase.Fail2banServerTest)
- testServerActions_NginxBlockMap (fail2ban.tests.fail2banclienttestcase.Fail2banServerTest)
- testDirectRE_1 (fail2ban.tests.fail2banregextestcase.Fail2banRegexTest)
- testLogTargetSYSLOG (fail2ban.tests.servertestcase.TransmitterLogging)
- testSyslogSocketNOK (fail2ban.tests.servertestcase.TransmitterLogging)
Finally, we install it by running:
# python3 -v setup.py install
import _frozen_importlib # frozen
import _imp # builtin
...
creating /usr/local/lib/python3.9/site-packages/fail2ban
copying build/lib/fail2ban/__init__.py -> /usr/local/lib/python3.9/site-packages/fail2ban
copying build/lib/fail2ban/exceptions.py -> /usr/local/lib/python3.9/site-packages/fail2ban
copying build/lib/fail2ban/helpers.py -> /usr/local/lib/python3.9/site-packages/fail2ban
copying build/lib/fail2ban/protocol.py -> /usr/local/lib/python3.9/site-packages/fail2ban
...
byte-compiling /usr/local/lib/python3.9/site-packages/fail2ban/__init__.py to __init__.cpython-39.pyc
byte-compiling /usr/local/lib/python3.9/site-packages/fail2ban/exceptions.py to exceptions.cpython-39.pyc
byte-compiling /usr/local/lib/python3.9/site-packages/fail2ban/helpers.py to helpers.cpython-39.pyc
...
creating /etc/fail2ban
copying config/fail2ban.conf -> /etc/fail2ban
copying config/jail.conf -> /etc/fail2ban
copying config/paths-arch.conf -> /etc/fail2ban
copying config/paths-common.conf -> /etc/fail2ban
...
# cleanup[3] wiping _sha3
# cleanup[3] wiping _blake2
# cleanup[3] wiping _hashlib
...
# destroy _sitebuiltins
# destroy importlib.util
# destroy importlib.abc
...
# destroy select
# destroy _frozen_importlib
# clear sys.audit hooks
root@main:/root/dev/f2b/fail2ban# fail2ban-client -v
2022-12-20 01:38:02,945 fail2ban.configreader [33416]: INFO Loading configs for fail2ban under /etc/fail2ban
2022-12-20 01:38:02,948 fail2ban.configparserinc[33416]: INFO Loading files: ['/etc/fail2ban/fail2ban.conf']
2022-12-20 01:38:02,950 fail2ban.configparserinc[33416]: INFO Loading files: ['/etc/fail2ban/fail2ban.conf']
2022-12-20 01:38:02,951 fail2ban [33416]: INFO Using socket file /var/run/fail2ban/fail2ban.sock
2022-12-20 01:38:02,951 fail2ban [33416]: INFO Using pid file /var/run/fail2ban/fail2ban.pid, [INFO] logging to /var/log/fail2ban.log
Usage: fail2ban-client [OPTIONS] <COMMAND>
Fail2Ban v1.0.3.dev1 reads log file that contains password failure report
It works! ... I thought it was supposed to be the stable version 1.0.2, but anyway!
We now need to modify some defaults and add the jails before its first run.
Initial Configuration
As instructed on the Wiki page Proper fail2ban configuration, we copy the corresponding ".conf" file to ".local", and we only modify the local files. In the configuration directory we find:
# tree -L 1 /etc/fail2ban
/etc/fail2ban
|-- action.d
|-- fail2ban.conf
|-- fail2ban.d
|-- filter.d
|-- jail.conf
|-- jail.d
|-- paths-arch.conf
|-- paths-common.conf
|-- paths-debian.conf
|-- paths-fedora.conf
|-- paths-freebsd.conf
|-- paths-opensuse.conf
`-- paths-osx.conf
We create paths-openbsd.conf from paths-freebsd.conf:
# OpenBSD
[INCLUDES]
before = paths-common.conf
after = paths-overrides.local
[DEFAULT]
# https://man.openbsd.org/syslog.conf.5
#
syslog_authpriv = /var/log/authlog
syslog_mail = /var/log/maillog
syslog_mail_warn = /var/log/maillog
apache_error_log = /var/www/logs/httpd-error.log
apache_access_log = /var/www/logs/httpd-access.log
nginx_error_log = /var/www/logs/error*.log
nginx_access_log = /var/www/logs/access*.log
The main difference is that on OpenBSD, the web servers are chrooted by default on /var/www, so the log files are all stored at /var/www/logs.
Customising jail.conf
We copy jail.conf to jail.local and change the following lines:
- Line 36:
before = paths-openbsd.conf
- Uncomment lines 49, 53, 61, and 65:
bantime.increment = true
bantime.rndtime = 6m
bantime.factor = 1
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
- We add our public IP (111.222.000.111) to ignoreip:
ignoreip = 127.0.0.1/8 ::1 111.222.000.111
- ... and:
findtime = 60m
logencoding = utf-8
- Most important, the Ban action:
banaction = pf
banaction_allports = pf
Add jail for sshd
Finally, we add a jail for ssh by adding the file /etc/fail2ban/jail.d/sshd.conf:
[sshd]
enabled = true
maxretry = 2
bantime = 13m
The name of the jail in braquets coincides with the names of the jails defined in the file jail.conf. For nginx we have: [nginx-http-auth], [nginx-limit-req], [nginx-botsearch], and [nginx-bad-request]. We can create another file for nginx (nginx.conf) with all the jails:
[nginx-http-auth]
enabled = true
[nginx-limit-req]
enabled = true
[nginx-botsearch]
enabled = true
[nginx-bad-request]
enabled = true
We should now be able to start fail2ban withour errors:
# fail2ban-client -v start
2022-12-20 02:43:06,105 fail2ban.configreader [79930]: INFO Loading configs for fail2ban under /etc/fail2ban
2022-12-20 02:43:06,108 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/fail2ban.conf']
2022-12-20 02:43:06,110 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/fail2ban.conf']
2022-12-20 02:43:06,110 fail2ban [79930]: INFO Using socket file /var/run/fail2ban/fail2ban.sock
2022-12-20 02:43:06,111 fail2ban [79930]: INFO Using pid file /var/run/fail2ban/fail2ban.pid, [INFO] logging to /var/log/fail2ban.log
2022-12-20 02:43:06,215 fail2ban.configreader [79930]: INFO Loading configs for jail under /etc/fail2ban
2022-12-20 02:43:06,217 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/jail.conf']
2022-12-20 02:43:06,237 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/paths-debian.conf']
2022-12-20 02:43:06,238 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/paths-common.conf']
2022-12-20 02:43:06,240 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/paths-overrides.local']
2022-12-20 02:43:06,243 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/jail.d/sshd.conf']
2022-12-20 02:43:06,244 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/jail.local']
2022-12-20 02:43:06,262 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/paths-openbsd.conf']
2022-12-20 02:43:06,263 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/paths-common.conf', '/etc/fail2ban/paths-debian.conf', '/etc/fail2ban/jail.conf', '/etc/fail2ban/jail.d/sshd.conf', '/etc/fail2ban/paths-common.conf', '/etc/fail2ban/paths-openbsd.conf', '/etc/fail2ban/jail.local']
2022-12-20 02:43:06,274 fail2ban.configreader [79930]: INFO Loading configs for action.d/pf under /etc/fail2ban
2022-12-20 02:43:06,274 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/action.d/pf.conf']
2022-12-20 02:43:06,276 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/action.d/pf.conf']
2022-12-20 02:43:06,294 fail2ban.configreader [79930]: INFO Loading configs for filter.d/sshd under /etc/fail2ban
2022-12-20 02:43:06,295 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/filter.d/sshd.conf']
2022-12-20 02:43:06,298 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/filter.d/common.conf']
2022-12-20 02:43:06,300 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/filter.d/common.local']
2022-12-20 02:43:06,300 fail2ban.configparserinc[79930]: INFO Loading files: ['/etc/fail2ban/filter.d/common.conf', '/etc/fail2ban/filter.d/sshd.conf']
2022-12-20 02:43:06,525 fail2ban [35278]: INFO Using socket file /var/run/fail2ban/fail2ban.sock
2022-12-20 02:43:06,525 fail2ban [35278]: INFO Using pid file /var/run/fail2ban/fail2ban.pid, [INFO] logging to /var/log/fail2ban.log
2022-12-20 02:43:06,607 fail2ban.server [35278]: INFO Starting in daemon mode
Server ready
PF configuration
The last piece of the puzzle is PF. We just need to add the following to /etc/pf.conf:
anchor "f2b/*"
We can check the banned IPs by checking the log file /var/log/fail2ban.log or by running:
# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 1
| |- Total failed: 773
| `- File list: /var/log/authlog
`- Actions
|- Currently banned: 5
|- Total banned: 329
`- Banned IP list: 109.107.166.160 195.226.194.242 162.243.165.205 23.94.43.69 42.96.40.182
HTH