mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-04-25 08:56:21 +00:00
Update notification service
This commit is contained in:
@@ -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)"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user