@cryptotaxi247 / netdata-1 / commits / 07a151cab

fail2ban plugin: docstrings added + minor fixes

Ilya committed Mar 27, 2017 at 20:56 UTC 07a151cabd8ba96d3376df65e428e4aa22be43a0
1 file changed +114 -55
python.d/fail2ban.chart.py
+114 -55
@@ -2,32 +2,37 @@
2 # Description: fail2ban log netdata python.d module
3 # Author: l2isbad
4
5 -from base import LogService
5 from re import compile as r_compile
6 from os import access as is_accessible, R_OK
8 -from os.path import isdir
7 +from os.path import isdir, getsize
8 from glob import glob
9 import bisect
10 +from base import LogService
11
12 priority = 60000
13 retries = 60
14 REGEX_JAILS = r_compile(r'\[([A-Za-z-_]+)][^\[\]]*?(?<!# )enabled = (?:(true|false))')
15 -REGEX_DATA = r_compile(r'\[(?P<jail>[a-z]+)\] (?P<ban>[A-Z])[a-z]+ (?P<ipaddr>\d{1,3}(?:\.\d{1,3}){3})')
15 +REGEX_DATA = r_compile(r'\[(?P<jail>[a-z]+)\] (?P<action>[A-Z])[a-z]+ (?P<ipaddr>\d{1,3}(?:\.\d{1,3}){3})')
16 ORDER = ['jails_bans', 'jails_in_jail']
17
18
19 class Service(LogService):
20 + """
21 + fail2ban log class
22 + Reads logs line by line
23 + Jail auto detection included
24 + It produces following charts:
25 + * Bans per second for every jail
26 + * Banned IPs for every jail (since the last restart of netdata)
27 + """
28 def __init__(self, configuration=None, name=None):
29 LogService.__init__(self, configuration=configuration, name=name)
30 self.order = ORDER
31 + self.definitions = dict()
32 self.log_path = self.configuration.get('log_path', '/var/log/fail2ban.log')
33 self.conf_path = self.configuration.get('conf_path', '/etc/fail2ban/jail.local')
34 self.conf_dir = self.configuration.get('conf_dir', '/etc/fail2ban/jail.d/')
26 - self.bans = dict()
27 - try:
28 - self.exclude = self.configuration['exclude'].split()
29 - except (KeyError, AttributeError):
30 - self.exclude = list()
35 + self.exclude = self.configuration.get('exclude')
36
37 def _get_data(self):
38 """
@@ -38,7 +43,7 @@ class Service(LogService):
43 if raw is None:
44 return None
45 elif not raw:
41 - return self.data
46 + return self.to_netdata
47
48 # Fail2ban logs looks like
49 # 2016-12-25 12:36:04,711 fail2ban.actions[2455]: WARNING [ssh] Ban 178.156.32.231
@@ -46,31 +51,46 @@ class Service(LogService):
51 match = REGEX_DATA.search(row)
52 if match:
53 match_dict = match.groupdict()
49 - jail, ban, ipaddr = match_dict['jail'], match_dict['ban'], match_dict['ipaddr']
54 + jail, action, ipaddr = match_dict['jail'], match_dict['action'], match_dict['ipaddr']
55 if jail in self.jails_list:
51 - if ban == 'B':
52 - self.data[jail] += 1
53 - if address_not_in_jail(self.bans[jail], ipaddr, self.data[jail + '_in_jail']):
54 - self.data[jail + '_in_jail'] += 1
56 + if action == 'B':
57 + self.to_netdata[jail] += 1
58 + if address_not_in_jail(self.banned_ips[jail], ipaddr, self.to_netdata[jail + '_in_jail']):
59 + self.to_netdata[jail + '_in_jail'] += 1
60 else:
56 - if ipaddr in self.bans[jail]:
57 - self.bans[jail].remove(ipaddr)
58 - self.data[jail + '_in_jail'] -= 1
61 + if ipaddr in self.banned_ips[jail]:
62 + self.banned_ips[jail].remove(ipaddr)
63 + self.to_netdata[jail + '_in_jail'] -= 1
64
60 - return self.data
65 + return self.to_netdata
66
67 def check(self):
68 + """
69 + :return: bool
70 +
71 + Check if the "log_path" is not empty and readable
72 + """
73
64 - # Check "log_path" is accessible.
65 - # If NOT STOP plugin
66 - if not is_accessible(self.log_path, R_OK):
67 - self.error('Cannot access file %s' % self.log_path)
74 + if not (is_accessible(self.log_path, R_OK) and getsize(self.log_path) != 0):
75 + self.error('%s is not readable or empty' % self.log_path)
76 return False
77 + self.jails_list, self.to_netdata, self.banned_ips = self.jails_auto_detection_()
78 + self.definitions = create_definitions_(self.jails_list)
79 + self.info('Jails: %s' % self.jails_list)
80 + return True
81 +
82 + def jails_auto_detection_(self):
83 + """
84 + return: <tuple>
85
86 + * jails_list - list of enabled jails (['ssh', 'apache', ...])
87 + * to_netdata - dict ({'ssh': 0, 'ssh_in_jail': 0, ...})
88 + * banned_ips - here will be stored all the banned ips ({'ssh': ['1.2.3.4', '5.6.7.8', ...], ...})
89 + """
90 raw_jails_list = list()
91 jails_list = list()
92
73 - for raw_jail in parse_configuration_files(self.conf_path, self.conf_dir, self.error):
93 + for raw_jail in parse_configuration_files_(self.conf_path, self.conf_dir, self.error):
94 raw_jails_list.extend(raw_jail)
95
96 for jail, status in raw_jails_list:
@@ -80,30 +100,52 @@ class Service(LogService):
100 jails_list.remove(jail)
101
102 # If for some reason parse failed we still can START with default jails_list.
83 - self.jails_list = list(set(jails_list) - set(self.exclude)) or ['ssh']
84 -
85 - self.data = dict([(jail, 0) for jail in self.jails_list])
86 - self.data.update(dict([(jail + '_in_jail', 0) for jail in self.jails_list]))
87 - self.bans = dict([(jail, list()) for jail in self.jails_list])
88 -
89 - self._data_from_check = self.data
90 - self.create_dimensions()
91 - self.info('Plugin successfully started. Jails: %s' % self.jails_list)
92 - return True
93 -
94 - def create_dimensions(self):
95 - self.definitions = {
96 - 'jails_bans': {'options': [None, 'Jails Ban Statistics', 'bans/s', 'bans', 'jail.bans', 'line'],
97 - 'lines': []},
98 - 'jails_in_jail': {'options': [None, 'Banned IPs (since the last restart of netdata)', 'IPs',
99 - 'in jail', 'jail.in_jail', 'line'],
100 - 'lines': []},
101 - }
102 - for jail in self.jails_list:
103 - self.definitions['jails_bans']['lines'].append([jail, jail, 'incremental'])
104 - self.definitions['jails_in_jail']['lines'].append([jail + '_in_jail', jail, 'absolute'])
105 -
106 -def parse_configuration_files(jails_conf_path, jails_conf_dir, print_error):
103 + jails_list = list(set(jails_list) - set(self.exclude.split()
104 + if isinstance(self.exclude, str) else list())) or ['ssh']
105 +
106 + to_netdata = dict([(jail, 0) for jail in jails_list])
107 + to_netdata.update(dict([(jail + '_in_jail', 0) for jail in jails_list]))
108 + banned_ips = dict([(jail, list()) for jail in jails_list])
109 +
110 + return jails_list, to_netdata, banned_ips
111 +
112 +
113 +def create_definitions_(jails_list):
114 + """
115 + Chart definitions creating
116 + """
117 +
118 + definitions = {
119 + 'jails_bans': {'options': [None, 'Jails Ban Statistics', 'bans/s', 'bans', 'jail.bans', 'line'],
120 + 'lines': []},
121 + 'jails_in_jail': {'options': [None, 'Banned IPs (since the last restart of netdata)', 'IPs',
122 + 'in jail', 'jail.in_jail', 'line'],
123 + 'lines': []}}
124 + for jail in jails_list:
125 + definitions['jails_bans']['lines'].append([jail, jail, 'incremental'])
126 + definitions['jails_in_jail']['lines'].append([jail + '_in_jail', jail, 'absolute'])
127 +
128 + return definitions
129 +
130 +
131 +def parse_configuration_files_(jails_conf_path, jails_conf_dir, print_error):
132 + """
133 + :param jails_conf_path: <str>
134 + :param jails_conf_dir: <str>
135 + :param print_error: <function>
136 + :return: <tuple>
137 +
138 + Uses "find_jails_in_files" function to find all jails in the "jails_conf_dir" directory
139 + and in the "jails_conf_path"
140 +
141 + All files must endswith ".local" or ".conf"
142 + Return order is important.
143 + According man jail.conf it should be
144 + * jail.conf
145 + * jail.d/*.conf (in alphabetical order)
146 + * jail.local
147 + * jail.d/*.local (in alphabetical order)
148 + """
149 path_conf, path_local, dir_conf, dir_local = list(), list(), list(), list()
150
151 # Parse files in the directory
@@ -116,22 +158,31 @@ def parse_configuration_files(jails_conf_path, jails_conf_dir, print_error):
158 print_error('%s is empty or not readable' % jails_conf_dir)
159 else:
160 dir_conf, dir_local = (find_jails_in_files(dir_conf, print_error),
119 - find_jails_in_files(dir_local, print_error))
161 + find_jails_in_files(dir_local, print_error))
162
163 # Parse .conf and .local files
122 - if (isinstance(jails_conf_path, str) and jails_conf_path.endswith(('.local', '.conf'))):
164 + if isinstance(jails_conf_path, str) and jails_conf_path.endswith(('.local', '.conf')):
165 path_conf, path_local = (find_jails_in_files([jails_conf_path.split('.')[0] + '.conf'], print_error),
124 - find_jails_in_files([jails_conf_path.split('.')[0] + '.local'], print_error))
166 + find_jails_in_files([jails_conf_path.split('.')[0] + '.local'], print_error))
167
168 return path_conf, dir_conf, path_local, dir_local
169
170
171 def find_jails_in_files(list_of_files, print_error):
172 + """
173 + :param list_of_files: <list>
174 + :param print_error: <function>
175 + :return: <list>
176 +
177 + Open a file and parse it to find all (enabled and disabled) jails
178 + The output is a list of tuples:
179 + [('ssh', 'true'), ('apache', 'false'), ...]
180 + """
181 jails_list = list()
182 for conf in list_of_files:
183 if is_accessible(conf, R_OK):
133 - with open(conf, 'rt') as f:
134 - raw_data = f.read()
184 + with open(conf, 'rt') as conf:
185 + raw_data = conf.read()
186 data = ' '.join(raw_data.split())
187 jails_list.extend(REGEX_JAILS.findall(data))
188 else:
@@ -140,13 +191,21 @@ def find_jails_in_files(list_of_files, print_error):
191
192
193 def address_not_in_jail(pool, address, pool_size):
194 + """
195 + :param pool: <list>
196 + :param address: <str>
197 + :param pool_size: <int>
198 + :return: bool
199 +
200 + Checks if the address is in the pool.
201 + If not address will be added
202 + """
203 index = bisect.bisect_left(pool, address)
204 if index < pool_size:
205 if pool[index] == address:
206 return False
147 - else:
148 - bisect.insort_left(pool, address)
149 - return True
207 + bisect.insort_left(pool, address)
208 + return True
209 else:
210 bisect.insort_left(pool, address)
211 return True