Title: Extending fail2ban on NixOS | |
Author: Solène | |
Date: 02 October 2022 | |
Tags: linux nixos fail2ban security | |
Description: This article shows how to create new filters in fail2ban | |
on NixOS. | |
# Introduction | |
Fail2ban is a wonderful piece of software, it can analyze logs from | |
daemons and ban them in the firewall. It's triggered by certain | |
conditions like a single IP found in too many lines matching a pattern | |
(such as a login failure) under a certain time. | |
What's even cooler is that writing new filters is super easy! In this | |
text, I'll share how to write new filters for NixOS. | |
fail2ban GitHub project page | |
NixOS official website | |
# Terminology | |
Before continuing, if you are not familiar with fail2ban, here are the | |
few important keywords to understand: | |
* action: what to do with an IP (usually banning an IP) | |
* filter: set of regular expressions and information used to find bad | |
actors in logs | |
* jail: what ties together filters and actions in a logical unit | |
For instance, a sshd jail will have a filter applied on sshd logs, and | |
it will use a banning action. The jail can have more information like | |
how many times an IP must be found by a filter before using the action. | |
# Configuration | |
## Enabling fail2ban | |
The easiest part is to enable fail2ban. Take the opportunity to | |
declare IPs you don't want to block, and also block IPs on all ports if | |
it's something you want. | |
``` | |
services.fail2ban = { | |
enable = true; | |
ignoreIP = [ | |
"192.168.1.0/24" | |
]; | |
}; | |
# needed to ban on IPv4 and IPv6 for all ports | |
services.fail2ban = { | |
extraPackages = [pkgs.ipset]; | |
banaction = "iptables-ipset-proto6-allports"; | |
}; | |
``` | |
## Creating new filters | |
A filter is composed of one/many regex, and also a systemd journal unit | |
in case you are pulling information from it instead of a log file. | |
We will use the module `environment.etc` to create files in | |
`/etc/fail2ban/filter.d/` directory, so they can be used in the jails. | |
These are examples of filters you may want to use. They target very | |
large, this may not be ideal for your use case, but can serve as a good | |
start. | |
``` | |
environment.etc = { | |
"fail2ban/filter.d/molly.conf".text = '' | |
[Definition] | |
failregex = <HOST>\s+(31|40|51|53).*$ | |
''; | |
"fail2ban/filter.d/nginx-bruteforce.conf".text = '' | |
[Definition] | |
failregex = ^<HOST>.*GET.*(matrix/server|\.php|admin|wp\-).* HTTP/\d.\d\"… | |
''; | |
"fail2ban/filter.d/postfix-bruteforce.conf".text = '' | |
[Definition] | |
failregex = warning: [\w\.\-]+\[<HOST>\]: SASL LOGIN authentication faile… | |
journalmatch = _SYSTEMD_UNIT=postfix.service | |
''; | |
}; | |
``` | |
## Defining the jails using our new filters | |
Now we can declare fail2ban jails with each filter we created. If you | |
use a log file, make sure to have `backend = auto`, otherwise the | |
systemd journal is used and this won't work. | |
The most important settings are: | |
* filter: choose your filter using its filename minus the `.conf` part | |
* maxretry: how many times an IP should be reported before taking an | |
action | |
* findtime: how long should we keep entries to match in maxretry | |
``` | |
services.fail2ban.jails = { | |
# max 6 failures in 600 seconds | |
"nginx-spam" = '' | |
enabled = true | |
filter = nginx-bruteforce | |
logpath = /var/log/nginx/access.log | |
backend = auto | |
maxretry = 6 | |
findtime = 600 | |
''; | |
# max 3 failures in 600 seconds | |
"postfix-bruteforce" = '' | |
enabled = true | |
filter = postfix-bruteforce | |
findtime = 600 | |
maxretry = 3 | |
''; | |
# max 10 failures in 600 seconds | |
"molly" = '' | |
enabled = true | |
filter = molly | |
findtime = 600 | |
maxretry = 10 | |
logpath = /var/log/molly-brown/access.log | |
backend = auto | |
''; | |
}; | |
``` | |
# Creating filters | |
It's actually easy to create filters, fail2ban provides a good | |
framework like automatic date and host detection, which make creating | |
regex very easy. | |
You can use the command `fail2ban-regex` to experiment with regexes on | |
some logs. | |
Here is an example of a log file that would contain an IP and an error | |
message: | |
``` | |
fail2ban-regex /var/log/someservice.log "<HOST> ERROR" | |
``` | |
Here is an example of a systemd unit log that would contain an IP, then | |
a space and a 403 error: | |
``` | |
fail2ban-regex -m _SYSTEMD_UNIT=someservice.service systemd-journal "<HOST> 403" | |
``` | |
You can analyze what lines matched or not with the flags | |
`--print-all-matched` and `--print-all-missed`. | |
I recommend you to read fail2ban man pages and --help output if you | |
want to create filters. | |
# Conclusion | |
Fail2ban is a fantastic tool to easily create filtering rules to ban | |
the bad actors. It turned out most rules didn't work out of the box, | |
or were too narrow for my use case, so extending fail2ban was quite | |
straightforward. |