1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.sshguard;
7in {
8
9 ###### interface
10
11 options = {
12
13 services.sshguard = {
14 enable = mkOption {
15 default = false;
16 type = types.bool;
17 description = "Whether to enable the sshguard service.";
18 };
19
20 attack_threshold = mkOption {
21 default = 30;
22 type = types.int;
23 description = ''
24 Block attackers when their cumulative attack score exceeds threshold. Most attacks have a score of 10.
25 '';
26 };
27
28 blacklist_threshold = mkOption {
29 default = null;
30 example = 120;
31 type = types.nullOr types.int;
32 description = ''
33 Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
34 '';
35 };
36
37 blacklist_file = mkOption {
38 default = "/var/lib/sshguard/blacklist.db";
39 type = types.path;
40 description = ''
41 Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
42 '';
43 };
44
45 blocktime = mkOption {
46 default = 120;
47 type = types.int;
48 description = ''
49 Block attackers for initially blocktime seconds after exceeding threshold. Subsequent blocks increase by a factor of 1.5.
50
51 sshguard unblocks attacks at random intervals, so actual block times will be longer.
52 '';
53 };
54
55 detection_time = mkOption {
56 default = 1800;
57 type = types.int;
58 description = ''
59 Remember potential attackers for up to detection_time seconds before resetting their score.
60 '';
61 };
62
63 whitelist = mkOption {
64 default = [ ];
65 example = [ "198.51.100.56" "198.51.100.2" ];
66 type = types.listOf types.str;
67 description = ''
68 Whitelist a list of addresses, hostnames, or address blocks.
69 '';
70 };
71
72 services = mkOption {
73 default = [ "sshd" ];
74 example = [ "sshd" "exim" ];
75 type = types.listOf types.str;
76 description = ''
77 Systemd services sshguard should receive logs of.
78 '';
79 };
80
81 };
82
83 };
84
85
86 ###### implementation
87
88 config = mkIf cfg.enable {
89
90 environment.systemPackages = [ pkgs.sshguard pkgs.iptables pkgs.ipset ];
91
92 environment.etc."sshguard.conf".text = let
93 list_services = ( name: "-t ${name} ");
94 in ''
95 BACKEND="${pkgs.sshguard}/libexec/sshg-fw-ipset"
96 LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl -afb -p info -n1 ${toString (map list_services cfg.services)} -o cat"
97 '';
98
99 systemd.services.sshguard =
100 { description = "SSHGuard brute-force attacks protection system";
101
102 wantedBy = [ "multi-user.target" ];
103 after = [ "network.target" ];
104 partOf = optional config.networking.firewall.enable "firewall.service";
105
106 path = [ pkgs.iptables pkgs.ipset pkgs.iproute pkgs.systemd ];
107
108 postStart = ''
109 mkdir -p /var/lib/sshguard
110 ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:ip family inet
111 ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:ip family inet6
112 ${pkgs.iptables}/bin/iptables -I INPUT -m set --match-set sshguard4 src -j DROP
113 ${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP
114 '';
115
116 preStop = ''
117 ${pkgs.iptables}/bin/iptables -D INPUT -m set --match-set sshguard4 src -j DROP
118 ${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP
119 '';
120
121 unitConfig.Documentation = "man:sshguard(8)";
122
123 serviceConfig = {
124 Type = "simple";
125 ExecStart = let
126 list_whitelist = ( name: "-w ${name} ");
127 in ''
128 ${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)}
129 '';
130 PIDFile = "/run/sshguard/sshguard.pid";
131 Restart = "always";
132
133 ReadOnlyDirectories = "/";
134 ReadWriteDirectories = "/run/sshguard /var/lib/sshguard";
135 RuntimeDirectory = "sshguard";
136 StateDirectory = "sshguard";
137 CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
138 };
139 };
140 };
141}