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