1 { config, lib, pkgs, ... }:
7 cfg = config.services.fail2ban;
9 fail2banConf = pkgs.writeText "fail2ban.local" cfg.daemonConfig;
11 jailConf = pkgs.writeText "jail.local" ''
14 before = paths-nixos.conf
16 ${concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def:
17 optionalString (def != "")
24 pathsConf = pkgs.writeText "paths-nixos.conf" ''
29 before = paths-common.conf
31 after = paths-overrides.local
48 description = lib.mdDoc ''
49 Whether to enable the fail2ban service.
51 See the documentation of {option}`services.fail2ban.jails`
52 for what jails are enabled by default.
57 default = pkgs.fail2ban;
58 defaultText = literalExpression "pkgs.fail2ban";
60 example = literalExpression "pkgs.fail2ban_0_11";
61 description = lib.mdDoc "The fail2ban package to use for running the fail2ban service.";
64 packageFirewall = mkOption {
65 default = pkgs.iptables;
66 defaultText = literalExpression "pkgs.iptables";
68 example = literalExpression "pkgs.nftables";
69 description = lib.mdDoc "The firewall package used by fail2ban service.";
72 extraPackages = mkOption {
74 type = types.listOf types.package;
75 example = lib.literalExpression "[ pkgs.ipset ]";
76 description = lib.mdDoc ''
77 Extra packages to be made available to the fail2ban service. The example contains
78 the packages needed by the `iptables-ipset-proto6` action.
84 type = types.ints.unsigned;
85 description = lib.mdDoc "Number of failures before a host gets banned.";
88 banaction = mkOption {
89 default = "iptables-multiport";
91 example = "nftables-multiport";
92 description = lib.mdDoc ''
93 Default banning action (e.g. iptables, iptables-new, iptables-multiport,
94 iptables-ipset-proto6-allports, shorewall, etc) It is used to
95 define action_* variables. Can be overridden globally or per
96 section within jail.local file
100 banaction-allports = mkOption {
101 default = "iptables-allport";
103 example = "nftables-allport";
104 description = lib.mdDoc ''
105 Default banning action (e.g. iptables, iptables-new, iptables-multiport,
106 shorewall, etc) It is used to define action_* variables. Can be overridden
107 globally or per section within jail.local file
111 bantime-increment.enable = mkOption {
114 description = lib.mdDoc ''
115 Allows to use database for searching of previously banned ip's to increase
116 a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32...
120 bantime-increment.rndtime = mkOption {
124 description = lib.mdDoc ''
125 "bantime-increment.rndtime" is the max number of seconds using for mixing with random time
126 to prevent "clever" botnets calculate exact time IP can be unbanned again
130 bantime-increment.maxtime = mkOption {
134 description = lib.mdDoc ''
135 "bantime-increment.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
139 bantime-increment.factor = mkOption {
143 description = lib.mdDoc ''
144 "bantime-increment.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
145 default value of factor is 1 and with default value of formula, the ban time grows by 1, 2, 4, 8, 16 ...
149 bantime-increment.formula = mkOption {
150 default = "ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor";
152 example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
153 description = lib.mdDoc ''
154 "bantime-increment.formula" used by default to calculate next value of ban time, default value bellow,
155 the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32...
159 bantime-increment.multipliers = mkOption {
160 default = "1 2 4 8 16 32 64";
162 example = "2 4 16 128";
163 description = lib.mdDoc ''
164 "bantime-increment.multipliers" used to calculate next value of ban time instead of formula, coresponding
165 previously ban count and given "bantime.factor" (for multipliers default is 1);
166 following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count,
167 always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours
171 bantime-increment.overalljails = mkOption {
175 description = lib.mdDoc ''
176 "bantime-increment.overalljails" (if true) specifies the search of IP in the database will be executed
177 cross over all jails, if false (dafault), only current jail of the ban IP will be searched
181 ignoreIP = mkOption {
183 type = types.listOf types.str;
184 example = [ "192.168.0.0/16" "2001:DB8::42" ];
185 description = lib.mdDoc ''
186 "ignoreIP" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban will not ban a host which
187 matches an address in this list. Several addresses can be defined using space (and/or comma) separator.
191 daemonConfig = mkOption {
195 socket = /run/fail2ban/fail2ban.sock
196 pidfile = /run/fail2ban/fail2ban.pid
197 dbfile = /var/lib/fail2ban/fail2ban.sqlite3
200 description = lib.mdDoc ''
201 The contents of Fail2ban's main configuration file. It's
202 generally not necessary to change it.
208 example = literalExpression ''
209 { apache-nohome-iptables = '''
210 # Block an IP address if it accesses a non-existent
211 # home directory more than 5 times in 10 minutes,
212 # since that indicates that it's scanning.
213 filter = apache-nohome
214 action = iptables-multiport[name=HTTP, port="http,https"]
215 logpath = /var/log/httpd/error_log*
222 # block IPs which failed to log-in
223 # aggressive mode add blocking for aborted connections
225 filter = dovecot[mode=aggressive]
230 type = types.attrsOf types.lines;
231 description = lib.mdDoc ''
232 The configuration of each Fail2ban “jail”. A jail
233 consists of an action (such as blocking a port using
234 {command}`iptables`) that is triggered when a
235 filter applied to a log file triggers more than a certain
236 number of times in a certain time period. Actions are
237 defined in {file}`/etc/fail2ban/action.d`,
238 while filters are defined in
239 {file}`/etc/fail2ban/filter.d`.
241 NixOS comes with a default `sshd` jail;
243 {option}`services.openssh.logLevel` should be set to
244 `"VERBOSE"` or higher so that fail2ban
245 can observe failed login attempts.
246 This module sets it to `"VERBOSE"` if
247 not set otherwise, so enabling fail2ban can make SSH logs
256 ###### implementation
258 config = mkIf cfg.enable {
260 warnings = mkIf (config.networking.firewall.enable == false && config.networking.nftables.enable == false) [
261 "fail2ban can not be used without a firewall"
264 environment.systemPackages = [ cfg.package ];
267 "fail2ban/fail2ban.local".source = fail2banConf;
268 "fail2ban/jail.local".source = jailConf;
269 "fail2ban/fail2ban.conf".source = "${cfg.package}/etc/fail2ban/fail2ban.conf";
270 "fail2ban/jail.conf".source = "${cfg.package}/etc/fail2ban/jail.conf";
271 "fail2ban/paths-common.conf".source = "${cfg.package}/etc/fail2ban/paths-common.conf";
272 "fail2ban/paths-nixos.conf".source = pathsConf;
273 "fail2ban/action.d".source = "${cfg.package}/etc/fail2ban/action.d/*.conf";
274 "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf";
277 systemd.services.fail2ban = {
278 description = "Fail2ban Intrusion Prevention System";
280 wantedBy = [ "multi-user.target" ];
281 after = [ "network.target" ];
282 partOf = optional config.networking.firewall.enable "firewall.service";
284 restartTriggers = [ fail2banConf jailConf pathsConf ];
286 path = [ cfg.package cfg.packageFirewall pkgs.iproute2 ] ++ cfg.extraPackages;
288 unitConfig.Documentation = "man:fail2ban(1)";
291 ExecStart = "${cfg.package}/bin/fail2ban-server -xf start";
292 ExecStop = "${cfg.package}/bin/fail2ban-server stop";
293 ExecReload = "${cfg.package}/bin/fail2ban-server reload";
295 Restart = "on-failure";
296 PIDFile = "/run/fail2ban/fail2ban.pid";
298 CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
300 NoNewPrivileges = true;
302 RuntimeDirectory = "fail2ban";
303 RuntimeDirectoryMode = "0750";
304 StateDirectory = "fail2ban";
305 StateDirectoryMode = "0750";
306 LogsDirectory = "fail2ban";
307 LogsDirectoryMode = "0750";
309 ProtectSystem = "strict";
312 PrivateDevices = true;
313 ProtectHostname = true;
314 ProtectKernelTunables = true;
315 ProtectKernelModules = true;
316 ProtectControlGroups = true;
320 # Add some reasonable default jails. The special "DEFAULT" jail
321 # sets default values for all other jails.
322 services.fail2ban.jails.DEFAULT = ''
323 ${optionalString cfg.bantime-increment.enable ''
324 # Bantime incremental
325 bantime.increment = ${boolToString cfg.bantime-increment.enable}
326 bantime.maxtime = ${cfg.bantime-increment.maxtime}
327 bantime.factor = ${cfg.bantime-increment.factor}
328 bantime.formula = ${cfg.bantime-increment.formula}
329 bantime.multipliers = ${cfg.bantime-increment.multipliers}
330 bantime.overalljails = ${boolToString cfg.bantime-increment.overalljails}
332 # Miscellaneous options
333 ignoreip = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}
334 maxretry = ${toString cfg.maxretry}
337 banaction = ${cfg.banaction}
338 banaction_allports = ${cfg.banaction-allports}
340 # Block SSH if there are too many failing connection attempts.
341 # Benefits from verbose sshd logging to observe failed login attempts,
342 # so we set that here unless the user overrode it.
343 services.openssh.logLevel = lib.mkDefault "VERBOSE";
344 services.fail2ban.jails.sshd = mkDefault ''
346 port = ${concatMapStringsSep "," (p: toString p) config.services.openssh.ports}