Report ssh connections attempts by periodic mail.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
5.4 KiB

#!/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<status>[a-z ]+)[; ]+logname=[.]*[ ]uid=[0-9]+[ ]euid=[0-9]+[ ]tty=[sh]+[ ]ruser=[.]*[ ]rhost=(?P<hostname>[0-9.]+)[ ]+user=(?P<user>[a-zA-Z0-9]+)$''',
'failed':r'''^Failed[ ]password[ ]for[ ]+(?P<user>[a-zA-Z0-9]+)[ ]from[ ](?P<hostname>[0-9.]+)[ ]port[ ][0-9]+[ ](?P<protocol>[a-z0-9]+)$''',
'invalid':r'''^Failed[ ]password[ ]for[ ]invalid[ ]user[ ]+(?P<user>[a-zA-Z0-9]+)[ ]from[ ](?P<hostname>[0-9.]+)[ ]port[ ][0-9]+[ ](?P<protocol>[a-z0-9]+)$''',
'closed':r'''^Connection[ ]closed[ ]by[ ]authenticating[ ]user[ ](?P<user>[a-zA-Z0-9]+)[ ](?P<hostname>[0-9.]+)[ ]port[ ][0-9]+[ ](?P<step>[a-z\[\]]+)$''',
'reset':r'''^Connection[ ]reset[ ]by[ ]invalid[ ]user[ ](?P<user>[a-zA-Z0-9]+)[ ](?P<hostname>[0-9.]+)[ ]port[ ][0-9]+[ ](?P<step>[a-z\[\]]+)$''',
'pam':r'''^PAM[ ][0-9][ ]more[ ]authentication[ ]failures;[ ]logname=[ ]uid=[0-9]+[ ]euid=[0-9]+[ ]tty=ssh[ ]ruser=[ ]rhost=(?P<hostname>[0-9.]+)[ ]+user=(?P<user>[a-zA-Z0-9]+)$''',
'pamano':r'''^PAM[ ][0-9][ ]more[ ]authentication[ ]failures;[ ]logname=[ ]uid=[0-9]+[ ]euid=[0-9]+[ ]tty=ssh[ ]ruser=[ ]rhost=(?P<hostname>[0-9.]+)([ ]+user=(?P<user>[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()