X-Git-Url: https://arthur.barton.de/gitweb/?a=blobdiff_plain;f=python.d%2Ffail2ban.chart.py;h=85d0ade618006adb707a9d6b5ea20f1156b2be0a;hb=315c388516a4750eab4b64d38f8bddd58b4e5aa4;hp=85ac2d43fae274f1959624d3dfa14ad91f1e2cb2;hpb=5caa7fcd7b22d19de1eb03042a2b66bd4a1ef9dc;p=netdata.git diff --git a/python.d/fail2ban.chart.py b/python.d/fail2ban.chart.py index 85ac2d43..85d0ade6 100644 --- a/python.d/fail2ban.chart.py +++ b/python.d/fail2ban.chart.py @@ -3,20 +3,17 @@ # Author: l2isbad from base import LogService -from re import compile - -try: - from itertools import filterfalse -except ImportError: - from itertools import ifilterfalse as filterfalse +from re import compile as r_compile from os import access as is_accessible, R_OK from os.path import isdir from glob import glob +import bisect priority = 60000 retries = 60 -REGEX = compile(r'\[([A-Za-z-]+)][^\[\]]*? enabled = true') -ORDER = ['jails_group'] +REGEX_JAILS = r_compile(r'\[([A-Za-z-_]+)][^\[\]]*?(?[a-z]+)\] (?P[A-Z])[a-z]+ (?P\d{1,3}(?:\.\d{1,3}){3})') +ORDER = ['jails_bans', 'jails_in_jail'] class Service(LogService): @@ -26,35 +23,39 @@ class Service(LogService): self.log_path = self.configuration.get('log_path', '/var/log/fail2ban.log') self.conf_path = self.configuration.get('conf_path', '/etc/fail2ban/jail.local') self.conf_dir = self.configuration.get('conf_dir', '') + self.bans = dict() try: self.exclude = self.configuration['exclude'].split() except (KeyError, AttributeError): - self.exclude = [] + self.exclude = list() def _get_data(self): """ Parse new log lines :return: dict """ - try: - raw = self._get_raw_data() - if raw is None: - return None - elif not raw: - return self.data - except (ValueError, AttributeError): + raw = self._get_raw_data() + if raw is None: return None + elif not raw: + return self.data # Fail2ban logs looks like # 2016-12-25 12:36:04,711 fail2ban.actions[2455]: WARNING [ssh] Ban 178.156.32.231 - data = dict( - zip( - self.jails_list, - [len(list(filterfalse(lambda line: (jail + '] Ban') not in line, raw))) for jail in self.jails_list] - )) - - for jail in data: - self.data[jail] += data[jail] + for row in raw: + match = REGEX_DATA.search(row) + if match: + match_dict = match.groupdict() + jail, ban, ipaddr = match_dict['jail'], match_dict['ban'], match_dict['ipaddr'] + if jail in self.jails_list: + if ban == 'B': + self.data[jail] += 1 + if address_not_in_jail(self.bans[jail], ipaddr, self.data[jail + '_in_jail']): + self.data[jail + '_in_jail'] += 1 + else: + if ipaddr in self.bans[jail]: + self.bans[jail].remove(ipaddr) + self.data[jail + '_in_jail'] -= 1 return self.data @@ -65,28 +66,41 @@ class Service(LogService): if not is_accessible(self.log_path, R_OK): self.error('Cannot access file %s' % self.log_path) return False + jails_list = list() if self.conf_dir: - jails_list, error = parse_conf_dir(self.conf_dir) - else: - jails_list, error = parse_conf_path(self.conf_path) + dir_jails, error = parse_conf_dir(self.conf_dir) + jails_list.extend(dir_jails) + if not dir_jails: + self.error(error) - if not jails_list: - self.error(error) + if self.conf_path: + path_jails, error = parse_conf_path(self.conf_path) + jails_list.extend(path_jails) + if not path_jails: + self.error(error) # If for some reason parse failed we still can START with default jails_list. self.jails_list = list(set(jails_list) - set(self.exclude)) or ['ssh'] + self.data = dict([(jail, 0) for jail in self.jails_list]) + self.data.update(dict([(jail + '_in_jail', 0) for jail in self.jails_list])) + self.bans = dict([(jail, list()) for jail in self.jails_list]) + self.create_dimensions() self.info('Plugin successfully started. Jails: %s' % self.jails_list) return True def create_dimensions(self): self.definitions = { - 'jails_group': {'options': [None, "Jails ban statistics", "bans/s", 'jails', 'jail.ban', 'line'], - 'lines': []}} + 'jails_bans': {'options': [None, 'Jails Ban Statistics', "bans/s", 'bans', 'jail.bans', 'line'], + 'lines': []}, + 'jails_in_jail': {'options': [None, 'Banned IPs (since the last restart of netdata)', 'IPs', + 'in jail', 'jail.in_jail', 'line'], 'lines': []}, + } for jail in self.jails_list: - self.definitions['jails_group']['lines'].append([jail, jail, 'incremental']) + self.definitions['jails_bans']['lines'].append([jail, jail, 'incremental']) + self.definitions['jails_in_jail']['lines'].append([jail + '_in_jail', jail, 'absolute']) def parse_conf_dir(conf_dir): @@ -109,7 +123,7 @@ def parse_conf_dir(conf_dir): raw_data = f.read() data = ' '.join(raw_data.split()) - jails_list.extend(REGEX.findall(data)) + jails_list.extend(REGEX_JAILS.findall(data)) jails_list = list(set(jails_list)) return jails_list, 'can\'t locate any jails in %s. Default jail is [\'ssh\']' % conf_dir @@ -123,5 +137,18 @@ def parse_conf_path(conf_path): raw_data = jails_conf.read() data = raw_data.split() - jails_list = REGEX.findall(' '.join(data)) + jails_list = REGEX_JAILS.findall(' '.join(data)) return jails_list, 'can\'t locate any jails in %s. Default jail is [\'ssh\']' % conf_path + + +def address_not_in_jail(pool, address, pool_size): + index = bisect.bisect_left(pool, address) + if index < pool_size: + if pool[index] == address: + return False + else: + bisect.insort_left(pool, address) + return True + else: + bisect.insort_left(pool, address) + return True