2 changed files with 114 additions and 1 deletions
@ -0,0 +1,113 @@
@@ -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<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() |
||||
Loading…
Reference in new issue