❄️ Dotfiles for our NixOS system configuration.

feat(services): fail2ban (test)

Chloe becd1626 8de456fb

Changed files
+158
secrets
services
+14
secrets/abuseipdb.age
···
+
-----BEGIN AGE ENCRYPTED FILE-----
+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IGN2U2dKQSA5eHJO
+
L0V2dE1tem9DSy92ZnBweG55YjRmVjdrM3FPN24vZUE0K1E2Smx3ClFiQXpiUCt6
+
N0JBTm0yZWI5Q3lYTjVlWXYzTkZBTFpLY0dqSE5INmlITDgKLT4gc3NoLWVkMjU1
+
MTkgMUNUOTd3IFYxRXhpWS9tQnl0SEc5RGo2Ymhyb3Jqa0dVN0twcEplK3lLR0tl
+
SkJBeWsKMFYya3FtRTg0Um9mRnNWbElMWkdMNEE5RlExTkNQaUpGRUEzK2pOdzc0
+
RQotPiBzc2gtZWQyNTUxOSBlUDNUdFEgVDRrNUNSWXl5bWlXWFEvdmpVZWtsRTJr
+
WmU3OWhkTFlVeUlpTW1vekxHbwpxOFJPQTFsN2RQUmY0YzRUVlBBR0JiL0FzbHZy
+
b2pqQzNJd1ZSSElYVHFrCi0+ICctZ3JlYXNlICFofCAlKytiRHFHClZvUQotLS0g
+
Mi9YUUNMK0pxc1dOSmt1VWxjcG8yUTl4VmQxT3RKQm9JZUoxK1ZyUUZ0Ywr+XbIf
+
30MbeXvOVwJSQMLcc/m9044pc1UXNcO6aLrbShVeVMs95zg18luAiE0V4ML7Ldrs
+
LTduxwbz/k3Tr+H4AKTCocXsilVmGhqaEZ6ST0aEjMHvuXFiXSHu8upnBybYrDfy
+
pp+sJnTp4/SfL9y+WA==
+
-----END AGE ENCRYPTED FILE-----
+1
secrets/secrets.nix
···
];
in
{
+
"abuseipdb.age".publicKeys = keys;
"bluesky-pds.age".publicKeys = keys;
"caddy.age".publicKeys = keys;
"destiny-labeler.age".publicKeys = keys;
+4
services/caddy/default.nix
···
encode zstd gzip
}
'';
+
logFormat = ''
+
level info
+
format json
+
'';
};
settings.firewall.allowedTCPPorts = [
+1
services/default.nix
···
./bluesky-pds
./caddy
./destiny-labeler
+
./fail2ban
./glance
./knot
./lanyard
+138
services/fail2ban/default.nix
···
+
{ config, pkgs, ... }:
+
+
{
+
age.secrets.abuseipdb = {
+
file = ../../secrets/abuseipdb.age;
+
mode = "600";
+
owner = "abuseipdb";
+
group = "abuseipdb";
+
};
+
+
services.fail2ban = {
+
enable = true;
+
+
# Include curl for making HTTP requests to AbuseIPDB
+
extraPackages = [ pkgs.curl ];
+
+
# Globally applicable settings for all jails
+
daemonSettings = {
+
Definition = {
+
# How long to keep an IP in the ban list (in seconds)
+
# 1 day = 86400 seconds
+
bantime = "86400";
+
+
# How far back to look for failures (in seconds)
+
# 1 hour = 3600 seconds
+
findtime = "3600";
+
+
# Number of failures before banning
+
maxretry = 3;
+
+
# Allow fail2ban to write to syslog
+
logtarget = "SYSLOG";
+
};
+
};
+
+
# Ignore local networks and trusted services
+
ignoreIP = [
+
"127.0.0.1/8"
+
"::1"
+
"100.64.0.0/10" # Tailscale IP range
+
];
+
+
# Jails for protecting various services
+
jails = {
+
# SSH protection - monitor failed login attempts
+
sshd = {
+
enabled = true;
+
port = "ssh";
+
filter = "sshd";
+
logpath = "%(syslog_authpriv)s";
+
backend = "auto";
+
maxretry = 5;
+
findtime = "3600";
+
bantime = "86400";
+
action = [
+
"iptables-multiport[name=SSH, port='ssh']"
+
"abuseipdb[abuseipdb_apikey=${config.age.secrets.abuseipdb.path}, abuseipdb_category='18,22', abuseipdb_comment='Fail2Ban SSH Brute Force']"
+
];
+
};
+
+
# Caddy HTTP/HTTPS protection - monitor for repeated 4xx/5xx errors
+
caddy-http = {
+
enabled = true;
+
port = "http,https";
+
filter = "caddy-http";
+
logpath = "/var/log/caddy/access.log";
+
backend = "auto";
+
maxretry = 10;
+
findtime = "600";
+
bantime = "3600";
+
action = [
+
"iptables-multiport[name=Caddy, port='http,https']"
+
"abuseipdb[abuseipdb_apikey=${config.age.secrets.abuseipdb.path}, abuseipdb_category='21', abuseipdb_comment='Fail2Ban Caddy Abuse']"
+
];
+
};
+
+
# Rate-based protection - ban on excessive requests
+
caddy-ratelimit = {
+
enabled = true;
+
port = "http,https";
+
filter = "caddy-ratelimit";
+
logpath = "/var/log/caddy/access.log";
+
backend = "auto";
+
maxretry = 50;
+
findtime = "60";
+
bantime = "1800";
+
action = [
+
"iptables-multiport[name=Caddy-RateLimit, port='http,https']"
+
"abuseipdb[abuseipdb_apikey=${config.age.secrets.abuseipdb.path}, abuseipdb_category='21', abuseipdb_comment='Fail2Ban Rate Limiting']"
+
];
+
};
+
};
+
};
+
+
# Custom filters for Fail2Ban
+
environment.etc = {
+
# Caddy HTTP error monitoring filter
+
"fail2ban/filter.d/caddy-http.conf".text = ''
+
[Definition]
+
failregex = ^<HOST> -.*" (?:400|401|403|404|405|429|500|502|503|504) .*$
+
ignoreregex =
+
'';
+
+
# Caddy rate limiting filter - detects repeated requests within short timeframe
+
"fail2ban/filter.d/caddy-ratelimit.conf".text = ''
+
[Definition]
+
failregex = ^<HOST> -.*" \d{3} .*$
+
ignoreregex =
+
'';
+
+
# AbuseIPDB action for reporting IPs
+
# The API key is passed via the abuseipdb_apikey parameter which should be
+
# set to the path of the decrypted secret file (e.g., /run/agenix/abuseipdb)
+
"fail2ban/action.d/abuseipdb.conf".text = ''
+
[Definition]
+
actionstart =
+
actionstop =
+
actioncheck =
+
+
# Report IP to AbuseIPDB
+
# Reads the API key from the file specified in the abuseipdb_apikey parameter
+
actionban = /run/current-system/sw/bin/curl -s -X POST https://api.abuseipdb.com/api/v2/report \
+
-H "Key: $(cat <abuseipdb_apikey>)" \
+
-H "Accept: application/json" \
+
-d "ip=<ip>&category=<abuseipdb_category>&comment=<abuseipdb_comment>" \
+
-o /dev/null
+
+
# No action to unban - AbuseIPDB reports are permanent
+
actionunban =
+
+
[Init]
+
# Default path - will be overridden by jail configuration
+
abuseipdb_apikey = /run/agenix/abuseipdb
+
abuseipdb_category = 18
+
abuseipdb_comment = Fail2Ban Report
+
'';
+
};
+
}