|
|
|
@@ -3,23 +3,44 @@
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
|
|
let
|
|
|
|
|
|
|
|
|
|
cfg = config.services.fail2ban;
|
|
|
|
|
|
|
|
|
|
fail2banConf = pkgs.writeText "fail2ban.local" cfg.daemonConfig;
|
|
|
|
|
settingsFormat = pkgs.formats.keyValue { };
|
|
|
|
|
|
|
|
|
|
jailConf = pkgs.writeText "jail.local" ''
|
|
|
|
|
[INCLUDES]
|
|
|
|
|
configFormat = pkgs.formats.ini {
|
|
|
|
|
mkKeyValue = generators.mkKeyValueDefault { } " = ";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
before = paths-nixos.conf
|
|
|
|
|
mkJailConfig = name: attrs:
|
|
|
|
|
optionalAttrs (name != "DEFAULT") { inherit (attrs) enabled; } //
|
|
|
|
|
optionalAttrs (attrs.filter != null) { filter = if (builtins.isString filter) then filter else name; } //
|
|
|
|
|
attrs.settings;
|
|
|
|
|
|
|
|
|
|
${concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def:
|
|
|
|
|
optionalString (def != "")
|
|
|
|
|
''
|
|
|
|
|
[${name}]
|
|
|
|
|
${def}
|
|
|
|
|
'')))}
|
|
|
|
|
'';
|
|
|
|
|
mkFilter = name: attrs: nameValuePair "fail2ban/filter.d/${name}.conf" {
|
|
|
|
|
source = configFormat.generate "filter.d/${name}.conf" attrs.filter;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fail2banConf = configFormat.generate "fail2ban.local" cfg.daemonSettings;
|
|
|
|
|
|
|
|
|
|
strJails = filterAttrs (_: builtins.isString) cfg.jails;
|
|
|
|
|
attrsJails = filterAttrs (_: builtins.isAttrs) cfg.jails;
|
|
|
|
|
|
|
|
|
|
jailConf =
|
|
|
|
|
let
|
|
|
|
|
configFile = configFormat.generate "jail.local" (
|
|
|
|
|
{ INCLUDES.before = "paths-nixos.conf"; } // (mapAttrs mkJailConfig attrsJails)
|
|
|
|
|
);
|
|
|
|
|
extraConfig = concatStringsSep "\n" (attrValues (mapAttrs
|
|
|
|
|
(name: def:
|
|
|
|
|
optionalString (def != "")
|
|
|
|
|
''
|
|
|
|
|
[${name}]
|
|
|
|
|
${def}
|
|
|
|
|
'')
|
|
|
|
|
strJails));
|
|
|
|
|
|
|
|
|
|
in
|
|
|
|
|
pkgs.concatText "jail.local" [ configFile (pkgs.writeText "extra-jail.local" extraConfig) ];
|
|
|
|
|
|
|
|
|
|
pathsConf = pkgs.writeText "paths-nixos.conf" ''
|
|
|
|
|
# NixOS
|
|
|
|
@@ -32,15 +53,18 @@ let
|
|
|
|
|
|
|
|
|
|
[DEFAULT]
|
|
|
|
|
'';
|
|
|
|
|
|
|
|
|
|
in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
imports = [
|
|
|
|
|
(mkRemovedOptionModule [ "services" "fail2ban" "daemonConfig" ] "The daemon is now configured through the attribute set `services.fail2ban.daemonSettings`.")
|
|
|
|
|
(mkRemovedOptionModule [ "services" "fail2ban" "extraSettings" ] "The extra default configuration can now be set using `services.fail2ban.jails.DEFAULT.settings`.")
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
###### interface
|
|
|
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
|
|
|
|
services.fail2ban = {
|
|
|
|
|
enable = mkOption {
|
|
|
|
|
default = false;
|
|
|
|
@@ -69,7 +93,7 @@ in
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
extraPackages = mkOption {
|
|
|
|
|
default = [];
|
|
|
|
|
default = [ ];
|
|
|
|
|
type = types.listOf types.package;
|
|
|
|
|
example = lib.literalExpression "[ pkgs.ipset ]";
|
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
@@ -180,7 +204,7 @@ in
|
|
|
|
|
example = true;
|
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
"bantime.overalljails" (if true) specifies the search of IP in the database will be executed
|
|
|
|
|
cross over all jails, if false (default), only current jail of the ban IP will be searched
|
|
|
|
|
cross over all jails, if false (default), only current jail of the ban IP will be searched.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -194,60 +218,75 @@ in
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
daemonConfig = mkOption {
|
|
|
|
|
default = ''
|
|
|
|
|
[Definition]
|
|
|
|
|
logtarget = SYSLOG
|
|
|
|
|
socket = /run/fail2ban/fail2ban.sock
|
|
|
|
|
pidfile = /run/fail2ban/fail2ban.pid
|
|
|
|
|
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
|
|
|
|
|
'';
|
|
|
|
|
type = types.lines;
|
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
The contents of Fail2ban's main configuration file. It's
|
|
|
|
|
generally not necessary to change it.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
daemonSettings = mkOption {
|
|
|
|
|
inherit (configFormat) type;
|
|
|
|
|
|
|
|
|
|
extraSettings = mkOption {
|
|
|
|
|
type = with types; attrsOf (oneOf [ bool ints.positive str ]);
|
|
|
|
|
default = {};
|
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
Extra default configuration for all jails (i.e. `[DEFAULT]`). See
|
|
|
|
|
<https://github.com/fail2ban/fail2ban/blob/master/config/jail.conf> for an overview.
|
|
|
|
|
'';
|
|
|
|
|
example = literalExpression ''
|
|
|
|
|
defaultText = literalExpression ''
|
|
|
|
|
{
|
|
|
|
|
findtime = "15m";
|
|
|
|
|
Definition = {
|
|
|
|
|
logtarget = "SYSLOG";
|
|
|
|
|
socket = "/run/fail2ban/fail2ban.sock";
|
|
|
|
|
pidfile = "/run/fail2ban/fail2ban.pid";
|
|
|
|
|
dbfile = "/var/lib/fail2ban/fail2ban.sqlite3";
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
'';
|
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
The contents of Fail2ban's main configuration file.
|
|
|
|
|
It's generally not necessary to change it.
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
jails = mkOption {
|
|
|
|
|
default = { };
|
|
|
|
|
example = literalExpression ''
|
|
|
|
|
{ apache-nohome-iptables = '''
|
|
|
|
|
# Block an IP address if it accesses a non-existent
|
|
|
|
|
# home directory more than 5 times in 10 minutes,
|
|
|
|
|
# since that indicates that it's scanning.
|
|
|
|
|
filter = apache-nohome
|
|
|
|
|
action = iptables-multiport[name=HTTP, port="http,https"]
|
|
|
|
|
logpath = /var/log/httpd/error_log*
|
|
|
|
|
backend = auto
|
|
|
|
|
findtime = 600
|
|
|
|
|
bantime = 600
|
|
|
|
|
maxretry = 5
|
|
|
|
|
''';
|
|
|
|
|
dovecot = '''
|
|
|
|
|
# block IPs which failed to log-in
|
|
|
|
|
# aggressive mode add blocking for aborted connections
|
|
|
|
|
enabled = true
|
|
|
|
|
filter = dovecot[mode=aggressive]
|
|
|
|
|
maxretry = 3
|
|
|
|
|
''';
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
apache-nohome-iptables = {
|
|
|
|
|
settings = {
|
|
|
|
|
# Block an IP address if it accesses a non-existent
|
|
|
|
|
# home directory more than 5 times in 10 minutes,
|
|
|
|
|
# since that indicates that it's scanning.
|
|
|
|
|
filter = "apache-nohome";
|
|
|
|
|
action = '''iptables-multiport[name=HTTP, port="http,https"]''';
|
|
|
|
|
logpath = "/var/log/httpd/error_log*";
|
|
|
|
|
backend = "auto";
|
|
|
|
|
findtime = 600;
|
|
|
|
|
bantime = 600;
|
|
|
|
|
maxretry = 5;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
dovecot = {
|
|
|
|
|
settings = {
|
|
|
|
|
# block IPs which failed to log-in
|
|
|
|
|
# aggressive mode add blocking for aborted connections
|
|
|
|
|
filter = "dovecot[mode=aggressive]";
|
|
|
|
|
maxretry = 3;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
'';
|
|
|
|
|
type = types.attrsOf types.lines;
|
|
|
|
|
type = with types; attrsOf (either lines (submodule ({ name, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
enabled = mkEnableOption "this jail." // {
|
|
|
|
|
default = true;
|
|
|
|
|
readOnly = name == "DEFAULT";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
filter = mkOption {
|
|
|
|
|
type = nullOr (either str configFormat.type);
|
|
|
|
|
|
|
|
|
|
default = null;
|
|
|
|
|
description = lib.mdDoc "Content of the filter used for this jail.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
settings = mkOption {
|
|
|
|
|
inherit (settingsFormat) type;
|
|
|
|
|
|
|
|
|
|
default = { };
|
|
|
|
|
description = lib.mdDoc "Additional settings for this jail.";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
})));
|
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
The configuration of each Fail2ban “jail”. A jail
|
|
|
|
|
consists of an action (such as blocking a port using
|
|
|
|
@@ -278,7 +317,7 @@ in
|
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
|
assertions = [
|
|
|
|
|
{
|
|
|
|
|
assertion = (cfg.bantime-increment.formula == null || cfg.bantime-increment.multipliers == null);
|
|
|
|
|
assertion = cfg.bantime-increment.formula == null || cfg.bantime-increment.multipliers == null;
|
|
|
|
|
message = ''
|
|
|
|
|
Options `services.fail2ban.bantime-increment.formula` and `services.fail2ban.bantime-increment.multipliers` cannot be both specified.
|
|
|
|
|
'';
|
|
|
|
@@ -300,7 +339,7 @@ in
|
|
|
|
|
"fail2ban/paths-nixos.conf".source = pathsConf;
|
|
|
|
|
"fail2ban/action.d".source = "${cfg.package}/etc/fail2ban/action.d/*.conf";
|
|
|
|
|
"fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf";
|
|
|
|
|
};
|
|
|
|
|
} // (mapAttrs' mkFilter (filterAttrs (_: v: v.filter != null && !builtins.isString v.filter) attrsJails));
|
|
|
|
|
|
|
|
|
|
systemd.packages = [ cfg.package ];
|
|
|
|
|
systemd.services.fail2ban = {
|
|
|
|
@@ -335,39 +374,41 @@ in
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# Defaults for the daemon settings
|
|
|
|
|
services.fail2ban.daemonSettings.Definition = {
|
|
|
|
|
logtarget = mkDefault "SYSLOG";
|
|
|
|
|
socket = mkDefault "/run/fail2ban/fail2ban.sock";
|
|
|
|
|
pidfile = mkDefault "/run/fail2ban/fail2ban.pid";
|
|
|
|
|
dbfile = mkDefault "/var/lib/fail2ban/fail2ban.sqlite3";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# Add some reasonable default jails. The special "DEFAULT" jail
|
|
|
|
|
# sets default values for all other jails.
|
|
|
|
|
services.fail2ban.jails.DEFAULT = ''
|
|
|
|
|
# Bantime increment options
|
|
|
|
|
bantime.increment = ${boolToString cfg.bantime-increment.enable}
|
|
|
|
|
${optionalString (cfg.bantime-increment.rndtime != null) "bantime.rndtime = ${cfg.bantime-increment.rndtime}"}
|
|
|
|
|
${optionalString (cfg.bantime-increment.maxtime != null) "bantime.maxtime = ${cfg.bantime-increment.maxtime}"}
|
|
|
|
|
${optionalString (cfg.bantime-increment.factor != null) "bantime.factor = ${cfg.bantime-increment.factor}"}
|
|
|
|
|
${optionalString (cfg.bantime-increment.formula != null) "bantime.formula = ${cfg.bantime-increment.formula}"}
|
|
|
|
|
${optionalString (cfg.bantime-increment.multipliers != null) "bantime.multipliers = ${cfg.bantime-increment.multipliers}"}
|
|
|
|
|
${optionalString (cfg.bantime-increment.overalljails != null) "bantime.overalljails = ${boolToString cfg.bantime-increment.overalljails}"}
|
|
|
|
|
# Miscellaneous options
|
|
|
|
|
ignoreip = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}
|
|
|
|
|
${optionalString (cfg.bantime != null) ''
|
|
|
|
|
bantime = ${cfg.bantime}
|
|
|
|
|
''}
|
|
|
|
|
maxretry = ${toString cfg.maxretry}
|
|
|
|
|
backend = systemd
|
|
|
|
|
# Actions
|
|
|
|
|
banaction = ${cfg.banaction}
|
|
|
|
|
banaction_allports = ${cfg.banaction-allports}
|
|
|
|
|
${optionalString (cfg.extraSettings != {}) ''
|
|
|
|
|
# Extra settings
|
|
|
|
|
${generators.toKeyValue {} cfg.extraSettings}
|
|
|
|
|
''}
|
|
|
|
|
'';
|
|
|
|
|
# Block SSH if there are too many failing connection attempts.
|
|
|
|
|
services.fail2ban.jails = mkMerge [
|
|
|
|
|
{
|
|
|
|
|
DEFAULT.settings = (optionalAttrs cfg.bantime-increment.enable
|
|
|
|
|
({ "bantime.increment" = cfg.bantime-increment.enable; } // (mapAttrs'
|
|
|
|
|
(name: nameValuePair "bantime.${name}")
|
|
|
|
|
(filterAttrs (n: v: v != null && n != "enable") cfg.bantime-increment))
|
|
|
|
|
)
|
|
|
|
|
) // {
|
|
|
|
|
# Miscellaneous options
|
|
|
|
|
inherit (cfg) banaction maxretry;
|
|
|
|
|
ignoreip = ''127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}'';
|
|
|
|
|
backend = "systemd";
|
|
|
|
|
# Actions
|
|
|
|
|
banaction_allports = cfg.banaction-allports;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Block SSH if there are too many failing connection attempts.
|
|
|
|
|
(mkIf config.services.openssh.enable {
|
|
|
|
|
sshd.settings.port = mkDefault (concatMapStringsSep "," builtins.toString config.services.openssh.ports);
|
|
|
|
|
})
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
# Benefits from verbose sshd logging to observe failed login attempts,
|
|
|
|
|
# so we set that here unless the user overrode it.
|
|
|
|
|
services.openssh.settings.LogLevel = lib.mkDefault "VERBOSE";
|
|
|
|
|
services.fail2ban.jails.sshd = mkDefault ''
|
|
|
|
|
enabled = true
|
|
|
|
|
port = ${concatMapStringsSep "," (p: toString p) config.services.openssh.ports}
|
|
|
|
|
'';
|
|
|
|
|
services.openssh.settings.LogLevel = mkDefault "VERBOSE";
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|