-
Notifications
You must be signed in to change notification settings - Fork 21
Improve ebauth log parsing, and parse stepup-authentication logs also #558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| {{ rsyslog_dir }}/log_logins/{{ item.name }}/stepup-authentication.log | ||
| { | ||
| missingok | ||
| daily | ||
| rotate 180 | ||
| sharedscripts | ||
| dateext | ||
| dateyesterday | ||
| compress | ||
| delaycompress | ||
| create 0640 root {{ rsyslog_read_group }} | ||
| postrotate | ||
| /usr/local/sbin/parse_stepupauth_to_mysql_{{ item.name }}.py > /dev/null | ||
| systemctl kill -s HUP rsyslog.service | ||
| endscript | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,11 +21,17 @@ cursor = db.cursor() | |
|
|
||
| def update_lastseen(user_id, date): | ||
| query = """ | ||
| REPLACE INTO last_login (userid, lastseen) | ||
| INSERT INTO last_login (userid, lastseen) | ||
| VALUES (%s, %s) | ||
| ON DUPLICATE KEY UPDATE | ||
| lastseen = GREATEST(lastseen, VALUES(lastseen)) | ||
| """ | ||
| cursor.execute(query, (user_id, date)) | ||
| db.commit() | ||
| try: | ||
| cursor.execute(query, (user_id, date)) | ||
| db.commit() | ||
| except Exception as e: | ||
| db.rollback() | ||
| print(f"Error updating last_login for user {user_id}: {e}") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙌 |
||
|
|
||
| def load_in_mysql(a,b,c,d,e,f,g,h): | ||
| sql = """insert into log_logins(idpentityid,spentityid,loginstamp,userid,keyid,sessionid,requestid,trustedproxyentityid) values(%s,%s,%s,%s,%s,%s,%s,%s)""" | ||
|
|
@@ -73,4 +79,3 @@ for filename in os.listdir(workdir): | |
|
|
||
| cursor.close() | ||
| db.close() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| #!/usr/bin/python3 | ||
| # This script parses rotated stepup-authentication.log files produced by engineblock. | ||
| # It filters for successful logins (authentication_result:OK) and inserts the data | ||
| # into the log_logins and last_login MySQL tables. | ||
| # This script is intended to be run separately during logrotate. | ||
|
|
||
| import os | ||
| import sys | ||
| import json | ||
| import MySQLdb | ||
| from dateutil.parser import parse | ||
|
|
||
| # Configuration variables (to be injected by Ansible/Jinja2) | ||
| mysql_host="{{ item.db_loglogins_host }}" | ||
| mysql_user="{{ item.db_loglogins_user }}" | ||
| mysql_password="{{ item.db_loglogins_password }}" | ||
| mysql_db="{{ item.db_loglogins_name }}" | ||
| workdir="{{ rsyslog_dir }}/log_logins/{{ item.name}}/" | ||
|
|
||
| # Establish database connection | ||
| try: | ||
| db = MySQLdb.connect(mysql_host,mysql_user,mysql_password,mysql_db ) | ||
| cursor = db.cursor() | ||
| except Exception as e: | ||
| print(f"Error connecting to MySQL: {e}") | ||
| sys.exit(1) | ||
|
|
||
| # --- Database Functions --- | ||
|
|
||
| def update_lastseen(user_id, date): | ||
| """ | ||
| Updates the last_login table. | ||
| Uses GREATEST() to ensure only newer dates overwrite the existing 'lastseen' value. | ||
| """ | ||
| query = """ | ||
| INSERT INTO last_login (userid, lastseen) | ||
| VALUES (%s, %s) | ||
| ON DUPLICATE KEY UPDATE | ||
| lastseen = GREATEST(lastseen, VALUES(lastseen)) | ||
| """ | ||
| try: | ||
| cursor.execute(query, (user_id, date)) | ||
| db.commit() | ||
| except Exception as e: | ||
| db.rollback() | ||
| print(f"Error updating last_login for user {user_id}: {e}") | ||
|
|
||
| def load_stepup_in_mysql(idp, sp, loginstamp, userid, requestid): | ||
| """ | ||
| Inserts Step-up login data into the log_logins table. | ||
| Fills keyid, sessionid, and trustedproxyentityid with NULL. | ||
| """ | ||
| # Columns in log_logins: idpentityid, spentityid, loginstamp, userid, keyid, sessionid, requestid, trustedproxyentityid | ||
|
|
||
| keyid = None | ||
| sessionid = None | ||
| trustedproxyentityid = None | ||
|
|
||
| sql = """ | ||
| INSERT INTO log_logins(idpentityid, spentityid, loginstamp, userid, keyid, sessionid, requestid, trustedproxyentityid) | ||
| VALUES(%s, %s, %s, %s, %s, %s, %s, %s) | ||
| """ | ||
| try: | ||
| cursor.execute(sql, (idp, sp, loginstamp, userid, keyid, sessionid, requestid, trustedproxyentityid)) | ||
| db.commit() | ||
| except Exception as e: | ||
| db.rollback() | ||
| print(f"Error inserting stepup data: {e}") | ||
| # Print the data that failed insertion | ||
| print((idp, sp, loginstamp, userid, keyid, sessionid, requestid, trustedproxyentityid)) | ||
|
|
||
| # --- Parsing Function --- | ||
|
|
||
| def parse_stepup_lines(a): | ||
| """ | ||
| Opens the stepup log file, parses each line, filters for successful logins, | ||
| and loads the data into MySQL. | ||
| """ | ||
| input_file = open((a), 'r') | ||
| for line in input_file: | ||
| try: | ||
| # Assumes JSON data starts after the first ']:' | ||
| jsonline = line.split(']:',2)[1] | ||
| data = json.loads(jsonline) | ||
| except: | ||
| continue | ||
|
|
||
| # 1. Filtering condition: Only parse logs having authentication_result:OK | ||
| if data.get("authentication_result") != "OK": | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only successful authentications are logged, so this check is not necessary. There is currently a bug in the Stepup-Gateway where FAILED is logged, even though the result is OK, making this check do the wrong thing now. |
||
| continue | ||
|
|
||
| # 2. Extract required fields | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fields are in a JSON dict "context", not in the top level dict. E.g. Correct: otherwise user_id and timestamp will always be none and nothing will happen. |
||
| user_id = data.get("identity_id") | ||
| timestamp = data.get("datetime") | ||
| request_id = data.get("request_id") | ||
| sp_entity_id = data.get("requesting_sp") | ||
| idp_entity_id = data.get("authenticating_idp") | ||
|
|
||
| # Basic data validation | ||
| if not user_id or not timestamp: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want to know if this fails. Now it fails silently, which means we would miss if this is not working. |
||
| continue | ||
|
|
||
| try: | ||
| # 3. Format date and time for MySQL | ||
| loginstamp = parse(timestamp).strftime("%Y-%m-%d %H:%M:%S") | ||
| last_login_date = parse(timestamp).strftime("%Y-%m-%d") | ||
| except: | ||
| continue | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same: this fails silently. Not what we want. |
||
|
|
||
| # 4. Insert into MySQL | ||
| load_stepup_in_mysql(idp_entity_id, sp_entity_id, loginstamp, user_id, request_id) | ||
|
|
||
| # 5. Update last login date | ||
| update_lastseen(user_id, last_login_date) | ||
|
|
||
|
|
||
| # --- Main Execution --- | ||
|
|
||
| ## Loop over the files and parse them one by one | ||
| for filename in os.listdir(workdir): | ||
| filetoparse=(os.path.join(workdir, filename)) | ||
|
|
||
| # Check for Stepup files, ignore compressed files | ||
| if os.path.isfile(filetoparse) and filename.startswith("stepup-authentication.log-") and not filename.endswith(".gz"): | ||
| print(f"Parsing stepup log file: {filename}") | ||
| parse_stepup_lines(filetoparse) | ||
| else: | ||
| continue | ||
|
|
||
| # Close database connection | ||
| cursor.close() | ||
| db.close() | ||
| print("Stepup log parsing complete.") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙌