Merge pull request #201907 from Tom-Hubrecht/fail2ban
This commit is contained in:
commit
7672c1e9ae
4 changed files with 154 additions and 92 deletions
|
@ -82,6 +82,8 @@
|
|||
|
||||
- DocBook option documentation is no longer supported, all module documentation now uses markdown.
|
||||
|
||||
- `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.
|
||||
|
||||
- `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.
|
||||
|
||||
- `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.
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
}
|
||||
|
|
|
@ -256,6 +256,7 @@ in {
|
|||
etebase-server = handleTest ./etebase-server.nix {};
|
||||
etesync-dav = handleTest ./etesync-dav.nix {};
|
||||
evcc = handleTest ./evcc.nix {};
|
||||
fail2ban = handleTest ./fail2ban.nix { };
|
||||
fakeroute = handleTest ./fakeroute.nix {};
|
||||
fancontrol = handleTest ./fancontrol.nix {};
|
||||
fcitx5 = handleTest ./fcitx5 {};
|
||||
|
|
18
nixos/tests/fail2ban.nix
Normal file
18
nixos/tests/fail2ban.nix
Normal file
|
@ -0,0 +1,18 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }: {
|
||||
name = "fail2ban";
|
||||
|
||||
nodes.machine = _: {
|
||||
services.fail2ban = {
|
||||
enable = true;
|
||||
bantime-increment.enable = true;
|
||||
};
|
||||
|
||||
services.openssh.enable = true;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
machine.wait_for_unit("fail2ban")
|
||||
'';
|
||||
})
|
Loading…
Reference in a new issue