import logging from systemd import journal from datetime import timedelta from config import Config from utils import filter_message_list from message_parse import Message from sys import exit import detector import signal import time import logs running = True logger = logging.getLogger("sshd-bruteforce-detector") def handle_exit_signal(signum, _): global running global logger running = False logger.info(f"{signal.Signals(signum).name} received. Stopping manually...") if signum == signal.SIGINT: exit(130) else: exit(0) def main(): logs.setup_logging() global logger config = Config() logger = logging.getLogger("sshd-bruteforce-detector") window = config.get_window() max_attempts = config.get_max_attempts() j = journal.Reader() j.add_match(_SYSTEMD_UNIT="sshd.service") j.seek_tail() j.get_previous() messages: dict[str, list] = {} message_detected = False logger.info( f"Starting sshd-bruteforce-detector with configuration: window={window} max_attempts={max_attempts}" ) global running while running: for entry in j: message_str = entry["MESSAGE"] if Message.is_failed_auth_message(message_str): message = Message(message_str, entry["__REALTIME_TIMESTAMP"]) if not messages.get(str(message.rhost)): messages[str(message.rhost)] = [] messages[str(message.rhost)].append(message) logger.info( f"Authentication failure from host {message.rhost} for user {message.user} on sshd" ) message_detected = True new_messages = messages.copy() for ip, message_list in messages.items(): new_messages[ip] = filter_message_list( message_list, timedelta(seconds=window) ) if ( detector.detect_suspect_messages(new_messages[ip], max_attempts) and message_detected ): alert_window = new_messages[ip][-1].date - new_messages[ip][0].date logger.critical( f"{len(new_messages[ip])} authentication failure events from {ip} on sshd in {alert_window.seconds} seconds." ) message_detected = False time.sleep(0.3) if __name__ == "__main__": signal.signal(signal.SIGTERM, handle_exit_signal) signal.signal(signal.SIGINT, handle_exit_signal) main()