about summary refs log tree commit diff
path: root/nixos/modules/services/security
diff options
context:
space:
mode:
authorRyan Lahfa <masterancpp@gmail.com>2023-07-02 13:57:47 +0200
committerGitHub <noreply@github.com>2023-07-02 13:57:47 +0200
commit7672c1e9aefcc9175b20a41fb96f08e8eae1ae70 (patch)
treeedffae0229eb17d609b16050d1045ec5e8d68f0e /nixos/modules/services/security
parent5a233eee5492d193a274f768b3e96211a2a025f4 (diff)
parent208ee8b2e23d1a658a43e67aef38694de2219069 (diff)
downloadnixlib-7672c1e9aefcc9175b20a41fb96f08e8eae1ae70.tar
nixlib-7672c1e9aefcc9175b20a41fb96f08e8eae1ae70.tar.gz
nixlib-7672c1e9aefcc9175b20a41fb96f08e8eae1ae70.tar.bz2
nixlib-7672c1e9aefcc9175b20a41fb96f08e8eae1ae70.tar.lz
nixlib-7672c1e9aefcc9175b20a41fb96f08e8eae1ae70.tar.xz
nixlib-7672c1e9aefcc9175b20a41fb96f08e8eae1ae70.tar.zst
nixlib-7672c1e9aefcc9175b20a41fb96f08e8eae1ae70.zip
Merge pull request #201907 from Tom-Hubrecht/fail2ban
Diffstat (limited to 'nixos/modules/services/security')
-rw-r--r--nixos/modules/services/security/fail2ban.nix225
1 files changed, 133 insertions, 92 deletions
diff --git a/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix
index eebf2a2f7b55..9393fa751288 100644
--- a/nixos/modules/services/security/fail2ban.nix
+++ b/nixos/modules/services/security/fail2ban.nix
@@ -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";
   };
 }