summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xautologs34
-rw-r--r--logparser.py205
2 files changed, 239 insertions, 0 deletions
diff --git a/autologs b/autologs
new file mode 100755
index 0000000..5359142
--- /dev/null
+++ b/autologs
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+scriptlocalpath="";
+dbpass='';
+
+PrepareInputFiles() {
+ local sincedate=$1;
+ local untildate=$2;
+ local inputfilename=$3;
+ journalctl -u ssh -S "${sincedate}" -U "${untildate}" > "${scriptlocalpath}/${inputfilename}";
+}
+
+ClearTempFiles() {
+ local UpperRangeShort=$(date '+%Y-%m' --date 'today');
+ local inputfilename=$1;
+ if [ -f "${scriptlocalpath}/${inputfilename}" ]; then
+ mv "${scriptlocalpath}/${inputfilename}" "${scriptlocalpath}/backup/${UpperRangeShort}${inputfilename}";
+ else
+ echo "did not find ${inputfilename} file";
+ fi
+}
+
+main() {
+ local UpperRangeShort=$(date '+%Y-%m' --date 'today');
+ local LowerRangeShort=$(date '+%Y-%m' --date '1 week ago');
+ local UpperRange="${UpperRangeShort}-01";
+ local LowerRange="${LowerRangeShort}-01";
+ local logfilename='sshlogs';
+ PrepareInputFiles ${LowerRange} ${UpperRange} ${logfilename};
+ python3 "${scriptlocalpath}/ssh_login_parser.py" ${LowerRange} ${UpperRange} ${logfilename} ${dbpass};
+ ClearTempFiles ${logfilename};
+}
+
+main;
diff --git a/logparser.py b/logparser.py
new file mode 100644
index 0000000..f5b6d93
--- /dev/null
+++ b/logparser.py
@@ -0,0 +1,205 @@
+import time
+import datetime
+import re
+import mysql.connector
+import getpass
+import sys
+
+MonthCodes=dict();
+MonthCodes["Jan"]=1;
+MonthCodes["Feb"]=2;
+MonthCodes["Mar"]=3;
+MonthCodes["Apr"]=4;
+MonthCodes["May"]=5;
+MonthCodes["Jun"]=6;
+MonthCodes["Jul"]=7;
+MonthCodes["Aug"]=8;
+MonthCodes["Sep"]=9;
+MonthCodes["Oct"]=10;
+MonthCodes["Nov"]=11;
+MonthCodes["Dec"]=12;
+
+expressions=dict();
+expressions["Connection closed by invalid user "]="password";
+expressions["Disconnected from invalid user "]="password";
+expressions["Connection closed by authenticating user "]="password";
+expressions["Disconnected from authenticating user "]="password";
+expressions["Disconnecting authenticating user "]="password";
+expressions["Connection closed by "]="kex";
+expressions["Disconnecting invalid user "]="password";
+
+OrderedDataKeys=["PID","timestamp","duration","IP","port","user","attempts","type"];
+
+noise=["/n",
+": Too many authentication failures",
+" [preauth]",
+"Bye Bye"
+];
+
+def RemoveNoise(textline):
+ updatedtext=""+textline;
+ for n in noise:
+ updatedtext=updatedtext.replace(n,"");
+ replaceind=updatedtext.find(": Change of username or service not allowed");
+ if replaceind!=-1:
+ updatedtext=updatedtext[:replaceind];
+ return updatedtext;
+
+class sshlogentry:
+ def __init__(self,ProcID):
+ self.timestampsList=[];
+ self.data=dict();
+ self.PID=ProcID;
+ self.data["timestamp"]=None;
+ self.data["IP"]=None;
+ self.data["port"]=None;
+ self.data["attempts"]=0;
+ self.data["user"]=None;
+ self.data["duration"]=None;
+ self.data["type"]=None;
+ self.isValid=True;
+
+ def VerifyData(self):
+ if len(self.timestampsList)==0:
+ self.isValid=False;
+ return False;
+ self.data["timestamp"]=min(self.timestampsList);
+ self.data["duration"]=max(self.timestampsList)-self.data["timestamp"];
+ self.data["PID"]=self.PID;
+ if self.data["user"]==None:
+ self.data["user"]="n.a";
+ for d in self.data:
+ self.isValid *= self.data[d]!=None;
+ return True;
+ #SQLstatement="INSERT INTO entries (PID, timestamp, duration, IPaddress, port, user, attempts) VALUES (%s, %s, %s, %s, %s, %s, %s)";
+ def OutputData(self,keyorder):
+ return tuple([self.data.get(datakey,"") for datakey in keyorder]);
+
+#year is not explicitly written in logs
+#might need to check the code behind auth.log
+def FormatLogDate(DateString,ld_year):
+ ld_month=MonthCodes[DateString[:3]];
+ ld_day=int(DateString[4:6]);
+ ld_hour,ld_min,ld_sec=[int(ld_temp) for ld_temp in DateString[7:15].split(":")];
+ return datetime.datetime(ld_year,ld_month,ld_day,ld_hour,ld_min,ld_sec);
+
+
+def ParseFile(filename,LowerTS,UpperTS,VerboseFeedback=True):
+ Entries=[];
+ Errors=[];
+ CurrentEntry=sshlogentry(None);
+ #
+ DataSource=open(filename,"r");
+ for ds_i,dsLine in enumerate(DataSource):
+ dsLine=dsLine.replace("\n","");
+ OpeningBracket=dsLine.index("[");
+ ClosingBracket=dsLine.index("]");
+ entryID=dsLine[OpeningBracket+1:ClosingBracket];
+ if entryID != CurrentEntry.PID:
+ CurrentEntry.VerifyData();
+ if CurrentEntry.isValid:
+ if LowerTS <= CurrentEntry.data["timestamp"] <=UpperTS:
+ Entries.append(CurrentEntry);
+ else:
+ Errors.append(CurrentEntry);
+ CurrentEntry=sshlogentry(entryID);
+ entryDate=dsLine[0:15];
+ entryDateFormatted=FormatLogDate(entryDate,2025);
+ entryTimestamp=int(entryDateFormatted.timestamp());
+ if VerboseFeedback:
+ print(f'{entryDateFormatted.year}-{entryDateFormatted.month}-{entryDateFormatted.day}: line #{ds_i}');
+ CurrentEntry.timestampsList.append(entryTimestamp);
+ #
+ dsLineParsable=RemoveNoise(dsLine[ClosingBracket+3:]);
+ CurrentEntry.data["attempts"]+=dsLineParsable.count("Failed password for");
+ for expression in expressions:
+ if expression in dsLineParsable and " port " in dsLineParsable:
+ entrySublist,entryPort=dsLineParsable.replace(expression,"").split(" port ");
+ CurrentEntry.data["port"]=int(entryPort);
+ IPmatch=re.search(r'(\d+)\.(\d+)\.(\d+)\.(\d+)',entrySublist);
+ if not IPmatch:
+ IPmatch=re.search(r'([a-f0-9:]+:+)+[a-f0-9]*(%\w+)*',entrySublist);
+ CurrentEntry.data["IP"]=IPmatch.group();
+ CurrentEntry.data["user"]=entrySublist.replace(" "+CurrentEntry.data["IP"],"").replace(" from","");
+ CurrentEntry.data["type"]=""+expressions[expression];
+ break;
+ DataSource.close();
+ return Entries,Errors;
+
+def CreateFeedFile(EntryList,feedorder,FeedFilename="ssh-logins-feedfile",delim=";"):
+ feedfile=open(FeedFilename,"w");
+ for entryItem in EntryList:
+ feedline=delim.join([f'{ei}'for ei in entryItem.OutputData(feedorder)]);
+ feedfile.write(feedline+"\n");
+ feedfile.close();
+
+def UpdateDatabase(ParsedEntriesList,keyorder, pword=None):
+ ParsedValuesList=[];
+ for pel in ParsedEntriesList:
+ ParsedValuesList.append(pel.OutputData(keyorder));
+ if pword==None:
+ SQLpassword=getpass.getpass("SQL user password: ");
+ else:
+ SQLpassword=pwd;
+ SQLDatabase=mysql.connector.connect(
+ host="localhost",
+ user="sshlogins",
+ password=SQLpassword,
+ database="sshlogs");
+ SQLcursor=SQLDatabase.cursor();
+ OrderedData=", ".join(keyorder);
+ SQLstatement="INSERT INTO entries (PID, timestamp, duration, IPaddress, port, user, attempts, type) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)";
+ batchsize=100;
+ for batch in range(1+len(ParsedValuesList)//batchsize):
+ SQLcursor.executemany(SQLstatement,ParsedValuesList[batch*batchsize:(batch+1)*batchsize]);
+ SQLDatabase.commit();
+ print("batch ",batch," out of ",1+len(ParsedValuesList)//batchsize," inserted");
+
+def CreateUncategorizedFile(UncategorizedList,fname="uncategorized-sshlogs.err",delim=";"):
+ uncat=open(fname,"a");
+ for err in UncategorizedList:
+ uncat.write(err+"\n");
+ uncat.close();
+
+def ConvertBoundaryDates(LB,UB):
+ boundaryTimestamps=list();
+ #second date must be reduced by 1
+ #so it's a previous month
+ for bi,bdate in enumerate([LB,UB]):
+ yy,mm,dd=[int(v) for v in bdate.split("-")]
+ ts=datetime.datetime(yy,mm,dd).timestamp();
+ boundaryTimestamps.append(int(ts)-bi);
+ return boundaryTimestamps;
+
+def PerformUpdate(LowerBoundsDate,UpperBoundsDate,RawData,dbpassword=None):
+ print("Performing full database update:");
+ print("Start...");
+ LowerBounds,UpperBounds=ConvertBoundaryDates(LowerBoundsDate,UpperBoundsDate);
+ updateEntries,updateErrors=ParseFile(RawData,LowerBounds,UpperBounds,True);
+ print("File parsed");
+ #CreateFeedFile(EntryList=updateEntries,feedorder=OrderedDataKeys,FeedFilename="ssh-logins-feedfile");
+ print("backup feedfile created");
+ UpdateDatabase(updateEntries,OrderedDataKeys,dbpassword);
+ print("Instances retrieved:",len(updateEntries));
+ print("Instances discarded:",len(updateErrors));
+ updateEntries,updateErrors=[],[]; #i think this will clear memory
+
+'''
++-------------+-----------+
+| COLUMN_NAME | DATA_TYPE |
++-------------+-----------+
+| lp | int |
+| PID | int |
+| timestamp | int |
+| duration | mediumint |
+| IPaddress | varchar |
+| port | varchar |
+| user | varchar |
+| attempts | smallint |
+| type | varchar |
++-------------+-----------+
+9 rows in set (0,01 sec)
+ '''
+
+if __name__=="__main__":
+ PerformUpdate(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4]);