From 25237df6358b0d0d9899601f5ea17cdc6debf0cd Mon Sep 17 00:00:00 2001 From: Thuban <thuban@yeuxdelibad.net> Date: Tue, 11 Apr 2017 08:24:31 +0000 Subject: [PATCH] debug --- vilain.py | 126 ++++++++++++++++++++++++----------------- 1 files changed, 73 insertions(+), 53 deletions(-) diff --git a/vilain.py b/vilain.py index 24e2ff4..dcfb90e 100755 --- a/vilain.py +++ b/vilain.py @@ -9,13 +9,11 @@ Description : Mimic fail2ban with pf for OpenBSD. Inspired from http://www.vincentdelft.be/post/post_20161106 + with improvements of vincendelft In pf.conf, add : table <vilain_bruteforce> persist block quick from <vilain_bruteforce> - - You might want to add a cron task to remove old banned IP. As example, to ban for one day max : - pfctl -t vilain_bruteforce -T expire 86400 To see banned IP : pfctl -t vilain_bruteforce -T show @@ -29,13 +27,11 @@ import subprocess import asyncio import time -from multiprocessing import Process configfile = "/etc/vilain.conf" -version = "0.3" +version = "0.4" vilain_table = "vilain_bruteforce" logfile = "/var/log/daemon" -sleeptime = 0.5 if os.geteuid() != 0: print("Only root can use this tool") @@ -46,7 +42,7 @@ logging.basicConfig(filename=logfile, format='%(asctime)s %(module)s:%(funcName)s:%(message)s', datefmt='%H:%M:%S') -logger.setLevel(logging.DEBUG) +logger.setLevel(logging.INFO) ch = logging.StreamHandler(sys.stdout) logger.addHandler(ch) @@ -64,14 +60,14 @@ c = readconfig() d = c.defaults() watch_while = int(d['watch_while']) - maxtries = int(d['maxtries']) + default_maxtries = int(d['maxtries']) vilain_table = d['vilain_table'] + sleeptime = float(d['sleeptime']) + ignore_ips = [] if c.has_section('ignoreip'): - ignoreip = [ i[1] for i in c.items('ignoreip') if i[0] not in c.defaults()] - else: - ignoreip = [] - return(watch_while, maxtries, vilain_table, ignoreip) + 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() @@ -79,21 +75,41 @@ if c.has_option(s,'logfile'): logfile = c.get(s,'logfile') regex = c.get(s,'regex') - d = {'name' : s, 'logfile':logfile, 'regex':regex} + #we take the default value of maxtries + maxtries = c.defaults()['maxtries'] + if c.has_option(s,'maxtries'): + #if we have a maxtries defined in the section, we overwrite the default + maxtries = int(c.get(s,'maxtries')) + d = {'name' : s, 'logfile':logfile, 'regex':regex, 'maxtries': maxtries} yield d + class Vilain(): def __init__(self): self.loop = asyncio.get_event_loop() - self.watch_while, self.maxtries, self.vilain_table, self.ignore_ip = load_config() - #self.bad_ip_queue = [] + self.watch_while, self.default_maxtries, self.vilain_table, self.ignore_ips, self.sleeptime = load_config() + self.ip_seen_at = {} + self.load_bad_ips() self.bad_ip_queue = asyncio.Queue(loop=self.loop) for entry in load_sections(): logger.info("Start vilain for {}".format(entry['name'])) - asyncio.ensure_future(self.check_logs(entry['logfile'], entry['regex'], entry['name'])) + asyncio.ensure_future(self.check_logs(entry['logfile'], entry['maxtries'], entry['regex'], entry['name'])) asyncio.ensure_future(self.ban_ips()) + asyncio.ensure_future(self.clean_ips()) + + def load_bad_ips(self): + try: + ret = subprocess.check_output(["pfctl", "-t", self.vilain_table, "-T", "show"]) + except: + ret = "" + for res in ret.split(): + ip = res.strip().decode('utf-8') + logger.debug('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 + self.ip_seen_at[ip]={'time':time.time(),'count':1} + def start(self): try: @@ -103,7 +119,8 @@ finally: self.loop.close() - async def check_logs(self, logfile, regex, reason): + + async def check_logs(self, logfile, maxtries, regex, reason): """ worker who put in bad_ip_queue bruteforce IP """ @@ -116,9 +133,10 @@ mtime = stat.st_mtime RE = re.compile(regex) while True: - await asyncio.sleep(sleeptime) + await asyncio.sleep(self.sleeptime) stat = os.stat(logfile) if mtime < stat.st_mtime: + logger.debug("{} has been modified".format(logfile)) mtime = stat.st_mtime with open(logfile, "rb") as f: f.seek(size) @@ -127,54 +145,57 @@ line = "".join(ul).strip() ret = RE.match(line) + logger.debug('line:{}'.format(line)) if ret: bad_ip = ret.groups()[0] - if bad_ip not in self.ignore_ip : - #self.bad_ip_queue.append({'ip' : bad_ip, 'reason' : reason}) + if bad_ip not in self.ignore_ips : + logger.info('line match {} because of rule : {}'.format(bad_ip, reason)) await self.bad_ip_queue.put({'ip' : bad_ip, 'reason' : reason}) + logger.debug('queue size: {}'.format(self.bad_ip_queue.qsize())) + else: + logger.info('line match {}. But IP in ignore list'.format(bad_ip)) size = stat.st_size async def ban_ips(self): """ - worker who ban IP on bad_ip_queue - add IP in bad_ips_list - record time when this IP has been seen in ip_seen_at = { ip:time } - - check number of occurence of the same ip in bad_ips_list - if more than 3 : ban and clean of list - - check old ip in ip_seen_at : remove older than watch_while + record time when this IP has been seen in ip_seen_at = { ip:{'time':<time>,'count':<counter} } """ - - bad_ips_list = [] - ip_seen_at = {} + logger.info('ban_ips sarted with sleeptime={}'.format(self.sleeptime)) while True: - await asyncio.sleep(sleeptime) - #if not len(s#elf.bad_ip_queue) > 0: - # continue + await asyncio.sleep(self.sleeptime) ip_item = await self.bad_ip_queue.get() - #ip_item = self.bad_ip_queue.pop() + logger.debug('ban_ips awake') ip = ip_item['ip'] reason = ip_item['reason'] - logger.info("{} detected, reason {}".format(ip, reason)) - bad_ips_list.append(ip) - ip_seen_at[ip] = time.time() - n_ip = bad_ips_list.count(ip) - if n_ip >= self.maxtries: - logger.info("Blacklisting {}".format(ip)) - subprocess.call(["pfctl", "-t", self.vilain_table, "-T", "add", ip]) - ip_seen_at.pop(ip) - while ip in bad_ips_list: - bad_ips_list.remove(ip) + maxtries = ip_item['maxtries'] + self.ip_seen_at.setdefault(ip,{'time':time.time(),'count':0}) + self.ip_seen_at[ip]['count'] += 1 + n_ip = self.ip_seen_at[ip]['count'] + 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)) + self.ip_seen_at.pop(ip) + #for debugging, this line allow us to see if the script run until here + logger.debug('ban_ips end:{}'.format(self.ip_seen_at)) + async def clean_ips(self): + """ + check old ip in ip_seen_at : remove older than watch_while + """ + logger.info('clean_ips sarted with sleeptime={}'.format(self.sleeptime)) + while True: + await asyncio.sleep(self.sleeptime) to_remove = [] - for recorded_ip, last_seen in ip_seen_at.items(): - if time.time() - last_seen >= self.watch_while: - logger.info("{} not seen since a long time, forgetting...".format(recorded_ip)) + 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 i in to_remove: - ip_seen_at.pop(i) - + for ip in to_remove: + self.ip_seen_at.pop(ip) + #for debugging, this line allow us to see if the script run until here + logger.debug('clean_ips end:{}'.format(self.ip_seen_at)) @@ -182,8 +203,7 @@ def main(): os.chdir(os.path.dirname(os.path.abspath(__file__))) v = Vilain() - p = Process(target=v.start()) - p.start() + v.start() return 0 if __name__ == '__main__': -- Gitblit v1.9.3