failUser.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #!/usr/bin/env python3
  2. from json import loads, dumps
  3. from json.decoder import JSONDecodeError
  4. import pendulum
  5. from subprocess import run, PIPE, CalledProcessError
  6. from os.path import exists, join
  7. from pyinotify import WatchManager, Notifier, ProcessEvent
  8. from pyinotify import IN_MODIFY, IN_DELETE, IN_MOVE_SELF, IN_CREATE
  9. import sys
  10. # https://github.com/manos/python-inotify-tail_example/blob/master/tail-F_inotify.py
  11. # Branch off the logging into a seperate file
  12. from config import log, load_config, save_config, add_block, rm_block, check_blocks
  13. myConfig = load_config()
  14. myfile = myConfig["target"]
  15. last_run = myConfig["last_unblock"]
  16. bad_users = myConfig["bad_users"]
  17. enable_live = myConfig["debug_blocks"]
  18. target = open(myfile, 'r')
  19. target.seek(0,2)
  20. WM = WatchManager()
  21. dirmask = IN_MODIFY | IN_DELETE | IN_MOVE_SELF | IN_CREATE
  22. def blocker(ip):
  23. # Utility function to block given ip as string
  24. if not enable_live:
  25. run(["iptables", "-I", "DOCKER-USER", "-i", "eth0", "-s", ip, "-j", "DROP"], stdout=PIPE, check=True)
  26. else:
  27. print("iptables -I DOCKER-USER -i eth0 -s {0} -j DROP".format(ip))
  28. def unblocker(ip):
  29. # Utility function to unblock given ip as string
  30. if not enable_live:
  31. try:
  32. run(["iptables", "-D", "DOCKER-USER", "-i", "eth0", "-s", ip, "-j", "DROP"], stdout=PIPE, check=True)
  33. except CalledProcessError:
  34. pass
  35. else:
  36. print("iptables -D DOCKER-USER -i eth0 -s {0} -j DROP".format(ip))
  37. # def is_bad(line):
  38. # # Given line, attempt to parse... then is there a issue with it
  39. # # Returns a python dict with ip and time in log
  40. # if line: # Do we actually have something?
  41. # try:
  42. # j = loads(line)
  43. # #if j["msg"] == "Attempt to login with banned username":
  44. # if j["username"] in bad_users:
  45. # r = {}
  46. # r["ip"] = "{0}".format(j["ip"][7:])
  47. # r["time"] = j["time"]
  48. # return r
  49. # except JSONDecodeError:
  50. # log.error("Failed to decode line, '{0}'".format(line))
  51. def numeric_check(name):
  52. """ Attempt's to convert name into a integer or rather float
  53. If it succeeds then either we have a really dumb user or
  54. we have a hacker.
  55. """
  56. try:
  57. name = float(name)
  58. return True
  59. except TypeError:
  60. return False
  61. def contains_bad(name):
  62. """ Checks each of the bad names to see if the bad name is in the
  63. name given.
  64. I.E. root123 would trigger because root is in the name.
  65. """
  66. for b in myConfig["bad_users"]:
  67. if b in name:
  68. return True
  69. return False
  70. struct = {}
  71. state = 0
  72. def is_bad(line):
  73. global state, struct
  74. if state == 0 and line.startswith("SUSPECTED"):
  75. _, user, at = line.split("'")
  76. at = at.replace(" on ", "")
  77. struct = {"user": user.lower(), "time": at}
  78. state = 1
  79. #print(struct)
  80. elif state == 1 and line.startswith("Using port"):
  81. _, ip = line.split("[")
  82. ip = ip.replace("]", "")
  83. struct["ip"] = ip
  84. state = 0
  85. #print(struct)
  86. return struct
  87. def checkup():
  88. # Check all our blocks
  89. unblocks = check_blocks()
  90. if unblocks:
  91. for ip in unblocks:
  92. log.info("Unblocked {0}".format(ip))
  93. unblocker(ip)
  94. rm_block(ip)
  95. class EventHandler(ProcessEvent):
  96. def process_IN_MODIFY(self, event):
  97. if myfile not in join(event.path, event.name):
  98. return
  99. else:
  100. #luser = is_bad(target.readline().rstrip())
  101. for line in target.readlines():
  102. luser = is_bad(line.rstrip())
  103. if(luser):
  104. if luser["ip"] in myConfig["whitelist"]:
  105. return # Don't block ourselves
  106. if luser["user"] in myConfig["bad_users"] or numeric_check(luser["user"]) or contains_bad(luser["user"]):
  107. # The user either is directly in the bad users list, either all numbers or contains a bad username.
  108. blocker(luser["ip"])
  109. now = pendulum.now().to_atom_string()
  110. log.info("Blocked {0} at {1}".format(luser["ip"], now))
  111. add_block(luser["ip"], now)
  112. def process_IN_MOVE_SELF(self, event):
  113. log.debug("Log file moved... continuing read on stale log!")
  114. def process_IN_CREATE(self, event):
  115. global target
  116. if myfile in join(event.path, event.name):
  117. target.close()
  118. target = open(myfile, 'r')
  119. log.debug("Log file created... Catching up!")
  120. for line in target.readlines():
  121. luser = is_bad(line.rstrip())
  122. if(luser):
  123. if luser["ip"] in myConfig["whitelist"]:
  124. return # Don't block ourselves
  125. if luser["user"] in myConfig["bad_users"] or numeric_check(luser["user"]) or contains_bad(luser["user"]):
  126. # The user either is directly in the bad users list, either all numbers or contains a bad username.
  127. blocker(luser["ip"])
  128. now = pendulum.now().to_atom_string()
  129. log.info("Blocked {0} at {1}".format(luser["ip"], now))
  130. add_block(luser["ip"], now)
  131. target.seek(0,2)
  132. return
  133. notifier = Notifier(WM, EventHandler())
  134. index = myfile.rfind("/")
  135. WM.add_watch(myfile[:index], dirmask)
  136. last = pendulum.parse(last_run)
  137. while True:
  138. try:
  139. now = pendulum.now()
  140. if now.diff(last).in_hours() > 1:
  141. last = now
  142. checkup()
  143. notifier.process_events()
  144. if notifier.check_events():
  145. notifier.read_events()
  146. except KeyboardInterrupt:
  147. break
  148. # Issue stop on event system
  149. notifier.stop()
  150. target.close()
  151. # Update config
  152. myConfig["last_unblock"] = last.to_atom_string()
  153. myConfig["bad_users"] = bad_users
  154. save_config(myConfig)
  155. exit(0)