Update notification service

This commit is contained in:
MacRimi
2026-03-06 19:32:10 +01:00
parent f0e3d7d09a
commit a064a7471e
4 changed files with 697 additions and 126 deletions

View File

@@ -4,6 +4,7 @@ Provides REST API endpoints for authentication management
"""
import logging
import logging.handlers
import os
import subprocess
import threading
@@ -13,13 +14,25 @@ import auth_manager
import jwt
import datetime
# Dedicated logger for auth failures (Fail2Ban reads this)
# Dedicated logger for auth failures (Fail2Ban reads this file)
auth_logger = logging.getLogger("proxmenux-auth")
_auth_handler = logging.FileHandler("/var/log/proxmenux-auth.log")
_auth_handler.setFormatter(logging.Formatter("%(asctime)s proxmenux-auth: %(message)s"))
auth_logger.addHandler(_auth_handler)
auth_logger.setLevel(logging.WARNING)
# Handler 1: File for Fail2Ban
_auth_file_handler = logging.FileHandler("/var/log/proxmenux-auth.log")
_auth_file_handler.setFormatter(logging.Formatter("%(asctime)s proxmenux-auth: %(message)s"))
auth_logger.addHandler(_auth_file_handler)
# Handler 2: Syslog for JournalWatcher notifications
# This sends to the systemd journal so notification_events.py can detect auth failures
try:
_auth_syslog_handler = logging.handlers.SysLogHandler(address='/dev/log', facility=logging.handlers.SysLogHandler.LOG_AUTH)
_auth_syslog_handler.setFormatter(logging.Formatter("proxmenux-auth: %(message)s"))
_auth_syslog_handler.ident = "proxmenux-auth"
auth_logger.addHandler(_auth_syslog_handler)
except Exception:
pass # Syslog may not be available in all environments
def _get_client_ip():
"""Get the real client IP, supporting reverse proxies (X-Forwarded-For, X-Real-IP)"""

View File

@@ -246,10 +246,6 @@ class JournalWatcher:
syslog_id = entry.get('SYSLOG_IDENTIFIER', '')
priority = int(entry.get('PRIORITY', 6))
# Debug: log auth-related messages
if 'auth' in msg.lower() or 'password' in msg.lower():
print(f"[v0] JournalWatcher received auth message: syslog_id={syslog_id}, msg={msg[:80]}")
self._check_auth_failure(msg, syslog_id, entry)
self._check_fail2ban(msg, syslog_id)
self._check_kernel_critical(msg, syslog_id, priority)
@@ -279,15 +275,10 @@ class JournalWatcher:
(r'pvedaemon\[.*authentication failure.*rhost=(\S+)', 'pve'),
]
# Debug: check if message contains auth failure
if 'authentication failure' in msg.lower() or 'failed password' in msg.lower():
print(f"[v0] _check_auth_failure processing: {msg[:100]}")
for pattern, service in patterns:
match = re.search(pattern, msg, re.IGNORECASE)
if match:
groups = match.groups()
print(f"[v0] Auth pattern matched: service={service}, groups={groups}")
if service == 'ssh':
username, source_ip = groups[0], groups[1]
elif service == 'pam':
@@ -295,8 +286,6 @@ class JournalWatcher:
else:
source_ip = groups[0]
username = 'unknown'
print(f"[v0] Emitting auth_fail: ip={source_ip}, user={username}, service={service}")
self._emit('auth_fail', 'WARNING', {
'source_ip': source_ip,
'username': username,
@@ -1139,7 +1128,6 @@ class JournalWatcher:
now = time.time()
last = self._recent_events.get(event.fingerprint, 0)
if now - last < self._dedup_window:
print(f"[v0] _emit SKIPPED (dedup): {event_type} fingerprint={event.fingerprint[:20]}")
return # Skip duplicate within 30s window
self._recent_events[event.fingerprint] = now
@@ -1151,7 +1139,6 @@ class JournalWatcher:
k: v for k, v in self._recent_events.items() if v > cutoff
}
print(f"[v0] _emit QUEUED: {event_type} to queue (queue size: {self._queue.qsize()})")
self._queue.put(event)

View File

@@ -655,29 +655,19 @@ class NotificationManager:
event_group = template.get('group', 'other')
default_event_enabled = 'true' if template.get('default_enabled', True) else 'false'
print(f"[v0] _dispatch_to_channels called: event_type={event_type}, group={event_group}, channels={list(channels.keys())}")
for ch_name, channel in channels.items():
# ── Per-channel category check ──
# Default: category enabled (true) unless explicitly disabled.
ch_group_key = f'{ch_name}.events.{event_group}'
ch_group_val = self._config.get(ch_group_key, 'true')
print(f"[v0] Channel {ch_name}: {ch_group_key}={ch_group_val}")
if ch_group_val == 'false':
print(f"[v0] Channel {ch_name}: SKIPPED - category {event_group} disabled")
if self._config.get(ch_group_key, 'true') == 'false':
continue # Channel has this category disabled
# ── Per-channel event check ──
# Default: from template default_enabled, unless explicitly set.
ch_event_key = f'{ch_name}.event.{event_type}'
ch_event_val = self._config.get(ch_event_key, default_event_enabled)
print(f"[v0] Channel {ch_name}: {ch_event_key}={ch_event_val} (default={default_event_enabled})")
if ch_event_val == 'false':
print(f"[v0] Channel {ch_name}: SKIPPED - event {event_type} disabled")
if self._config.get(ch_event_key, default_event_enabled) == 'false':
continue # Channel has this specific event disabled
print(f"[v0] Channel {ch_name}: SENDING notification for {event_type}")
try:
# Per-channel emoji enrichment (opt-in via {channel}.rich_format)
ch_title, ch_body = title, body