1 # -*- coding: utf-8 -*-
2 # Description: fail2ban log netdata python.d module
5 from base import LogService
9 from itertools import filterfalse
11 from itertools import ifilterfalse as filterfalse
12 from os import access as is_accessible, R_OK
13 from os.path import isdir
18 REGEX = compile(r'\[([A-Za-z-]+)][^\[\]]*? enabled = true')
19 ORDER = ['jails_group']
22 class Service(LogService):
23 def __init__(self, configuration=None, name=None):
24 LogService.__init__(self, configuration=configuration, name=name)
26 self.log_path = self.configuration.get('log_path', '/var/log/fail2ban.log')
27 self.conf_path = self.configuration.get('conf_path', '/etc/fail2ban/jail.local')
28 self.conf_dir = self.configuration.get('conf_dir', '')
30 self.exclude = self.configuration['exclude'].split()
31 except (KeyError, AttributeError):
40 raw = self._get_raw_data()
45 except (ValueError, AttributeError):
48 # Fail2ban logs looks like
49 # 2016-12-25 12:36:04,711 fail2ban.actions[2455]: WARNING [ssh] Ban 178.156.32.231
53 [len(list(filterfalse(lambda line: (jail + '] Ban') not in line, raw))) for jail in self.jails_list]
57 self.data[jail] += data[jail]
63 # Check "log_path" is accessible.
65 if not is_accessible(self.log_path, R_OK):
66 self.error('Cannot access file %s' % self.log_path)
68 if not isdir(self.conf_dir):
71 # If "conf_dir" not specified (or not a dir) plugin will use "conf_path"
73 if is_accessible(self.conf_path, R_OK):
74 with open(self.conf_path, 'rt') as jails_conf:
75 jails_list = REGEX.findall(' '.join(jails_conf.read().split()))
76 self.jails_list = jails_list
78 self.jails_list = list()
79 self.error('Cannot access jail configuration file %s.' % self.conf_path)
80 # If "conf_dir" is specified and "conf_dir" is dir plugin will use "conf_dir"
82 dot_local = glob(self.conf_dir + '/*.local') # *.local jail configurations files
83 dot_conf = glob(self.conf_dir + '/*.conf') # *.conf jail configuration files
85 if not any([dot_local, dot_conf]):
86 self.error('%s is empty or not readable' % self.conf_dir)
87 # According "man jail.conf" files could be *.local AND *.conf
88 # *.conf files parsed first. Changes in *.local overrides configuration in *.conf
90 dot_local.extend([conf for conf in dot_conf if conf[:-5] not in [local[:-6] for local in dot_local]])
91 # Make sure all files are readable
92 dot_local = [conf for conf in dot_local if is_accessible(conf, R_OK)]
94 enabled_jails = list()
95 for jail_conf in dot_local:
96 with open(jail_conf, 'rt') as conf:
97 enabled_jails.extend(REGEX.findall(' '.join(conf.read().split())))
98 self.jails_list = list(set(enabled_jails))
100 self.jails_list = list()
101 self.error('Files in %s not readable' % self.conf_dir)
103 # If for some reason parse failed we still can START with default jails_list.
104 self.jails_list = list(set(self.jails_list) - set(self.exclude)) or ['ssh']
105 self.data = dict([(jail, 0) for jail in self.jails_list])
106 self.create_dimensions()
107 self.info('Plugin successfully started. Jails: %s' % self.jails_list)
110 def create_dimensions(self):
112 'jails_group': {'options': [None, "Jails ban statistics", "bans/s", 'Jails', 'jail.ban', 'line'],
114 for jail in self.jails_list:
115 self.definitions['jails_group']['lines'].append([jail, jail, 'incremental'])