]> arthur.barton.de Git - netdata.git/blob - python.d/fail2ban.chart.py
Merge pull request #1845 from l2isbad/self_signed_certificates_for_UrlService
[netdata.git] / python.d / fail2ban.chart.py
1 # -*- coding: utf-8 -*-
2 # Description: fail2ban log netdata python.d module
3 # Author: l2isbad
4
5 from base import LogService
6 from re import compile
7
8 try:
9     from itertools import filterfalse
10 except ImportError:
11     from itertools import ifilterfalse as filterfalse
12 from os import access as is_accessible, R_OK
13 from os.path import isdir
14 from glob import glob
15
16 priority = 60000
17 retries = 60
18 REGEX = compile(r'\[([A-Za-z-_]+)][^\[\]]*?(?<!# )enabled = true')
19 ORDER = ['jails_group']
20
21
22 class Service(LogService):
23     def __init__(self, configuration=None, name=None):
24         LogService.__init__(self, configuration=configuration, name=name)
25         self.order = ORDER
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', '')
29         try:
30             self.exclude = self.configuration['exclude'].split()
31         except (KeyError, AttributeError):
32             self.exclude = []
33
34     def _get_data(self):
35         """
36         Parse new log lines
37         :return: dict
38         """
39         try:
40             raw = self._get_raw_data()
41             if raw is None:
42                 return None
43             elif not raw:
44                 return self.data
45         except (ValueError, AttributeError):
46             return None
47
48         # Fail2ban logs looks like
49         # 2016-12-25 12:36:04,711 fail2ban.actions[2455]: WARNING [ssh] Ban 178.156.32.231
50         data = dict(
51             zip(
52                 self.jails_list,
53                 [len(list(filterfalse(lambda line: (jail + '] Ban') not in line, raw))) for jail in self.jails_list]
54             ))
55
56         for jail in data:
57             self.data[jail] += data[jail]
58
59         return self.data
60
61     def check(self):
62
63         # Check "log_path" is accessible.
64         # If NOT STOP plugin
65         if not is_accessible(self.log_path, R_OK):
66             self.error('Cannot access file %s' % self.log_path)
67             return False
68         jails_list = list()
69
70         if self.conf_dir:
71             dir_jails, error = parse_conf_dir(self.conf_dir)
72             jails_list.extend(dir_jails)
73             if not dir_jails:
74                 self.error(error)
75
76         if self.conf_path:
77             path_jails, error = parse_conf_path(self.conf_path)
78             jails_list.extend(path_jails)
79             if not path_jails:
80                 self.error(error)
81
82         # 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         self.data = dict([(jail, 0) for jail in self.jails_list])
85         self.create_dimensions()
86         self.info('Plugin successfully started. Jails: %s' % self.jails_list)
87         return True
88
89     def create_dimensions(self):
90         self.definitions = {
91             'jails_group': {'options': [None, "Jails ban statistics", "bans/s", 'jails', 'jail.ban', 'line'],
92                             'lines': []}}
93         for jail in self.jails_list:
94             self.definitions['jails_group']['lines'].append([jail, jail, 'incremental'])
95
96
97 def parse_conf_dir(conf_dir):
98     if not isdir(conf_dir):
99         return list(), '%s is not a directory' % conf_dir
100
101     jail_local = list(filter(lambda local: is_accessible(local, R_OK), glob(conf_dir + '/*.local')))
102     jail_conf = list(filter(lambda conf: is_accessible(conf, R_OK), glob(conf_dir + '/*.conf')))
103
104     if not (jail_local or jail_conf):
105         return list(), '%s is empty or not readable' % conf_dir
106
107     # According "man jail.conf" files could be *.local AND *.conf
108     # *.conf files parsed first. Changes in *.local overrides configuration in *.conf
109     if jail_conf:
110         jail_local.extend([conf for conf in jail_conf if conf[:-5] not in [local[:-6] for local in jail_local]])
111     jails_list = list()
112     for conf in jail_local:
113         with open(conf, 'rt') as f:
114             raw_data = f.read()
115
116         data = ' '.join(raw_data.split())
117         jails_list.extend(REGEX.findall(data))
118     jails_list = list(set(jails_list))
119
120     return jails_list, 'can\'t locate any jails in %s. Default jail is [\'ssh\']' % conf_dir
121
122
123 def parse_conf_path(conf_path):
124     if not is_accessible(conf_path, R_OK):
125         return list(), '%s is not readable' % conf_path
126
127     with open(conf_path, 'rt') as jails_conf:
128         raw_data = jails_conf.read()
129
130     data = raw_data.split()
131     jails_list = REGEX.findall(' '.join(data))
132     return jails_list, 'can\'t locate any jails in %s. Default jail is  [\'ssh\']' % conf_path