at 24.11-pre 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 = '' 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 = mkPackageOption pkgs "fail2ban" { 81 example = "fail2ban_0_11"; 82 }; 83 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 }; 90 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 }; 100 101 bantime = mkOption { 102 default = "10m"; 103 type = types.str; 104 example = "1h"; 105 description = "Number of seconds that a host is banned."; 106 }; 107 108 maxretry = mkOption { 109 default = 3; 110 type = types.ints.unsigned; 111 description = "Number of failures before a host gets banned."; 112 }; 113 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 }; 125 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 }; 136 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 }; 145 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 }; 155 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 }; 164 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 }; 174 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 }; 184 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 }; 196 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 }; 206 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 }; 216 217 daemonSettings = mkOption { 218 inherit (configFormat) type; 219 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 }; 235 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 }; 270 271 filter = mkOption { 272 type = nullOr (either str configFormat.type); 273 274 default = null; 275 description = "Content of the filter used for this jail."; 276 }; 277 278 settings = mkOption { 279 inherit (settingsFormat) type; 280 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`. 295 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 }; 306 307 }; 308 309 }; 310 311 ###### implementation 312 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 ]; 322 323 warnings = mkIf (!config.networking.firewall.enable && !config.networking.nftables.enable) [ 324 "fail2ban can not be used without a firewall" 325 ]; 326 327 environment.systemPackages = [ cfg.package ]; 328 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)); 339 340 systemd.packages = [ cfg.package ]; 341 systemd.services.fail2ban = { 342 wantedBy = [ "multi-user.target" ]; 343 partOf = optional config.networking.firewall.enable "firewall.service"; 344 345 restartTriggers = [ fail2banConf jailConf pathsConf ]; 346 347 path = [ cfg.package cfg.packageFirewall pkgs.iproute2 ] ++ cfg.extraPackages; 348 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 }; 372 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 }; 380 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 } 399 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 ]; 405 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 }; 410}