feat(python.d/fail2ban): add "Failed attempts" chart, cleanup (#11825)
Co-authored-by: Vladimir Kobal <vlad@prokk.net>
Ilya Mashchenko committed
Dec 23, 2021 at 17:05 UTC
438b475cd16e1dc609c448aa38d3253c4de53f5a
3 files changed
+110
-31
collectors/python.d.plugin/fail2ban/README.md
+50
-9
@@ -10,14 +10,55 @@ Monitors the fail2ban log file to show all bans for all active jails.
10
11
## Requirements
12
13
-- fail2ban.log file MUST BE readable by Netdata (A good idea is to add **create 0640 root netdata** to fail2ban conf at logrotate.d)
13
+The `fail2ban.log` file must be readable by the user `netdata`:
14
15
-It produces one chart with multiple lines (one line per jail)
15
+- change the file ownership and access permissions.
16
+- update `/etc/logrotate.d/fail2ban` to persists the changes after rotating the log file.
17
+
18
+<details>
19
+ <summary>Click to expand the instruction.</summary>
20
+
21
+To change the file ownership and access permissions, execute the following:
22
+
23
+```shell
24
+sudo chown root:netdata /var/log/fail2ban.log
25
+sudo chmod 640 /var/log/fail2ban.log
26
+```
27
+
28
+To persist the changes after rotating the log file, add `create 640 root netdata` to the `/etc/logrotate.d/fail2ban`:
29
+
30
+```shell
31
+/var/log/fail2ban.log {
32
+
33
+ weekly
34
+ rotate 4
35
+ compress
36
+
37
+ delaycompress
38
+ missingok
39
+ postrotate
40
+ fail2ban-client flushlogs 1>/dev/null
41
+ endscript
42
+
43
+ # If fail2ban runs as non-root it still needs to have write access
44
+ # to logfiles.
45
+ # create 640 fail2ban adm
46
+ create 640 root netdata
47
+}
48
+```
49
+
50
+</details>
51
+
52
+## Charts
53
+
54
+- Failed attempts in attempts/s
55
+- Bans in bans/s
56
+- Banned IP addresses (since the last restart of netdata) in ips
57
58
## Configuration
59
19
-Edit the `python.d/fail2ban.conf` configuration file using `edit-config` from the Netdata [config
20
-directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
60
+Edit the `python.d/fail2ban.conf` configuration file using `edit-config` from the
61
+Netdata [config directory](/docs/configure/nodes.md), which is typically at `/etc/netdata`.
62
63
```bash
64
cd /etc/netdata # Replace this path with your Netdata config directory, if different
@@ -28,13 +69,13 @@ Sample:
69
70
```yaml
71
local:
31
- log_path: '/var/log/fail2ban.log'
32
- conf_path: '/etc/fail2ban/jail.local'
33
- exclude: 'dropbear apache'
72
+ log_path: '/var/log/fail2ban.log'
73
+ conf_path: '/etc/fail2ban/jail.local'
74
+ exclude: 'dropbear apache'
75
```
76
36
-If no configuration is given, module will attempt to read log file at `/var/log/fail2ban.log` and conf file at `/etc/fail2ban/jail.local`.
37
-If conf file is not found default jail is `ssh`.
77
+If no configuration is given, module will attempt to read log file at `/var/log/fail2ban.log` and conf file
78
+at `/etc/fail2ban/jail.local`. If conf file is not found default jail is `ssh`.
79
80
---
81
collectors/python.d.plugin/fail2ban/fail2ban.chart.py
+35
-22
@@ -11,8 +11,9 @@ from glob import glob
11
from bases.FrameworkServices.LogService import LogService
12
13
ORDER = [
14
+ 'jails_failed_attempts',
15
'jails_bans',
15
- 'jails_in_jail',
16
+ 'jails_banned_ips',
17
]
18
19
@@ -23,40 +24,49 @@ def charts(jails):
24
25
ch = {
26
ORDER[0]: {
26
- 'options': [None, 'Jails Ban Rate', 'bans/s', 'bans', 'jail.bans', 'line'],
27
+ 'options': [None, 'Failed attempts', 'attempts/s', 'failed attempts', 'fail2ban.failed_attempts', 'line'],
28
'lines': []
29
},
30
ORDER[1]: {
30
- 'options': [None, 'Banned IPs (since the last restart of netdata)', 'IPs', 'in jail',
31
- 'jail.in_jail', 'line'],
31
+ 'options': [None, 'Bans', 'bans/s', 'bans', 'fail2ban.bans', 'line'],
32
+ 'lines': []
33
+ },
34
+ ORDER[2]: {
35
+ 'options': [None, 'Banned IP addresses (since the last restart of netdata)', 'ips', 'banned ips',
36
+ 'fail2ban.banned_ips', 'line'],
37
'lines': []
38
},
39
}
40
for jail in jails:
36
- dim = [
37
- jail,
38
- jail,
39
- 'incremental',
40
- ]
41
+ dim = ['{0}_failed_attempts'.format(jail), jail, 'incremental']
42
ch[ORDER[0]]['lines'].append(dim)
43
43
- dim = [
44
- '{0}_in_jail'.format(jail),
45
- jail,
46
- 'absolute',
47
- ]
44
+ dim = [jail, jail, 'incremental']
45
ch[ORDER[1]]['lines'].append(dim)
46
47
+ dim = ['{0}_in_jail'.format(jail), jail, 'absolute']
48
+ ch[ORDER[2]]['lines'].append(dim)
49
+
50
return ch
51
52
53
RE_JAILS = re.compile(r'\[([a-zA-Z0-9_-]+)\][^\[\]]+?enabled\s+= +(true|yes|false|no)')
54
55
+ACTION_BAN = 'Ban'
56
+ACTION_UNBAN = 'Unban'
57
+ACTION_RESTORE_BAN = 'Restore Ban'
58
+ACTION_FOUND = 'Found'
59
+
60
# Example:
56
-# 2018-09-12 11:45:53,715 fail2ban.actions[25029]: WARNING [ssh] Unban 195.201.88.33
57
-# 2018-09-12 11:45:58,727 fail2ban.actions[25029]: WARNING [ssh] Ban 217.59.246.27
58
-# 2018-09-12 11:45:58,727 fail2ban.actions[25029]: WARNING [ssh] Restore Ban 217.59.246.27
59
-RE_DATA = re.compile(r'\[(?P<jail>[A-Za-z-_0-9]+)\] (?P<action>Unban|Ban|Restore Ban) (?P<ip>[a-f0-9.:]+)')
61
+# 2018-09-12 11:45:58,727 fail2ban.actions[25029]: WARNING [ssh] Found 203.0.113.1
62
+# 2018-09-12 11:45:58,727 fail2ban.actions[25029]: WARNING [ssh] Ban 203.0.113.1
63
+# 2018-09-12 11:45:58,727 fail2ban.actions[25029]: WARNING [ssh] Restore Ban 203.0.113.1
64
+# 2018-09-12 11:45:53,715 fail2ban.actions[25029]: WARNING [ssh] Unban 203.0.113.1
65
+RE_DATA = re.compile(
66
+ r'\[(?P<jail>[A-Za-z-_0-9]+)\] (?P<action>{0}|{1}|{2}|{3}) (?P<ip>[a-f0-9.:]+)'.format(
67
+ ACTION_BAN, ACTION_UNBAN, ACTION_RESTORE_BAN, ACTION_FOUND
68
+ )
69
+)
70
71
DEFAULT_JAILS = [
72
'ssh',
@@ -94,6 +104,7 @@ class Service(LogService):
104
105
self.monitoring_jails = self.jails_auto_detection()
106
for jail in self.monitoring_jails:
107
+ self.data['{0}_failed_attempts'.format(jail)] = 0
108
self.data[jail] = 0
109
self.data['{0}_in_jail'.format(jail)] = 0
110
@@ -124,12 +135,14 @@ class Service(LogService):
135
136
jail, action, ip = match['jail'], match['action'], match['ip']
137
127
- if action == 'Ban' or action == 'Restore Ban':
138
+ if action == ACTION_FOUND:
139
+ self.data['{0}_failed_attempts'.format(jail)] += 1
140
+ elif action in (ACTION_BAN, ACTION_RESTORE_BAN):
141
self.data[jail] += 1
142
if ip not in self.banned_ips[jail]:
143
self.banned_ips[jail].add(ip)
144
self.data['{0}_in_jail'.format(jail)] += 1
132
- else:
145
+ elif action == ACTION_UNBAN:
146
if ip in self.banned_ips[jail]:
147
self.banned_ips[jail].remove(ip)
148
self.data['{0}_in_jail'.format(jail)] -= 1
@@ -196,9 +209,9 @@ class Service(LogService):
209
if name in exclude:
210
continue
211
199
- if status in ('true','yes') and name not in active_jails:
212
+ if status in ('true', 'yes') and name not in active_jails:
213
active_jails.append(name)
201
- elif status in ('false','no') and name in active_jails:
214
+ elif status in ('false', 'no') and name in active_jails:
215
active_jails.remove(name)
216
217
return active_jails or DEFAULT_JAILS
web/gui/dashboard_info.js
+25
@@ -703,6 +703,12 @@ netdataDashboard.menu = {
703
icon: '<i class="fas fa-brain"></i>',
704
info: 'Charts relating to anomaly detection, increased <code>anomalous</code> dimensions or a higher than usual <code>anomaly_rate</code> could be signs of some abnormal behaviour. Read our <a href="https://learn.netdata.cloud/guides/monitor/anomaly-detection" target="_blank">anomaly detection guide</a> for more details.'
705
},
706
+
707
+ 'fail2ban': {
708
+ title: 'Fail2ban',
709
+ icon: '<i class="fas fa-shield-alt"></i>',
710
+ info: 'Netdata keeps track of the current jail status by reading the Fail2ban log file.'
711
+ },
712
};
713
714
@@ -6373,4 +6379,23 @@ netdataDashboard.context = {
6379
info: 'Diagnostic metrics relating to training time of anomaly detection. '
6380
},
6381
6382
+ // ------------------------------------------------------------------------
6383
+ // Supervisor
6384
+
6385
+ 'fail2ban.failed_attempts': {
6386
+ info: '<p>The number of failed attempts.</p>'+
6387
+ '<p>This chart reflects the number of \'Found\' lines. '+
6388
+ 'Found means a line in the service’s log file matches the failregex in its filter.</p>'
6389
+ },
6390
+
6391
+ 'fail2ban.bans': {
6392
+ info: '<p>The number of bans.</p>'+
6393
+ '<p>This chart reflects the number of \'Ban\' and \'Restore Ban\' lines. '+
6394
+ 'Ban action happens when the number of failed attempts (maxretry) occurred in the last configured interval (findtime).</p>'
6395
+ },
6396
+
6397
+ 'fail2ban.banned_ips': {
6398
+ info: '<p>The number of banned IP addresses.</p>'
6399
+ },
6400
+
6401
};