diff --git a/README.md b/README.md index 775436d..99c0a1f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # ssh_reporting -Report ssh connections attempts by periodic mail. \ No newline at end of file +Report ssh connections attempts by periodic mail. diff --git a/report.py b/report.py new file mode 100644 index 0000000..751afb6 --- /dev/null +++ b/report.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +from systemd import journal +from datetime import datetime, timedelta +import re # read lines +# Import smtplib for the actual sending function +import smtplib +# Import the email modules we'll need +from email.message import EmailMessage +from tabulate import tabulate +import GeoIP +import os +import socket +import logging +import logging.config + +class connection: + def __init__(self,logger): + self.logger = logger + self.attempts = {} + self.gi = GeoIP.open("/usr/share/GeoIP/GeoIP.dat", GeoIP.GEOIP_STANDARD) + + def __add(self,hostname: str,user: str, pattern: str): + self.logger.debug("<<< attempts:{}".format(self.attempts)) + self.logger.debug("--- add {} {} {}".format(hostname,user,pattern)) + if hostname in self.attempts: + if user in self.attempts[hostname]: + if pattern in self.attempts[hostname][user]: + self.attempts[hostname][user][pattern] += 1 + else: + self.attempts[hostname][user][pattern] = 1 + else: + self.attempts[hostname][user] = { pattern: 1 } + else: + self.attempts[hostname] = { user: { pattern: 1 } } + self.logger.debug(">>> attempts:{}".format(self.attempts)) + + def __get_list(self): + liste = [] + for h,v1 in self.attempts.items(): + self.logger.debug("h {}".format(h)) + gcn = self.gi.country_name_by_addr(h) + if gcn is not None: + g = gcn + else: + g = "localhost" + for u,v2 in v1.items(): + self.logger.debug("u {}".format(u)) + for s,n in v2.items(): + self.logger.debug("s {}".format(s)) + self.logger.debug("g {} h {} u {} s {} n {}".format(g,h,u,s,n)) + liste.append([g,h,u,s,n]) + self.logger.debug(liste) + return liste + + def report(self): + headers = { 'Country':'', 'Hostname':'', 'User':'', 'Source':'', 'Number':'' } + header = headers.keys() + msg = EmailMessage() + msg.set_content(tabulate(self.__get_list(),headers)) + msg['Subject'] = f'[ssh][attempts] Reporting' + msg['From'] = "{}@{}".format(os.getlogin(),socket.gethostname()) + msg['To'] = "{}@{}".format(os.getlogin(),'192.168.0.238') + self.logger.debug(msg) + s = smtplib.SMTP('localhost') + s.send_message(msg) + s.quit() + + def addmessage(self, message:str): + patterns = { + 'pam_unix':r'''^pam_unix[^ ]*[ ](?P[a-z ]+)[; ]+logname=[.]*[ ]uid=[0-9]+[ ]euid=[0-9]+[ ]tty=[sh]+[ ]ruser=[.]*[ ]rhost=(?P[0-9.]+)[ ]+user=(?P[a-zA-Z0-9]+)$''', + 'failed':r'''^Failed[ ]password[ ]for[ ]+(?P[a-zA-Z0-9]+)[ ]from[ ](?P[0-9.]+)[ ]port[ ][0-9]+[ ](?P[a-z0-9]+)$''', + 'invalid':r'''^Failed[ ]password[ ]for[ ]invalid[ ]user[ ]+(?P[a-zA-Z0-9]+)[ ]from[ ](?P[0-9.]+)[ ]port[ ][0-9]+[ ](?P[a-z0-9]+)$''', + 'closed':r'''^Connection[ ]closed[ ]by[ ]authenticating[ ]user[ ](?P[a-zA-Z0-9]+)[ ](?P[0-9.]+)[ ]port[ ][0-9]+[ ](?P[a-z\[\]]+)$''', + 'reset':r'''^Connection[ ]reset[ ]by[ ]invalid[ ]user[ ](?P[a-zA-Z0-9]+)[ ](?P[0-9.]+)[ ]port[ ][0-9]+[ ](?P[a-z\[\]]+)$''', + 'pam':r'''^PAM[ ][0-9][ ]more[ ]authentication[ ]failures;[ ]logname=[ ]uid=[0-9]+[ ]euid=[0-9]+[ ]tty=ssh[ ]ruser=[ ]rhost=(?P[0-9.]+)[ ]+user=(?P[a-zA-Z0-9]+)$''', + 'pamano':r'''^PAM[ ][0-9][ ]more[ ]authentication[ ]failures;[ ]logname=[ ]uid=[0-9]+[ ]euid=[0-9]+[ ]tty=ssh[ ]ruser=[ ]rhost=(?P[0-9.]+)([ ]+user=(?P[a-zA-Z0-9]+|)|)$''', + 'stopping':r'''^Received[ ]signal[ ]15;[ ]terminating.$''', + 'ignoring':r'''^PAM[ ]service[(]sshd[)][ ]ignoring[ ]max[ ]retries;[ ] [0-9]+ > [0-9]+$''', + 'listen':r'''^Server[ ]listening[ ]on[ ][0-9:.]+[ ]port[ ][0-9]+[.]$''' + } + compiled = { r:re.compile(patterns[r]) for r in iter(patterns) } + matched = { r:re.match(compiled[r],message) for r in iter(patterns) } + self.logger.debug("$$$ matched: {}".format(matched)) + lst = [v for k, v in matched.items() if v] + self.logger.debug("lst: {}".format(lst)) + pattern = [k for k, v in matched.items() if v] + self.logger.debug("pattern: {}".format(pattern)) + for p in pattern: + match p: + case 'listen'|'stopping'|'ignoring': + continue + case 'pam_unix'|'failed'|'invalid'|'closed'|'reset'|'pam'|'pamano': + pattern0 = pattern[0] + self.logger.debug("::: pattern0: {}".format(pattern0)) + val = lst[0] if lst else None + if val: + self.__add(hostname=val['hostname'], user=val['user'], pattern=pattern0) + case _: + print('======== Unmatched =============') + break + +logging.basicConfig(filename='/tmp/ssh_reporting.log',level=logging.DEBUG) +logger=logging.getLogger(__name__) +j = journal.Reader() +c = connection(logger) +j.seek_realtime(datetime.now() - timedelta(hours=24)) +j.add_match('_SYSTEMD_UNIT=ssh.service') +for entry in j: + logger.debug(entry['MESSAGE']) + c.addmessage(entry['MESSAGE']) +logger.debug("--- Generating report") +c.report()