diff --git a/data/Dockerfiles/fail2ban/Dockerfile b/data/Dockerfiles/fail2ban/Dockerfile index 93c4e924..9f81f14e 100644 --- a/data/Dockerfiles/fail2ban/Dockerfile +++ b/data/Dockerfiles/fail2ban/Dockerfile @@ -2,7 +2,7 @@ FROM python:2-alpine LABEL maintainer "Andre Peters " RUN apk add -U --no-cache iptables ip6tables -RUN pip install docker +RUN pip install docker redis COPY logwatch.py / CMD ["python2", "-u", "/logwatch.py"] diff --git a/data/Dockerfiles/fail2ban/logwatch.py b/data/Dockerfiles/fail2ban/logwatch.py index 22ea88f7..4a3645e7 100644 --- a/data/Dockerfiles/fail2ban/logwatch.py +++ b/data/Dockerfiles/fail2ban/logwatch.py @@ -8,20 +8,28 @@ import ipaddress import subprocess from threading import Thread import docker +import redis +import time +import json +r = redis.StrictRedis(host='172.22.1.249', port=6379, db=0) RULES = { 'mailcowdockerized_postfix-mailcow_1': 'warning: .*\[([0-9a-f\.:]+)\]: SASL .* authentication failed', 'mailcowdockerized_dovecot-mailcow_1': '-login: Disconnected \(auth failed, .*\): user=.*, method=.*, rip=([0-9a-f\.:]+),', 'mailcowdockerized_sogo-mailcow_1': 'SOGo.* Login from \'([0-9a-f\.:]+)\' for user .* might not have worked', 'mailcowdockerized_php-fpm-mailcow_1': 'Mailcow UI: Invalid password for .* by ([0-9a-f\.:]+)', } -BAN_TIME = 1800 -MAX_ATTEMPTS = 10 + +r.setnx("F2B_BAN_TIME", "1800") +r.setnx("F2B_MAX_ATTEMPTS", "10") bans = {} +log = {} quit_now = False def ban(address): + BAN_TIME = int(r.get("F2B_BAN_TIME")) + MAX_ATTEMPTS = int(r.get("F2B_MAX_ATTEMPTS")) ip = ipaddress.ip_address(address.decode('ascii')) if type(ip) is ipaddress.IPv6Address and ip.ipv4_mapped: ip = ip.ipv4_mapped @@ -39,6 +47,9 @@ def ban(address): bans[net]['last_attempt'] = time.time() if bans[net]['attempts'] >= MAX_ATTEMPTS: + log['time'] = int(round(time.time())) + log['message'] = "Banning %s" % net + r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False)) print "Banning %s" % net if type(ip) is ipaddress.IPv4Address: subprocess.call(["iptables", "-I", "INPUT", "-s", net, "-j", "REJECT"]) @@ -47,9 +58,15 @@ def ban(address): subprocess.call(["ip6tables", "-I", "INPUT", "-s", net, "-j", "REJECT"]) subprocess.call(["ip6tables", "-I", "FORWARD", "-s", net, "-j", "REJECT"]) else: + log['time'] = int(round(time.time())) + log['message'] = "%d more attempts until %s is banned" % (MAX_ATTEMPTS - bans[net]['attempts'], net) + r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False)) print "%d more attempts until %s is banned" % (MAX_ATTEMPTS - bans[net]['attempts'], net) def unban(net): + log['time'] = int(round(time.time())) + log['message'] = "Unbanning %s" % net + r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False)) print "Unbanning %s" % net if type(ipaddress.ip_network(net.decode('ascii'))) is ipaddress.IPv4Network: subprocess.call(["iptables", "-D", "INPUT", "-s", net, "-j", "REJECT"]) @@ -64,11 +81,17 @@ def quit(signum, frame): quit_now = True def clear(): + log['time'] = int(round(time.time())) + log['message'] = "Clearing all bans" + r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False)) print "Clearing all bans" for net in bans.copy(): unban(net) def watch(container): + log['time'] = int(round(time.time())) + log['message'] = "Watching %s" % container + r.lpush("F2B_LOG", json.dumps(log, ensure_ascii=False)) print "Watching", container client = docker.from_env() for msg in client.containers.get(container).attach(stream=True, logs=False): @@ -79,6 +102,7 @@ def watch(container): def autopurge(): while not quit_now: + BAN_TIME = int(r.get("F2B_BAN_TIME")) for net in bans.copy(): if time.time() - bans[net]['last_attempt'] > BAN_TIME: unban(net) diff --git a/docker-compose.yml b/docker-compose.yml index 719d4a9a..8b7ee433 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -311,7 +311,7 @@ services: - acme fail2ban-mailcow: - image: mailcow/fail2ban:1.0 + image: mailcow/fail2ban:1.1 build: ./data/Dockerfiles/fail2ban depends_on: - dovecot-mailcow