nixos/fail2ban: use attrsets for settings instead of strings

This commit is contained in:
Tom Hubrecht 2022-11-19 13:13:56 +01:00
parent 67b08d46a1
commit 208ee8b2e2
4 changed files with 154 additions and 92 deletions

View File

@ -78,6 +78,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.

View File

@ -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";
};
}

View File

@ -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
View 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")
'';
})