logwatch.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. #!/usr/bin/env python2
  2. import re
  3. import time
  4. import atexit
  5. import signal
  6. import ipaddress
  7. import subprocess
  8. from threading import Thread
  9. import docker
  10. RULES = {
  11. 'mailcowdockerized_postfix-mailcow_1': 'warning: .*\[([0-9a-f\.:]+)\]: SASL .* authentication failed',
  12. 'mailcowdockerized_dovecot-mailcow_1': '-login: Disconnected \(auth failed, .*\): user=.*, method=.*, rip=([0-9a-f\.:]+),',
  13. 'mailcowdockerized_sogo-mailcow_1': 'SOGo.* Login from \'([0-9a-f\.:]+)\' for user .* might not have worked',
  14. }
  15. BAN_TIME = 1800
  16. MAX_ATTEMPTS = 10
  17. bans = {}
  18. quit_now = False
  19. def ban(address):
  20. ip = ipaddress.ip_address(address.decode('ascii'))
  21. if ip.is_private or ip.is_loopback:
  22. return
  23. net = ipaddress.ip_network((address + ('/24' if type(ip) is ipaddress.IPv4Address else '/64')).decode('ascii'), strict=False)
  24. net = str(net)
  25. if not net in bans or time.time() - bans[net]['last_attempt'] > BAN_TIME:
  26. bans[net] = { 'attempts': 0 }
  27. bans[net]['attempts'] += 1
  28. bans[net]['last_attempt'] = time.time()
  29. if bans[net]['attempts'] >= MAX_ATTEMPTS:
  30. print "Banning %s" % net
  31. if type(ip) is ipaddress.IPv4Address:
  32. subprocess.call(["iptables", "-I", "INPUT", "-s", net, "-j", "REJECT"])
  33. subprocess.call(["iptables", "-I", "FORWARD", "-s", net, "-j", "REJECT"])
  34. else:
  35. subprocess.call(["ip6tables", "-I", "INPUT", "-s", net, "-j", "REJECT"])
  36. subprocess.call(["ip6tables", "-I", "FORWARD", "-s", net, "-j", "REJECT"])
  37. else:
  38. print "%d more attempts until %s is banned" % (MAX_ATTEMPTS - bans[net]['attempts'], net)
  39. def unban(net):
  40. print "Unbanning %s" % net
  41. if type(ipaddress.ip_network(net.decode('ascii'))) is ipaddress.IPv4Network:
  42. subprocess.call(["iptables", "-D", "INPUT", "-s", net, "-j", "REJECT"])
  43. subprocess.call(["iptables", "-D", "FORWARD", "-s", net, "-j", "REJECT"])
  44. else:
  45. subprocess.call(["ip6tables", "-D", "INPUT", "-s", net, "-j", "REJECT"])
  46. subprocess.call(["ip6tables", "-D", "FORWARD", "-s", net, "-j", "REJECT"])
  47. del bans[net]
  48. def quit(signum, frame):
  49. global quit_now
  50. quit_now = True
  51. def clear():
  52. print "Clearing all bans"
  53. for net in bans.copy():
  54. unban(net)
  55. def watch(container):
  56. print "Watching", container
  57. client = docker.from_env()
  58. for msg in client.containers.get(container).attach(stream=True, logs=False):
  59. result = re.search(RULES[container], msg)
  60. if result:
  61. addr = result.group(1)
  62. ban(addr)
  63. def autopurge():
  64. while not quit_now:
  65. for net in bans.copy():
  66. if time.time() - bans[net]['last_attempt'] > BAN_TIME:
  67. unban(net)
  68. time.sleep(60)
  69. if __name__ == '__main__':
  70. threads = []
  71. for container in RULES:
  72. threads.append(Thread(target=watch, args=(container,)))
  73. threads[-1].daemon = True
  74. threads[-1].start()
  75. autopurge_thread = Thread(target=autopurge)
  76. autopurge_thread.daemon = True
  77. autopurge_thread.start()
  78. signal.signal(signal.SIGTERM, quit)
  79. atexit.register(clear)
  80. while not quit_now:
  81. for thread in threads:
  82. if not thread.isAlive():
  83. break
  84. time.sleep(0.1)
  85. clear()