grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / security / fail2ban.nix
blobb6ce42d7318c8d984e2a4d90c59f9c38358c2580
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.fail2ban;
8   settingsFormat = pkgs.formats.keyValue { };
10   configFormat = pkgs.formats.ini {
11     mkKeyValue = generators.mkKeyValueDefault { } " = ";
12   };
14   mkJailConfig = name: attrs:
15     optionalAttrs (name != "DEFAULT") { inherit (attrs) enabled; } //
16     optionalAttrs (attrs.filter != null) { filter = if (builtins.isString filter) then filter else name; } //
17     attrs.settings;
19   mkFilter = name: attrs: nameValuePair "fail2ban/filter.d/${name}.conf" {
20     source = configFormat.generate "filter.d/${name}.conf" attrs.filter;
21   };
23   fail2banConf = configFormat.generate "fail2ban.local" cfg.daemonSettings;
25   strJails = filterAttrs (_: builtins.isString) cfg.jails;
26   attrsJails = filterAttrs (_: builtins.isAttrs) cfg.jails;
28   jailConf =
29     let
30       configFile = configFormat.generate "jail.local" (
31         { INCLUDES.before = "paths-nixos.conf"; } // (mapAttrs mkJailConfig attrsJails)
32       );
33       extraConfig = concatStringsSep "\n" (attrValues (mapAttrs
34         (name: def:
35           optionalString (def != "")
36             ''
37               [${name}]
38               ${def}
39             '')
40         strJails));
42     in
43     pkgs.concatText "jail.local" [ configFile (pkgs.writeText "extra-jail.local" extraConfig) ];
45   pathsConf = pkgs.writeText "paths-nixos.conf" ''
46     # NixOS
48     [INCLUDES]
50     before = paths-common.conf
52     after  = paths-overrides.local
54     [DEFAULT]
55   '';
60   imports = [
61     (mkRemovedOptionModule [ "services" "fail2ban" "daemonConfig" ] "The daemon is now configured through the attribute set `services.fail2ban.daemonSettings`.")
62     (mkRemovedOptionModule [ "services" "fail2ban" "extraSettings" ] "The extra default configuration can now be set using `services.fail2ban.jails.DEFAULT.settings`.")
63   ];
65   ###### interface
67   options = {
68     services.fail2ban = {
69       enable = mkOption {
70         default = false;
71         type = types.bool;
72         description = ''
73           Whether to enable the fail2ban service.
75           See the documentation of {option}`services.fail2ban.jails`
76           for what jails are enabled by default.
77         '';
78       };
80       package = mkPackageOption pkgs "fail2ban" {
81         example = "fail2ban_0_11";
82       };
84       packageFirewall = mkOption {
85         default = config.networking.firewall.package;
86         defaultText = literalExpression "config.networking.firewall.package";
87         type = types.package;
88         description = "The firewall package used by fail2ban service. Defaults to the package for your firewall (iptables or nftables).";
89       };
91       extraPackages = mkOption {
92         default = [ ];
93         type = types.listOf types.package;
94         example = lib.literalExpression "[ pkgs.ipset ]";
95         description = ''
96           Extra packages to be made available to the fail2ban service. The example contains
97           the packages needed by the `iptables-ipset-proto6` action.
98         '';
99       };
101       bantime = mkOption {
102         default = "10m";
103         type = types.str;
104         example = "1h";
105         description = "Number of seconds that a host is banned.";
106       };
108       maxretry = mkOption {
109         default = 3;
110         type = types.ints.unsigned;
111         description = "Number of failures before a host gets banned.";
112       };
114       banaction = mkOption {
115         default = if config.networking.nftables.enable then "nftables-multiport" else "iptables-multiport";
116         defaultText = literalExpression ''if config.networking.nftables.enable then "nftables-multiport" else "iptables-multiport"'';
117         type = types.str;
118         description = ''
119           Default banning action (e.g. iptables, iptables-new, iptables-multiport,
120           iptables-ipset-proto6-allports, shorewall, etc). It is used to
121           define action_* variables. Can be overridden globally or per
122           section within jail.local file
123         '';
124       };
126       banaction-allports = mkOption {
127         default = if config.networking.nftables.enable then "nftables-allports" else "iptables-allports";
128         defaultText = literalExpression ''if config.networking.nftables.enable then "nftables-allports" else "iptables-allports"'';
129         type = types.str;
130         description = ''
131           Default banning action (e.g. iptables, iptables-new, iptables-multiport,
132           shorewall, etc) for "allports" jails. It is used to define action_* variables. Can be overridden
133           globally or per section within jail.local file
134         '';
135       };
137       bantime-increment.enable = mkOption {
138         default = false;
139         type = types.bool;
140         description = ''
141           "bantime.increment" allows to use database for searching of previously banned ip's to increase
142           a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32 ...
143         '';
144       };
146       bantime-increment.rndtime = mkOption {
147         default = null;
148         type = types.nullOr types.str;
149         example = "8m";
150         description = ''
151           "bantime.rndtime" is the max number of seconds using for mixing with random time
152           to prevent "clever" botnets calculate exact time IP can be unbanned again
153         '';
154       };
156       bantime-increment.maxtime = mkOption {
157         default = null;
158         type = types.nullOr types.str;
159         example = "48h";
160         description = ''
161           "bantime.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
162         '';
163       };
165       bantime-increment.factor = mkOption {
166         default = null;
167         type = types.nullOr types.str;
168         example = "4";
169         description = ''
170           "bantime.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
171           default value of factor is 1 and with default value of formula, the ban time grows by 1, 2, 4, 8, 16 ...
172         '';
173       };
175       bantime-increment.formula = mkOption {
176         default = null;
177         type = types.nullOr types.str;
178         example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
179         description = ''
180           "bantime.formula" used by default to calculate next value of ban time, default value bellow,
181           the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32 ...
182         '';
183       };
185       bantime-increment.multipliers = mkOption {
186         default = null;
187         type = types.nullOr types.str;
188         example = "1 2 4 8 16 32 64";
189         description = ''
190           "bantime.multipliers" used to calculate next value of ban time instead of formula, corresponding
191           previously ban count and given "bantime.factor" (for multipliers default is 1);
192           following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count,
193           always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours
194         '';
195       };
197       bantime-increment.overalljails = mkOption {
198         default = null;
199         type = types.nullOr types.bool;
200         example = true;
201         description = ''
202           "bantime.overalljails" (if true) specifies the search of IP in the database will be executed
203           cross over all jails, if false (default), only current jail of the ban IP will be searched.
204         '';
205       };
207       ignoreIP = mkOption {
208         default = [ ];
209         type = types.listOf types.str;
210         example = [ "192.168.0.0/16" "2001:DB8::42" ];
211         description = ''
212           "ignoreIP" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban will not ban a host which
213           matches an address in this list. Several addresses can be defined using space (and/or comma) separator.
214         '';
215       };
217       daemonSettings = mkOption {
218         inherit (configFormat) type;
220         defaultText = literalExpression ''
221           {
222             Definition = {
223               logtarget = "SYSLOG";
224               socket = "/run/fail2ban/fail2ban.sock";
225               pidfile = "/run/fail2ban/fail2ban.pid";
226               dbfile = "/var/lib/fail2ban/fail2ban.sqlite3";
227             };
228           }
229         '';
230         description = ''
231           The contents of Fail2ban's main configuration file.
232           It's generally not necessary to change it.
233         '';
234       };
236       jails = mkOption {
237         default = { };
238         example = literalExpression ''
239           {
240             apache-nohome-iptables = {
241               settings = {
242                 # Block an IP address if it accesses a non-existent
243                 # home directory more than 5 times in 10 minutes,
244                 # since that indicates that it's scanning.
245                 filter = "apache-nohome";
246                 action = '''iptables-multiport[name=HTTP, port="http,https"]''';
247                 logpath = "/var/log/httpd/error_log*";
248                 backend = "auto";
249                 findtime = 600;
250                 bantime = 600;
251                 maxretry = 5;
252               };
253             };
254             dovecot = {
255               settings = {
256                 # block IPs which failed to log-in
257                 # aggressive mode add blocking for aborted connections
258                 filter = "dovecot[mode=aggressive]";
259                 maxretry = 3;
260               };
261             };
262           };
263         '';
264         type = with types; attrsOf (either lines (submodule ({ name, ... }: {
265           options = {
266             enabled = mkEnableOption "this jail" // {
267               default = true;
268               readOnly = name == "DEFAULT";
269             };
271             filter = mkOption {
272               type = nullOr (either str configFormat.type);
274               default = null;
275               description = "Content of the filter used for this jail.";
276             };
278             settings = mkOption {
279               inherit (settingsFormat) type;
281               default = { };
282               description = "Additional settings for this jail.";
283             };
284           };
285         })));
286         description = ''
287           The configuration of each Fail2ban “jail”.  A jail
288           consists of an action (such as blocking a port using
289           {command}`iptables`) that is triggered when a
290           filter applied to a log file triggers more than a certain
291           number of times in a certain time period.  Actions are
292           defined in {file}`/etc/fail2ban/action.d`,
293           while filters are defined in
294           {file}`/etc/fail2ban/filter.d`.
296           NixOS comes with a default `sshd` jail;
297           for it to work well,
298           {option}`services.openssh.logLevel` should be set to
299           `"VERBOSE"` or higher so that fail2ban
300           can observe failed login attempts.
301           This module sets it to `"VERBOSE"` if
302           not set otherwise, so enabling fail2ban can make SSH logs
303           more verbose.
304         '';
305       };
307     };
309   };
311   ###### implementation
313   config = mkIf cfg.enable {
314     assertions = [
315       {
316         assertion = cfg.bantime-increment.formula == null || cfg.bantime-increment.multipliers == null;
317         message = ''
318           Options `services.fail2ban.bantime-increment.formula` and `services.fail2ban.bantime-increment.multipliers` cannot be both specified.
319         '';
320       }
321     ];
323     warnings = mkIf (!config.networking.firewall.enable && !config.networking.nftables.enable) [
324       "fail2ban can not be used without a firewall"
325     ];
327     environment.systemPackages = [ cfg.package ];
329     environment.etc = {
330       "fail2ban/fail2ban.local".source = fail2banConf;
331       "fail2ban/jail.local".source = jailConf;
332       "fail2ban/fail2ban.conf".source = "${cfg.package}/etc/fail2ban/fail2ban.conf";
333       "fail2ban/jail.conf".source = "${cfg.package}/etc/fail2ban/jail.conf";
334       "fail2ban/paths-common.conf".source = "${cfg.package}/etc/fail2ban/paths-common.conf";
335       "fail2ban/paths-nixos.conf".source = pathsConf;
336       "fail2ban/action.d".source = "${cfg.package}/etc/fail2ban/action.d/*.conf";
337       "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf";
338     } // (mapAttrs' mkFilter (filterAttrs (_: v: v.filter != null && !builtins.isString v.filter) attrsJails));
340     systemd.packages = [ cfg.package ];
341     systemd.services.fail2ban = {
342       wantedBy = [ "multi-user.target" ];
343       partOf = optional config.networking.firewall.enable "firewall.service";
345       restartTriggers = [ fail2banConf jailConf pathsConf ];
347       path = [ cfg.package cfg.packageFirewall pkgs.iproute2 ] ++ cfg.extraPackages;
349       serviceConfig = {
350         # Capabilities
351         CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
352         # Security
353         NoNewPrivileges = true;
354         # Directory
355         RuntimeDirectory = "fail2ban";
356         RuntimeDirectoryMode = "0750";
357         StateDirectory = "fail2ban";
358         StateDirectoryMode = "0750";
359         LogsDirectory = "fail2ban";
360         LogsDirectoryMode = "0750";
361         # Sandboxing
362         ProtectSystem = "strict";
363         ProtectHome = true;
364         PrivateTmp = true;
365         PrivateDevices = true;
366         ProtectHostname = true;
367         ProtectKernelTunables = true;
368         ProtectKernelModules = true;
369         ProtectControlGroups = true;
370       };
371     };
373     # Defaults for the daemon settings
374     services.fail2ban.daemonSettings.Definition = {
375       logtarget = mkDefault "SYSLOG";
376       socket = mkDefault "/run/fail2ban/fail2ban.sock";
377       pidfile = mkDefault "/run/fail2ban/fail2ban.pid";
378       dbfile = mkDefault "/var/lib/fail2ban/fail2ban.sqlite3";
379     };
381     # Add some reasonable default jails.  The special "DEFAULT" jail
382     # sets default values for all other jails.
383     services.fail2ban.jails = mkMerge [
384       {
385         DEFAULT.settings = (optionalAttrs cfg.bantime-increment.enable
386           ({ "bantime.increment" = cfg.bantime-increment.enable; } // (mapAttrs'
387             (name: nameValuePair "bantime.${name}")
388             (filterAttrs (n: v: v != null && n != "enable") cfg.bantime-increment))
389           )
390         ) // {
391           # Miscellaneous options
392           inherit (cfg) banaction maxretry bantime;
393           ignoreip = ''127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}'';
394           backend = "systemd";
395           # Actions
396           banaction_allports = cfg.banaction-allports;
397         };
398       }
400       # Block SSH if there are too many failing connection attempts.
401       (mkIf config.services.openssh.enable {
402         sshd.settings.port = mkDefault (concatMapStringsSep "," builtins.toString config.services.openssh.ports);
403       })
404     ];
406     # Benefits from verbose sshd logging to observe failed login attempts,
407     # so we set that here unless the user overrode it.
408     services.openssh.settings.LogLevel = mkDefault "VERBOSE";
409   };