at 23.11-pre 14 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.fail2ban; 8 9 fail2banConf = pkgs.writeText "fail2ban.local" cfg.daemonConfig; 10 11 jailConf = pkgs.writeText "jail.local" '' 12 [INCLUDES] 13 14 before = paths-nixos.conf 15 16 ${concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def: 17 optionalString (def != "") 18 '' 19 [${name}] 20 ${def} 21 '')))} 22 ''; 23 24 pathsConf = pkgs.writeText "paths-nixos.conf" '' 25 # NixOS 26 27 [INCLUDES] 28 29 before = paths-common.conf 30 31 after = paths-overrides.local 32 33 [DEFAULT] 34 ''; 35 36in 37 38{ 39 40 ###### interface 41 42 options = { 43 44 services.fail2ban = { 45 enable = mkOption { 46 default = false; 47 type = types.bool; 48 description = lib.mdDoc '' 49 Whether to enable the fail2ban service. 50 51 See the documentation of {option}`services.fail2ban.jails` 52 for what jails are enabled by default. 53 ''; 54 }; 55 56 package = mkOption { 57 default = pkgs.fail2ban; 58 defaultText = literalExpression "pkgs.fail2ban"; 59 type = types.package; 60 example = literalExpression "pkgs.fail2ban_0_11"; 61 description = lib.mdDoc "The fail2ban package to use for running the fail2ban service."; 62 }; 63 64 packageFirewall = mkOption { 65 default = config.networking.firewall.package; 66 defaultText = literalExpression "config.networking.firewall.package"; 67 type = types.package; 68 description = lib.mdDoc "The firewall package used by fail2ban service. Defaults to the package for your firewall (iptables or nftables)."; 69 }; 70 71 extraPackages = mkOption { 72 default = []; 73 type = types.listOf types.package; 74 example = lib.literalExpression "[ pkgs.ipset ]"; 75 description = lib.mdDoc '' 76 Extra packages to be made available to the fail2ban service. The example contains 77 the packages needed by the `iptables-ipset-proto6` action. 78 ''; 79 }; 80 81 bantime = mkOption { 82 default = null; 83 type = types.nullOr types.str; 84 example = "10m"; 85 description = lib.mdDoc "Number of seconds that a host is banned."; 86 }; 87 88 maxretry = mkOption { 89 default = 3; 90 type = types.ints.unsigned; 91 description = lib.mdDoc "Number of failures before a host gets banned."; 92 }; 93 94 banaction = mkOption { 95 default = if config.networking.nftables.enable then "nftables-multiport" else "iptables-multiport"; 96 defaultText = literalExpression ''if config.networking.nftables.enable then "nftables-multiport" else "iptables-multiport"''; 97 type = types.str; 98 description = lib.mdDoc '' 99 Default banning action (e.g. iptables, iptables-new, iptables-multiport, 100 iptables-ipset-proto6-allports, shorewall, etc). It is used to 101 define action_* variables. Can be overridden globally or per 102 section within jail.local file 103 ''; 104 }; 105 106 banaction-allports = mkOption { 107 default = if config.networking.nftables.enable then "nftables-allport" else "iptables-allport"; 108 defaultText = literalExpression ''if config.networking.nftables.enable then "nftables-allport" else "iptables-allport"''; 109 type = types.str; 110 description = lib.mdDoc '' 111 Default banning action (e.g. iptables, iptables-new, iptables-multiport, 112 shorewall, etc) for "allports" jails. It is used to define action_* variables. Can be overridden 113 globally or per section within jail.local file 114 ''; 115 }; 116 117 bantime-increment.enable = mkOption { 118 default = false; 119 type = types.bool; 120 description = lib.mdDoc '' 121 "bantime.increment" allows to use database for searching of previously banned ip's to increase 122 a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32 ... 123 ''; 124 }; 125 126 bantime-increment.rndtime = mkOption { 127 default = null; 128 type = types.nullOr types.str; 129 example = "8m"; 130 description = lib.mdDoc '' 131 "bantime.rndtime" is the max number of seconds using for mixing with random time 132 to prevent "clever" botnets calculate exact time IP can be unbanned again 133 ''; 134 }; 135 136 bantime-increment.maxtime = mkOption { 137 default = null; 138 type = types.nullOr types.str; 139 example = "48h"; 140 description = lib.mdDoc '' 141 "bantime.maxtime" is the max number of seconds using the ban time can reach (don't grows further) 142 ''; 143 }; 144 145 bantime-increment.factor = mkOption { 146 default = null; 147 type = types.nullOr types.str; 148 example = "4"; 149 description = lib.mdDoc '' 150 "bantime.factor" is a coefficient to calculate exponent growing of the formula or common multiplier, 151 default value of factor is 1 and with default value of formula, the ban time grows by 1, 2, 4, 8, 16 ... 152 ''; 153 }; 154 155 bantime-increment.formula = mkOption { 156 default = null; 157 type = types.nullOr types.str; 158 example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)"; 159 description = lib.mdDoc '' 160 "bantime.formula" used by default to calculate next value of ban time, default value bellow, 161 the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32 ... 162 ''; 163 }; 164 165 bantime-increment.multipliers = mkOption { 166 default = null; 167 type = types.nullOr types.str; 168 example = "1 2 4 8 16 32 64"; 169 description = lib.mdDoc '' 170 "bantime.multipliers" used to calculate next value of ban time instead of formula, corresponding 171 previously ban count and given "bantime.factor" (for multipliers default is 1); 172 following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count, 173 always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours 174 ''; 175 }; 176 177 bantime-increment.overalljails = mkOption { 178 default = null; 179 type = types.nullOr types.bool; 180 example = true; 181 description = lib.mdDoc '' 182 "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 184 ''; 185 }; 186 187 ignoreIP = mkOption { 188 default = [ ]; 189 type = types.listOf types.str; 190 example = [ "192.168.0.0/16" "2001:DB8::42" ]; 191 description = lib.mdDoc '' 192 "ignoreIP" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban will not ban a host which 193 matches an address in this list. Several addresses can be defined using space (and/or comma) separator. 194 ''; 195 }; 196 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 }; 211 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 '' 220 { 221 findtime = "15m"; 222 } 223 ''; 224 }; 225 226 jails = mkOption { 227 default = { }; 228 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 } 249 ''; 250 type = types.attrsOf types.lines; 251 description = lib.mdDoc '' 252 The configuration of each Fail2ban jail. A jail 253 consists of an action (such as blocking a port using 254 {command}`iptables`) that is triggered when a 255 filter applied to a log file triggers more than a certain 256 number of times in a certain time period. Actions are 257 defined in {file}`/etc/fail2ban/action.d`, 258 while filters are defined in 259 {file}`/etc/fail2ban/filter.d`. 260 261 NixOS comes with a default `sshd` jail; 262 for it to work well, 263 {option}`services.openssh.logLevel` should be set to 264 `"VERBOSE"` or higher so that fail2ban 265 can observe failed login attempts. 266 This module sets it to `"VERBOSE"` if 267 not set otherwise, so enabling fail2ban can make SSH logs 268 more verbose. 269 ''; 270 }; 271 272 }; 273 274 }; 275 276 ###### implementation 277 278 config = mkIf cfg.enable { 279 assertions = [ 280 { 281 assertion = (cfg.bantime-increment.formula == null || cfg.bantime-increment.multipliers == null); 282 message = '' 283 Options `services.fail2ban.bantime-increment.formula` and `services.fail2ban.bantime-increment.multipliers` cannot be both specified. 284 ''; 285 } 286 ]; 287 288 warnings = mkIf (!config.networking.firewall.enable && !config.networking.nftables.enable) [ 289 "fail2ban can not be used without a firewall" 290 ]; 291 292 environment.systemPackages = [ cfg.package ]; 293 294 environment.etc = { 295 "fail2ban/fail2ban.local".source = fail2banConf; 296 "fail2ban/jail.local".source = jailConf; 297 "fail2ban/fail2ban.conf".source = "${cfg.package}/etc/fail2ban/fail2ban.conf"; 298 "fail2ban/jail.conf".source = "${cfg.package}/etc/fail2ban/jail.conf"; 299 "fail2ban/paths-common.conf".source = "${cfg.package}/etc/fail2ban/paths-common.conf"; 300 "fail2ban/paths-nixos.conf".source = pathsConf; 301 "fail2ban/action.d".source = "${cfg.package}/etc/fail2ban/action.d/*.conf"; 302 "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf"; 303 }; 304 305 systemd.packages = [ cfg.package ]; 306 systemd.services.fail2ban = { 307 wantedBy = [ "multi-user.target" ]; 308 partOf = optional config.networking.firewall.enable "firewall.service"; 309 310 restartTriggers = [ fail2banConf jailConf pathsConf ]; 311 312 path = [ cfg.package cfg.packageFirewall pkgs.iproute2 ] ++ cfg.extraPackages; 313 314 serviceConfig = { 315 # Capabilities 316 CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ]; 317 # Security 318 NoNewPrivileges = true; 319 # Directory 320 RuntimeDirectory = "fail2ban"; 321 RuntimeDirectoryMode = "0750"; 322 StateDirectory = "fail2ban"; 323 StateDirectoryMode = "0750"; 324 LogsDirectory = "fail2ban"; 325 LogsDirectoryMode = "0750"; 326 # Sandboxing 327 ProtectSystem = "strict"; 328 ProtectHome = true; 329 PrivateTmp = true; 330 PrivateDevices = true; 331 ProtectHostname = true; 332 ProtectKernelTunables = true; 333 ProtectKernelModules = true; 334 ProtectControlGroups = true; 335 }; 336 }; 337 338 # Add some reasonable default jails. The special "DEFAULT" jail 339 # 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. 365 # Benefits from verbose sshd logging to observe failed login attempts, 366 # 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 ''; 372 }; 373}