1{
2 config,
3 lib,
4 pkgs,
5 self,
6 ...
7}: let
8 mkNotify = {
9 message,
10 channel,
11 priority ? 1,
12 }: ''
13 curl -u $(cat "${config.age.secrets.ntfyAuto.path}") \
14 -H "X-Priority: ${toString priority}" \
15 -d '${message}' \
16 https://${config.mySnippets.aylac-top.networkMap.ntfy.vHost}/${channel}
17 '';
18in {
19 options.myNixOS.services.fail2ban.enable = lib.mkEnableOption "fail2ban";
20
21 config = lib.mkIf config.myNixOS.services.fail2ban.enable {
22 age.secrets.cloudflareFail2ban.file = "${self.inputs.secrets}/cloudflare/fail2ban.age";
23
24 environment.etc = {
25 "fail2ban/action.d/mycloudflare.conf" = {
26 user = "root";
27 group = "root";
28 mode = "0640";
29 source = config.age.secrets.cloudflareFail2ban.path;
30 };
31
32 "fail2ban/filter.d/forgejo.conf".text = ''
33 [Definition]
34 failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
35 journalmatch = _SYSTEMD_UNIT=forgejo.service
36 '';
37
38 "fail2ban/action.d/ntfy.conf".text = ''
39 [Definition]
40 actionban = ${mkNotify {
41 message = "Arrested <ip> for trying to rob <name> at ${config.networking.hostName}";
42 channel = "fail2ban";
43 priority = 3;
44 }}
45 actionunban = ${mkNotify {
46 message = "Released <ip> from the jail at ${config.networking.hostName}";
47 channel = "fail2ban";
48 priority = 2;
49 }}
50 '';
51
52 "fail2ban/filter.d/vaultwarden.conf".text = ''
53 [INCLUDES]
54 before = common.conf
55
56 [Definition]
57 failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
58 ignoreregex =
59 journalmatch = _SYSTEMD_UNIT=vaultwarden.service
60 '';
61
62 "fail2ban/filter.d/vaultwarden-admin.conf".text = ''
63 [INCLUDES]
64 before = common.conf
65
66 [Definition]
67 failregex = ^.*Invalid admin token\. IP: <ADDR>.*$
68 ignoreregex =
69 journalmatch = _SYSTEMD_UNIT=vaultwarden.service
70 '';
71 };
72
73 services.fail2ban = {
74 enable = true;
75 ignoreIP = ["100.64.0.0/10"];
76 bantime = "24h";
77 bantime-increment.enable = true;
78 extraPackages = [pkgs.curl pkgs.jq pkgs.uutils-coreutils-noprefix];
79 jails = {
80 forgejo.settings = {
81 action = ''
82 mycloudflare
83 iptables-allports
84 ntfy'';
85 bantime = 900;
86 filter = "forgejo";
87 findtime = 3600;
88 maxretry = 4;
89 };
90
91 # HTTP basic-auth failures, 5 tries → 1-day ban
92 nginx-http-auth = {
93 settings = {
94 action = ''
95 mycloudflare
96 iptables-allports
97 ntfy'';
98 enabled = true;
99 maxretry = 5;
100 findtime = 300;
101 bantime = "24h";
102 };
103 };
104
105 # Generic scanner / bot patterns (wp-login.php, sqladmin, etc.)
106 nginx-botsearch = {
107 settings = {
108 action = ''
109 mycloudflare
110 iptables-allports
111 ntfy'';
112 enabled = true;
113 maxretry = 10;
114 findtime = 300;
115 bantime = "24h";
116 };
117 };
118
119 vaultwarden = ''
120 enabled = true
121 filter = vaultwarden
122 port = 80,443,${toString config.services.vaultwarden.config.ROCKET_PORT}
123 maxretry = 5
124 action = mycloudflare
125 iptables-allports
126 ntfy
127 '';
128
129 vaultwarden-admin = ''
130 enabled = true
131 port = 80,443,${toString config.services.vaultwarden.config.ROCKET_PORT}
132 filter = vaultwarden-admin
133 maxretry = 3
134 bantime = 14400
135 findtime = 14400
136 action = mycloudflare
137 iptables-allports
138 ntfy
139 '';
140 };
141 };
142 };
143}