Merge pull request #201907 from Tom-Hubrecht/fail2ban

authored by

Ryan Lahfa and committed by
GitHub
7672c1e9 5a233eee

+154 -92
+2
nixos/doc/manual/release-notes/rl-2311.section.md
··· 82 82 83 83 - DocBook option documentation is no longer supported, all module documentation now uses markdown. 84 84 85 + - `services.fail2ban.jails` can now be configured with attribute sets defining settings and filters instead of lines. The stringed options `daemonConfig` and `extraSettings` have respectively been replaced by `daemonSettings` and `jails.DEFAULT.settings` which use attribute sets. 86 + 85 87 - `services.nginx` gained a `defaultListen` option at server-level with support for PROXY protocol listeners, also `proxyProtocol` is now exposed in `services.nginx.virtualHosts.<name>.listen` option. It is now possible to run PROXY listeners and non-PROXY listeners at a server-level, see [#213510](https://github.com/NixOS/nixpkgs/pull/213510/) for more details. 86 88 87 89 - `services.prometheus.exporters` has a new exporter to monitor electrical power consumption based on PowercapRAPL sensor called [Scaphandre](https://github.com/hubblo-org/scaphandre), see [#239803](https://github.com/NixOS/nixpkgs/pull/239803) for more details.
+133 -92
nixos/modules/services/security/fail2ban.nix
··· 3 3 with lib; 4 4 5 5 let 6 - 7 6 cfg = config.services.fail2ban; 8 7 9 - fail2banConf = pkgs.writeText "fail2ban.local" cfg.daemonConfig; 8 + settingsFormat = pkgs.formats.keyValue { }; 10 9 11 - jailConf = pkgs.writeText "jail.local" '' 12 - [INCLUDES] 10 + configFormat = pkgs.formats.ini { 11 + mkKeyValue = generators.mkKeyValueDefault { } " = "; 12 + }; 13 13 14 - before = paths-nixos.conf 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 + }; 15 22 16 - ${concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def: 17 - optionalString (def != "") 18 - '' 19 - [${name}] 20 - ${def} 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) ]; 23 44 24 45 pathsConf = pkgs.writeText "paths-nixos.conf" '' 25 46 # NixOS ··· 32 53 33 54 [DEFAULT] 34 55 ''; 35 - 36 56 in 37 57 38 58 { 39 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 + 40 65 ###### interface 41 66 42 67 options = { 43 - 44 68 services.fail2ban = { 45 69 enable = mkOption { 46 70 default = false; ··· 69 93 }; 70 94 71 95 extraPackages = mkOption { 72 - default = []; 96 + default = [ ]; 73 97 type = types.listOf types.package; 74 98 example = lib.literalExpression "[ pkgs.ipset ]"; 75 99 description = lib.mdDoc '' ··· 180 204 example = true; 181 205 description = lib.mdDoc '' 182 206 "bantime.overalljails" (if true) specifies the search of IP in the database will be executed 183 - cross over all jails, if false (default), only current jail of the ban IP will be searched 207 + cross over all jails, if false (default), only current jail of the ban IP will be searched. 184 208 ''; 185 209 }; 186 210 ··· 194 218 ''; 195 219 }; 196 220 197 - daemonConfig = mkOption { 198 - default = '' 199 - [Definition] 200 - logtarget = SYSLOG 201 - socket = /run/fail2ban/fail2ban.sock 202 - pidfile = /run/fail2ban/fail2ban.pid 203 - dbfile = /var/lib/fail2ban/fail2ban.sqlite3 204 - ''; 205 - type = types.lines; 206 - description = lib.mdDoc '' 207 - The contents of Fail2ban's main configuration file. It's 208 - generally not necessary to change it. 209 - ''; 210 - }; 221 + daemonSettings = mkOption { 222 + inherit (configFormat) type; 211 223 212 - extraSettings = mkOption { 213 - type = with types; attrsOf (oneOf [ bool ints.positive str ]); 214 - default = {}; 215 - description = lib.mdDoc '' 216 - Extra default configuration for all jails (i.e. `[DEFAULT]`). See 217 - <https://github.com/fail2ban/fail2ban/blob/master/config/jail.conf> for an overview. 218 - ''; 219 - example = literalExpression '' 224 + defaultText = literalExpression '' 220 225 { 221 - findtime = "15m"; 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 + }; 222 232 } 233 + ''; 234 + description = lib.mdDoc '' 235 + The contents of Fail2ban's main configuration file. 236 + It's generally not necessary to change it. 223 237 ''; 224 238 }; 225 239 226 240 jails = mkOption { 227 241 default = { }; 228 242 example = literalExpression '' 229 - { apache-nohome-iptables = ''' 230 - # Block an IP address if it accesses a non-existent 231 - # home directory more than 5 times in 10 minutes, 232 - # since that indicates that it's scanning. 233 - filter = apache-nohome 234 - action = iptables-multiport[name=HTTP, port="http,https"] 235 - logpath = /var/log/httpd/error_log* 236 - backend = auto 237 - findtime = 600 238 - bantime = 600 239 - maxretry = 5 240 - '''; 241 - dovecot = ''' 242 - # block IPs which failed to log-in 243 - # aggressive mode add blocking for aborted connections 244 - enabled = true 245 - filter = dovecot[mode=aggressive] 246 - maxretry = 3 247 - '''; 248 - } 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 + }; 249 267 ''; 250 - type = types.attrsOf types.lines; 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 + }))); 251 290 description = lib.mdDoc '' 252 291 The configuration of each Fail2ban “jail”. A jail 253 292 consists of an action (such as blocking a port using ··· 278 317 config = mkIf cfg.enable { 279 318 assertions = [ 280 319 { 281 - assertion = (cfg.bantime-increment.formula == null || cfg.bantime-increment.multipliers == null); 320 + assertion = cfg.bantime-increment.formula == null || cfg.bantime-increment.multipliers == null; 282 321 message = '' 283 322 Options `services.fail2ban.bantime-increment.formula` and `services.fail2ban.bantime-increment.multipliers` cannot be both specified. 284 323 ''; ··· 300 339 "fail2ban/paths-nixos.conf".source = pathsConf; 301 340 "fail2ban/action.d".source = "${cfg.package}/etc/fail2ban/action.d/*.conf"; 302 341 "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf"; 303 - }; 342 + } // (mapAttrs' mkFilter (filterAttrs (_: v: v.filter != null && !builtins.isString v.filter) attrsJails)); 304 343 305 344 systemd.packages = [ cfg.package ]; 306 345 systemd.services.fail2ban = { ··· 335 374 }; 336 375 }; 337 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 + 338 385 # Add some reasonable default jails. The special "DEFAULT" jail 339 386 # sets default values for all other jails. 340 - services.fail2ban.jails.DEFAULT = '' 341 - # Bantime increment options 342 - bantime.increment = ${boolToString cfg.bantime-increment.enable} 343 - ${optionalString (cfg.bantime-increment.rndtime != null) "bantime.rndtime = ${cfg.bantime-increment.rndtime}"} 344 - ${optionalString (cfg.bantime-increment.maxtime != null) "bantime.maxtime = ${cfg.bantime-increment.maxtime}"} 345 - ${optionalString (cfg.bantime-increment.factor != null) "bantime.factor = ${cfg.bantime-increment.factor}"} 346 - ${optionalString (cfg.bantime-increment.formula != null) "bantime.formula = ${cfg.bantime-increment.formula}"} 347 - ${optionalString (cfg.bantime-increment.multipliers != null) "bantime.multipliers = ${cfg.bantime-increment.multipliers}"} 348 - ${optionalString (cfg.bantime-increment.overalljails != null) "bantime.overalljails = ${boolToString cfg.bantime-increment.overalljails}"} 349 - # Miscellaneous options 350 - ignoreip = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP} 351 - ${optionalString (cfg.bantime != null) '' 352 - bantime = ${cfg.bantime} 353 - ''} 354 - maxretry = ${toString cfg.maxretry} 355 - backend = systemd 356 - # Actions 357 - banaction = ${cfg.banaction} 358 - banaction_allports = ${cfg.banaction-allports} 359 - ${optionalString (cfg.extraSettings != {}) '' 360 - # Extra settings 361 - ${generators.toKeyValue {} cfg.extraSettings} 362 - ''} 363 - ''; 364 - # Block SSH if there are too many failing connection attempts. 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; 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 + 365 410 # Benefits from verbose sshd logging to observe failed login attempts, 366 411 # so we set that here unless the user overrode it. 367 - services.openssh.settings.LogLevel = lib.mkDefault "VERBOSE"; 368 - services.fail2ban.jails.sshd = mkDefault '' 369 - enabled = true 370 - port = ${concatMapStringsSep "," (p: toString p) config.services.openssh.ports} 371 - ''; 412 + services.openssh.settings.LogLevel = mkDefault "VERBOSE"; 372 413 }; 373 414 }
+1
nixos/tests/all-tests.nix
··· 256 256 etebase-server = handleTest ./etebase-server.nix {}; 257 257 etesync-dav = handleTest ./etesync-dav.nix {}; 258 258 evcc = handleTest ./evcc.nix {}; 259 + fail2ban = handleTest ./fail2ban.nix { }; 259 260 fakeroute = handleTest ./fakeroute.nix {}; 260 261 fancontrol = handleTest ./fancontrol.nix {}; 261 262 fcitx5 = handleTest ./fcitx5 {};
+18
nixos/tests/fail2ban.nix
··· 1 + import ./make-test-python.nix ({ pkgs, ... }: { 2 + name = "fail2ban"; 3 + 4 + nodes.machine = _: { 5 + services.fail2ban = { 6 + enable = true; 7 + bantime-increment.enable = true; 8 + }; 9 + 10 + services.openssh.enable = true; 11 + }; 12 + 13 + testScript = '' 14 + machine.wait_for_unit("multi-user.target") 15 + 16 + machine.wait_for_unit("fail2ban") 17 + ''; 18 + })