Unverified Commit 7672c1e9 authored by Ryan Lahfa's avatar Ryan Lahfa Committed by GitHub
Browse files

Merge pull request #201907 from Tom-Hubrecht/fail2ban

parents 5a233eee 208ee8b2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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.
+133 −92
Original line number Diff line number Diff line
@@ -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 { } " = ";
  };

  mkJailConfig = name: attrs:
    optionalAttrs (name != "DEFAULT") { inherit (attrs) enabled; } //
    optionalAttrs (attrs.filter != null) { filter = if (builtins.isString filter) then filter else name; } //
    attrs.settings;

  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;

    before = paths-nixos.conf
  strJails = filterAttrs (_: builtins.isString) cfg.jails;
  attrsJails = filterAttrs (_: builtins.isAttrs) cfg.jails;

    ${concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def:
  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;
@@ -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 = '''
          {
            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 = '''
                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
             enabled = true
             filter = dovecot[mode=aggressive]
             maxretry = 3
           ''';
          }
                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}"}
    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
      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
          inherit (cfg) banaction maxretry;
          ignoreip = ''127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}'';
          backend = "systemd";
          # Actions
      banaction   = ${cfg.banaction}
      banaction_allports = ${cfg.banaction-allports}
      ${optionalString (cfg.extraSettings != {}) ''
        # Extra settings
        ${generators.toKeyValue {} cfg.extraSettings}
      ''}
    '';
          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";
  };
}
+1 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
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")
  '';
})