This commit is contained in:
root
2025-10-28 21:41:58 +01:00
parent 7b41672d05
commit ea55e6f95c
17 changed files with 336 additions and 18 deletions

0
__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,42 +1,191 @@
# ============================================
# LogMon Configuration File
# ============================================
[general] [general]
# Tryb debug - wyświetla szczegółowe informacje
debug = false debug = false
# Ścieżka do pliku z logami LogMon
log_file = /var/log/logmon.log log_file = /var/log/logmon.log
# Plik PID demona
pid_file = /var/run/logmon.pid pid_file = /var/run/logmon.pid
# Backend firewall: csf, nftables, iptables, ufw
backend = csf backend = csf
# ============================================
# Konfiguracja backendów firewall
# ============================================
[backend_csf] [backend_csf]
# Ścieżka do wykonywania CSF
csf_path = /usr/sbin/csf csf_path = /usr/sbin/csf
# Dodatkowe opcje CSF
[backend_nftables] [backend_nftables]
table_name = filter # Nazwa tabeli i chain dla nftables
table_name = inet
chain_name = logmon_block chain_name = logmon_block
[backend_iptables] [backend_iptables]
# Nazwa chain dla iptables
chain_name = LOGMON_BLOCK chain_name = LOGMON_BLOCK
[backend_ufw] [backend_ufw]
# UFW nie wymaga dodatkowych parametrów # UFW nie wymaga dodatkowych parametrów
# ============================================
# Moduł Postfix - SMTP Server
# ============================================
[module_postfix] [module_postfix]
# Włącz/wyłącz moduł
enabled = true enabled = true
# Ścieżka do logu Postfix
log_file = /var/log/mail.log log_file = /var/log/mail.log
# Alternatywnie dla systemd:
# Alternatywnie dla systemd journald:
# use_journald = true # use_journald = true
# journald_unit = postfix.service # journald_unit = postfix.service
# Parametry detekcji # Maksymalna liczba niepowodzeń przed banem
max_failures = 5 max_failures = 5
# Okno czasowe w sekundach (domyślnie 60s = 1 minuta)
time_window = 60 time_window = 60
# Czas bana w sekundach (domyślnie 86400s = 24 godziny)
ban_duration = 86400 ban_duration = 86400
# Wzorce do wykrywania # Lista wzorców do wykrywania (oddzielone przecinkami)
patterns = auth_failed,sasl_failed patterns = postfix_auth_failed,postfix_sasl_failed
[pattern_auth_failed]
# ============================================
# Moduł Dovecot - IMAP/POP3 Server
# ============================================
[module_dovecot]
# Włącz/wyłącz moduł
enabled = true
# Ścieżka do logu Dovecot
log_file = /var/log/dovecot-info.log
# Maksymalna liczba niepowodzeń przed banem
max_failures = 5
# Okno czasowe w sekundach (domyślnie 120s = 2 minuty)
time_window = 120
# Czas bana w sekundach (domyślnie 86400s = 24 godziny)
ban_duration = 86400
# Ignoruj błędy SSL/TLS (często są to skanery, nie ataki brute-force)
ignore_ssl_errors = true
# Ignoruj połączenia z localhost (127.0.0.1)
ignore_localhost = true
# Lista wzorców do wykrywania
patterns = dovecot_auth_failed,dovecot_auth_failed_multi
# ============================================
# Wzorce dla Postfix
# ============================================
[pattern_postfix_auth_failed]
# Wykrywa: "authentication failed"
regex = authentication failed regex = authentication failed
score = 1 score = 1
[pattern_sasl_failed] [pattern_postfix_sasl_failed]
# Wykrywa: "SASL LOGIN authentication failed" i podobne
regex = SASL [A-Z\-\d]+ authentication failed regex = SASL [A-Z\-\d]+ authentication failed
score = 2 score = 2
# ============================================
# Wzorce dla Dovecot
# ============================================
[pattern_dovecot_auth_failed]
# Wykrywa: "auth failed, 1 attempts"
regex = auth failed, 1 attempts
score = 2
[pattern_dovecot_auth_failed_multi]
# Wykrywa: "auth failed, 2 attempts" lub więcej (2-9+)
regex = auth failed, [2-9]+ attempts
score = 5
# ============================================
# Dodatkowe moduły (przygotowane do rozbudowy)
# ============================================
# [module_ssh]
# enabled = false
# log_file = /var/log/auth.log
# max_failures = 5
# time_window = 300
# ban_duration = 3600
# patterns = ssh_failed_password,ssh_invalid_user
# [pattern_ssh_failed_password]
# regex = Failed password for .+ from
# score = 2
# [pattern_ssh_invalid_user]
# regex = Invalid user .+ from
# score = 3
# [module_nginx]
# enabled = false
# log_file = /var/log/nginx/error.log
# max_failures = 10
# time_window = 60
# ban_duration = 3600
# patterns = nginx_404_flood,nginx_403_scan
# [pattern_nginx_404_flood]
# regex = \[error\].*GET .* HTTP/
# score = 1
# [pattern_nginx_403_scan]
# regex = 403.*GET
# score = 2
# ============================================
# Whitelist IP (przygotowane do implementacji)
# ============================================
# [whitelist]
# # Lista IP które nigdy nie będą banowane (oddzielone przecinkami)
# ips = 127.0.0.1,192.168.1.0/24,10.0.0.0/8
#
# # Lub z pliku:
# # file = /etc/logmon/whitelist.txt
# ============================================
# Zaawansowane opcje
# ============================================
# [advanced]
# # Maksymalna liczba jednocześnie śledzonych IP
# max_tracked_ips = 10000
#
# # Jak często sprawdzać wygasłe bany (w sekundach)
# check_expired_interval = 10
#
# # Persystencja - zapisz stan banów do pliku
# persist_state = true
# persist_file = /var/lib/logmon/state.json

