old mode 100755
new mode 100644
| | |
| | | #!/usr/bin/env python3 |
| | | # -*- coding:Utf-8 -*- |
| | | # -*- coding:Utf-8 -*- |
| | | |
| | | |
| | | """ |
| | | Author : thuban <thuban@yeuxdelibad.net> |
| | | Author : thuban <thuban@yeuxdelibad.net> |
| | | Vincent <vincent.delft@gmail.com> |
| | | Yax https://blogduyax.madyanne.fr/ |
| | | Licence : MIT |
| | | Require : python >= 3.5 |
| | | |
| | | Description : Mimic fail2ban with pf for OpenBSD. |
| | | Inspired from http://www.vincentdelft.be/post/post_20161106 |
| | | |
| | | In pf.conf, add : |
| | | In pf.conf, add : |
| | | table <vilain_bruteforce> persist |
| | | block quick from <vilain_bruteforce> |
| | | block quick from <vilain_bruteforce> |
| | | |
| | | To see banned IP : |
| | | To see banned IP : |
| | | pfctl -t vilain_bruteforce -T show |
| | | """ |
| | | |
| | |
| | | import configparser |
| | | import re |
| | | import logging |
| | | import logging.handlers |
| | | import subprocess |
| | | import asyncio |
| | | import time |
| | | |
| | | CONFIGFILE = "/etc/vilain.conf" |
| | | VERSION = "0.5" |
| | | VERSION = "0.8.1" |
| | | vilain_table = "vilain_bruteforce" |
| | | LOGFILE = "/var/log/daemon" |
| | | |
| | |
| | | print("Only root can use this tool") |
| | | sys.exit(1) |
| | | |
| | | # Configure logging |
| | | # declare logger |
| | | logger = logging.getLogger(__name__) |
| | | logging.basicConfig(filename=LOGFILE, |
| | | format='%(asctime)s %(module)s:%(funcName)s:%(message)s', |
| | | datefmt='%H:%M:%S') |
| | | logger.setLevel(logging.INFO) |
| | | |
| | | def configure_logging(): |
| | | print('Log file : {}'.format(LOGFILE)) |
| | | log_handler = logging.handlers.WatchedFileHandler(LOGFILE) |
| | | formatter = logging.Formatter( |
| | | '%(asctime)s %(module)s:%(funcName)s:%(message)s', |
| | | '%Y-%m-%d %H:%M:%S') |
| | | log_handler.setFormatter(formatter) |
| | | logger.addHandler(log_handler) |
| | | logger.setLevel(logging.INFO) |
| | | |
| | | |
| | | # functions |
| | | def readconfig(): |
| | |
| | | |
| | | config = configparser.ConfigParser() |
| | | config.read(CONFIGFILE) |
| | | return(config) |
| | | return (config, config.defaults()) |
| | | |
| | | def load_config(): |
| | | c = readconfig() |
| | | d = c.defaults() |
| | | def load_config(c, d): |
| | | watch_while = int(d['watch_while']) |
| | | VILAIN_TABLE = d['vilain_table'] |
| | | default_maxtries = int(d['maxtries']) |
| | |
| | | ignoreips = [ i[1] for i in c.items('ignoreip') if i[0] not in c.defaults()] |
| | | return(watch_while, default_maxtries, vilain_table, ignoreips, sleeptime) |
| | | |
| | | def load_sections(): |
| | | c = readconfig() |
| | | def load_sections(c): |
| | | for s in c.sections(): |
| | | if c.has_option(s,'logfile'): |
| | | LOGFILE = c.get(s,'logfile') |
| | |
| | | yield d |
| | | |
| | | class Vilain(): |
| | | def __init__(self): |
| | | def __init__(self, config, config_dict): |
| | | logger.info('Start vilain version {}'.format(VERSION)) |
| | | self.loop = asyncio.get_event_loop() |
| | | self.watch_while, self.default_maxtries, self.vilain_table, self.ignore_ips, self.sleeptime = load_config() |
| | | self.watch_while, self.default_maxtries, self.vilain_table, self.ignore_ips, self.sleeptime = load_config(config, config_dict) |
| | | self.ip_seen_at = {} |
| | | self.load_bad_ips() |
| | | self.bad_ip_queue = asyncio.Queue(loop=self.loop) |
| | | |
| | | for entry in load_sections(): |
| | | for entry in load_sections(config): |
| | | logger.info("Start vilain for {}".format(entry)) |
| | | asyncio.ensure_future(self.check_logs(entry['logfile'], entry['maxtries'], entry['regex'], entry['name'])) |
| | | |
| | |
| | | for res in ret.split(): |
| | | ip = res.strip().decode('utf-8') |
| | | logger.info('Add existing banned IPs in your pf table: {}'.format(ip)) |
| | | #we assign the counter to 1, but for sure we don't know the real value |
| | | #we assign the counter to 1, but for sure we don't know the real value |
| | | self.ip_seen_at[ip]={'time':time.time(),'count':1} |
| | | |
| | | |
| | |
| | | logger.info("{} detected, reason {}, count: {}, maxtries: {}".format(ip, reason, n_ip, maxtries)) |
| | | if n_ip >= maxtries: |
| | | ret = subprocess.call(["pfctl", "-t", self.vilain_table, "-T", "add", ip]) |
| | | logger.info("Blacklisting {}, return code:{}".format(ip, ret)) |
| | | # now we can forget this ip |
| | | self.ip_seen_at.pop(ip) |
| | | logger.info("Blacklisting {}, reason {}, return code:{}".format(ip, reason, ret)) |
| | | #for debugging, this line allow us to see if the script run until here |
| | | logger.debug('ban_ips end:{}'.format(self.ip_seen_at)) |
| | | |
| | |
| | | logger.info('clean_ips started with sleeptime={}'.format(self.sleeptime)) |
| | | while True: |
| | | await asyncio.sleep(self.watch_while) |
| | | to_remove = [] |
| | | for recorded_ip, data in self.ip_seen_at.items(): |
| | | if time.time() - data['time'] >= self.watch_while: |
| | | ret = subprocess.call(["pfctl", "-t", self.vilain_table, "-T", "delete", recorded_ip]) |
| | | logger.info("{} not blocked any more, return code:{}".format(recorded_ip, ret)) |
| | | to_remove.append(recorded_ip) |
| | | for ip in to_remove: |
| | | self.ip_seen_at.pop(ip) |
| | | self.ip_seen_at.pop(recorded_ip) |
| | | #for debugging, this line allow us to see if the script run until here |
| | | ret = subprocess.call(["pfctl", "-t", self.vilain_table, "-T", "expire", self.watch_while]) |
| | | logger.debug('clean_ips end:{}'.format(self.ip_seen_at)) |
| | | |
| | | |
| | | |
| | | |
| | | def main(): |
| | | def main(config, config_dict): |
| | | os.chdir(os.path.dirname(os.path.abspath(__file__))) |
| | | v = Vilain() |
| | | v = Vilain(config, config_dict) |
| | | v.start() |
| | | return 0 |
| | | |
| | |
| | | if args.version: |
| | | print("Version: ", VERSION) |
| | | sys.exit(0) |
| | | main() |
| | | # read config |
| | | config, config_dict = readconfig() |
| | | logfile = config_dict.get('vilain_log', None) |
| | | if logfile: |
| | | LOGFILE = logfile |
| | | configure_logging() |
| | | main(config, config_dict) |
| | | |
| | | |
| | | # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 |
| | | |
| | | |
| | | # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 |
| | | |