diff --git a/lib/maintainers.nix b/lib/maintainers.nix index 57fa0bdd565c..20f973a2c016 100644 --- a/lib/maintainers.nix +++ b/lib/maintainers.nix @@ -468,6 +468,7 @@ s1lvester = "Markus Silvester "; samuelrivas = "Samuel Rivas "; sander = "Sander van der Burg "; + sargon = "Daniel Ehlers "; schmitthenner = "Fabian Schmitthenner "; schneefux = "schneefux "; schristo = "Scott Christopher "; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 5c6668fbdc6a..06571f688eee 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -531,8 +531,9 @@ ./services/security/munge.nix ./services/security/oauth2_proxy.nix ./services/security/physlock.nix - ./services/security/torify.nix + ./services/security/sshguard.nix ./services/security/tor.nix + ./services/security/torify.nix ./services/security/torsocks.nix ./services/system/cgmanager.nix ./services/system/cloud-init.nix diff --git a/nixos/modules/services/security/sshguard.nix b/nixos/modules/services/security/sshguard.nix new file mode 100644 index 000000000000..5a183443f71d --- /dev/null +++ b/nixos/modules/services/security/sshguard.nix @@ -0,0 +1,140 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.sshguard; +in { + + ###### interface + + options = { + + services.sshguard = { + enable = mkOption { + default = false; + type = types.bool; + description = "Whether to enable the sshguard service."; + }; + + attack_threshold = mkOption { + default = 30; + type = types.int; + description = '' + Block attackers when their cumulative attack score exceeds threshold. Most attacks have a score of 10. + ''; + }; + + blacklist_threshold = mkOption { + default = null; + example = 120; + type = types.nullOr types.int; + description = '' + Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file. + ''; + }; + + blacklist_file = mkOption { + default = "/var/lib/sshguard/blacklist.db"; + type = types.path; + description = '' + Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file. + ''; + }; + + blocktime = mkOption { + default = 120; + type = types.int; + description = '' + Block attackers for initially blocktime seconds after exceeding threshold. Subsequent blocks increase by a factor of 1.5. + + sshguard unblocks attacks at random intervals, so actual block times will be longer. + ''; + }; + + detection_time = mkOption { + default = 1800; + type = types.int; + description = '' + Remember potential attackers for up to detection_time seconds before resetting their score. + ''; + }; + + whitelist = mkOption { + default = [ ]; + example = [ "198.51.100.56" "198.51.100.2" ]; + type = types.listOf types.str; + description = '' + Whitelist a list of addresses, hostnames, or address blocks. + ''; + }; + + services = mkOption { + default = [ "sshd" ]; + example = [ "sshd" "exim" ]; + type = types.listOf types.str; + description = '' + Systemd services sshguard should receive logs of. + ''; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.sshguard pkgs.iptables pkgs.ipset ]; + + environment.etc."sshguard.conf".text = let + list_services = ( name: "-t ${name} "); + in '' + BACKEND="${pkgs.sshguard}/libexec/sshg-fw-ipset" + LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl -afb -p info -n1 ${toString (map list_services cfg.services)} -o cat" + ''; + + systemd.services.sshguard = + { description = "SSHGuard brute-force attacks protection system"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + partOf = optional config.networking.firewall.enable "firewall.service"; + + path = [ pkgs.iptables pkgs.ipset pkgs.iproute pkgs.systemd ]; + + postStart = '' + mkdir -p /var/lib/sshguard + ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:ip family inet + ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:ip family inet6 + ${pkgs.iptables}/bin/iptables -I INPUT -m set --match-set sshguard4 src -j DROP + ${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP + ''; + + preStop = '' + ${pkgs.iptables}/bin/iptables -D INPUT -m set --match-set sshguard4 src -j DROP + ${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP + ''; + + unitConfig.Documentation = "man:sshguard(8)"; + + serviceConfig = { + Type = "simple"; + ExecStart = let + list_whitelist = ( name: "-w ${name} "); + in '' + ${pkgs.sshguard}/bin/sshguard -a ${toString cfg.attack_threshold} ${optionalString (cfg.blacklist_threshold != null) "-b ${toString cfg.blacklist_threshold}:${cfg.blacklist_file} "}-i /run/sshguard/sshguard.pid -p ${toString cfg.blocktime} -s ${toString cfg.detection_time} ${toString (map list_whitelist cfg.whitelist)} + ''; + PIDFile = "/run/sshguard/sshguard.pid"; + Restart = "always"; + + ReadOnlyDirectories = "/"; + ReadWriteDirectories = "/run/sshguard /var/lib/sshguard"; + RuntimeDirectory = "sshguard"; + CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW"; + }; + }; + }; +} diff --git a/pkgs/tools/security/sshguard/0001-Remove-the-unnecessary-from-ipset-cmds.patch b/pkgs/tools/security/sshguard/0001-Remove-the-unnecessary-from-ipset-cmds.patch new file mode 100644 index 000000000000..f1233a04b7a6 --- /dev/null +++ b/pkgs/tools/security/sshguard/0001-Remove-the-unnecessary-from-ipset-cmds.patch @@ -0,0 +1,27 @@ +From 11f0d238d3149c31c4440b8f6a58fe6a00b82d3a Mon Sep 17 00:00:00 2001 +From: Daniel Aleksandersen +Date: Mon, 13 Mar 2017 16:29:33 +0100 +Subject: [PATCH 1/3] Remove the unnecessary = from ipset cmds + +--- + src/fw/sshg-fw-ipset.sh | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/fw/sshg-fw-ipset.sh b/src/fw/sshg-fw-ipset.sh +index 510bc2c..dc7f86b 100644 +--- a/src/fw/sshg-fw-ipset.sh ++++ b/src/fw/sshg-fw-ipset.sh +@@ -3,8 +3,8 @@ + # This file is part of SSHGuard. + + fw_init() { +- ipset -quiet create -exist sshguard4 hash:ip family=inet +- ipset -quiet create -exist sshguard6 hash:ip family=inet6 ++ ipset -quiet create -exist sshguard4 hash:ip family inet ++ ipset -quiet create -exist sshguard6 hash:ip family inet6 + } + + fw_block() { +-- +2.10.0 + diff --git a/pkgs/tools/security/sshguard/default.nix b/pkgs/tools/security/sshguard/default.nix new file mode 100644 index 000000000000..bb165e53c73d --- /dev/null +++ b/pkgs/tools/security/sshguard/default.nix @@ -0,0 +1,32 @@ +{ stdenv, fetchurl, autoreconfHook, yacc, flex}: + + +stdenv.mkDerivation rec { + version = "2.0.0"; + name = "sshguard-${version}"; + + src = fetchurl { + url = "mirror://sourceforge/sshguard/sshguard-2.0.0.tar.gz"; + sha256 = "e87c6c4a6dddf06f440ea76464eb6197869c0293f0a60ffa51f8a6a0d7b0cb06"; + }; + + doCheck = true; + + nativeBuildInputs = [ autoreconfHook yacc flex ]; + + configureFlags = [ "--sysconfdir=/etc" ]; + + patches = [ ./0001-Remove-the-unnecessary-from-ipset-cmds.patch ]; + + meta = with stdenv.lib; { + description = "SSHGuard protects hosts from brute-force attacks"; + longDescription = '' + SSHGuard can read log messages from various input sources. Log messages are parsed, line-by-line, for recognized patterns. + If an attack, such as several login failures within a few seconds, is detected, the offending IP is blocked. + ''; + homepage = https://sshguard.net; + license = licenses.bsd3; + maintainers = with maintainers; [ sargon ]; + platforms = with platforms; linux ++ darwin ++ freebsd ++ netbsd ++ openbsd; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 867c35215af9..3941eb34ca48 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -4003,6 +4003,8 @@ with pkgs; snort = callPackage ../applications/networking/ids/snort { }; + sshguard = callPackage ../tools/security/sshguard {}; + softhsm = callPackage ../tools/security/softhsm { }; solr = callPackage ../servers/search/solr { };