View File

@@ -16,10 +16,12 @@ from collections import defaultdict, deque
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
# Importy z lokalnych modułów
from modules import PostfixModule
from backends import CSFBackend, NFTablesBackend, IPTablesBackend, UFWBackend
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Importy z lokalnych modułów
from modules import PostfixModule, DovecotModule
from backends import CSFBackend, NFTablesBackend, IPTablesBackend, UFWBackend
class LogMonDaemon: class LogMonDaemon:
"""Główny demon LogMon""" """Główny demon LogMon"""
@@ -112,7 +114,15 @@ class LogMonDaemon:
self.logger.info("Loaded Postfix module") self.logger.info("Loaded Postfix module")
except Exception as e: except Exception as e:
self.logger.error(f"Error loading Postfix module: {e}") self.logger.error(f"Error loading Postfix module: {e}")
# Dovecot module
if self.config.getboolean('module_dovecot', 'enabled', fallback=False):
try:
module = DovecotModule(self.config, self)
modules.append(module)
self.logger.info("Loaded Dovecot module")
except Exception as e:
self.logger.error(f"Error loading Dovecot module: {e}")
# Tutaj można dodać więcej modułów w przyszłości # Tutaj można dodać więcej modułów w przyszłości
# if self.config.getboolean('module_ssh', 'enabled', fallback=False): # if self.config.getboolean('module_ssh', 'enabled', fallback=False):
# modules.append(SSHModule(self.config, self)) # modules.append(SSHModule(self.config, self))

View File

@@ -4,5 +4,7 @@ LogMon Modules - Moduły monitorowania różnych aplikacji
from .base import LogModule from .base import LogModule
from .postfix import PostfixModule from .postfix import PostfixModule
from .dovecot import DovecotModule
__all__ = ['LogModule', 'PostfixModule', 'DovecotModule']
__all__ = ['LogModule', 'PostfixModule']

Binary file not shown.

Binary file not shown.

Binary file not shown.

162
modules/dovecot.py Normal file
View File

@@ -0,0 +1,162 @@
"""
Moduł monitorujący Dovecot IMAP/POP3 server
"""
import re
import time
from .base import LogModule
class DovecotModule(LogModule):
"""Moduł monitorujący Dovecot"""
def __init__(self, config, daemon):
super().__init__(config, daemon)
# Kompiluj wzorce regex dla wydajności
self.patterns = self._load_patterns()
# Regex do wyciągania IP z logów Dovecot
# Obsługuje format: rip=IP, lip=IP
self.ip_pattern = re.compile(r'rip=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})')
# Ścieżka do pliku logu
self.log_file = config.get('module_dovecot', 'log_file',
fallback='/var/log/dovecot-info.log')
# Czy ignorować błędy SSL/TLS (często są to skanery, nie ataki brute-force)
self.ignore_ssl_errors = config.getboolean('module_dovecot', 'ignore_ssl_errors',
fallback=True)
# Czy ignorować połączenia z localhost
self.ignore_localhost = config.getboolean('module_dovecot', 'ignore_localhost',
fallback=True)
self.logger.info(f"Loaded {len(self.patterns)} patterns for Dovecot")
if self.ignore_ssl_errors:
self.logger.info("SSL/TLS errors will be ignored")
def _load_patterns(self):
"""Ładuje wzorce z konfiguracji"""
patterns = []
pattern_names = self.config.get('module_dovecot', 'patterns',
fallback='').split(',')
for name in pattern_names:
name = name.strip()
if not name:
continue
section = f'pattern_{name}'
if section not in self.config:
self.logger.warning(f"Pattern section '{section}' not found in config")
continue
try:
regex = self.config.get(section, 'regex')
score = self.config.getint(section, 'score', fallback=1)
patterns.append({
'name': name,
'regex': re.compile(regex, re.IGNORECASE),
'score': score
})
self.logger.debug(f"Loaded pattern '{name}': {regex} (score: {score})")
except Exception as e:
self.logger.error(f"Error loading pattern '{name}': {e}")
return patterns
def _run(self):
"""Główna pętla - tail -f na pliku logu"""
self.logger.info(f"Tailing log file: {self.log_file}")
try:
with open(self.log_file, 'r') as f:
# Przejdź na koniec pliku
f.seek(0, 2)
while self.running:
line = f.readline()
if line:
self.process_line(line.strip())
else:
# Brak nowych linii, czekaj chwilę
time.sleep(0.1)
except FileNotFoundError:
self.logger.error(f"Log file not found: {self.log_file}")
except PermissionError:
self.logger.error(f"Permission denied reading: {self.log_file}")
except Exception as e:
self.logger.error(f"Error tailing log: {e}")
def _is_ssl_error(self, line):
"""Sprawdza czy linia zawiera błąd SSL/TLS"""
ssl_keywords = [
'SSL_accept() failed',
'TLS handshaking',
'unsupported protocol',
'version too low',
'no shared cipher',
'wrong version number',
'internal error',
'Connection reset by peer',
'bad key share',
'unknown protocol',
'http request'
]
line_lower = line.lower()
return any(keyword.lower() in line_lower for keyword in ssl_keywords)
def process_line(self, line):
"""
Przetwarza linię z logu Dovecot
Przykłady linii:
- imap-login: Info: Disconnected: Connection closed (auth failed, 1 attempts in 2 secs): user=<user@domain.pl>, method=PLAIN, rip=1.2.3.4, lip=5.6.7.8, TLS
- imap-login: Info: Disconnected: Connection closed: SSL_accept() failed (no auth attempts): user=<>, rip=1.2.3.4
"""
# Ignoruj błędy SSL/TLS jeśli włączone
if self.ignore_ssl_errors and self._is_ssl_error(line):
return
# Wyciągnij IP
ip_match = self.ip_pattern.search(line)
if not ip_match:
return
ip = ip_match.group(1)
# Pomiń localhost jeśli włączone
if self.ignore_localhost and (ip.startswith('127.') or ip == '::1'):
return
# Pomiń lokalne IP
if ip.startswith('192.168.') or ip.startswith('10.') or ip.startswith('172.'):
# Sprawdź czy to nie jest 172.16-31.x.x (prywatne)
if ip.startswith('172.'):
second_octet = int(ip.split('.')[1])
if 16 <= second_octet <= 31:
return
else:
return
# Sprawdź wzorce
for pattern in self.patterns:
if pattern['regex'].search(line):
self.logger.debug(
f"Pattern '{pattern['name']}' matched for IP {ip}"
)
self.logger.debug(f"Line: {line}")
# Zgłoś niepowodzenie do demona
self.daemon.track_failure(ip, pattern['score'])
# Tylko pierwszy pasujący wzorzec
break

0
utils/__init__.py Normal file
View File

View File

@@ -1,5 +0,0 @@
"""
LogMon Utils - Narzędzia pomocnicze
"""
__all__ = []