Files
security-alert-center/backend/app/services/notify_dispatch.py
T

123 lines
5.2 KiB
Python

"""Dispatch ingest notifications per global policy (severity → channels)."""
import logging
from sqlalchemy.orm import Session
from app.models import Event, Problem
from app.services import email_notify, mobile_notify, telegram_notify, webhook_notify
from app.services.notification_cooldown import should_notify_event, should_notify_problem
from app.services.notification_policy import get_effective_notification_policy
from app.services.host_health import HEARTBEAT_TYPE
from app.services.notification_severity import severity_meets_minimum
logger = logging.getLogger(__name__)
LIFECYCLE_EVENT_TYPE = "agent.lifecycle"
DAILY_REPORT_EVENT_TYPES = frozenset({"report.daily.ssh", "report.daily.rdp"})
AUTH_LOGIN_SUCCESS_TYPES = frozenset({"rdp.login.success", "ssh.login.success"})
RDG_CONNECTION_TYPES = frozenset({
"rdg.connection.success",
"rdg.connection.disconnected",
"rdg.connection.failed",
})
def _event_telegram_via_agent(event: Event) -> bool:
details = event.details if isinstance(event.details, dict) else {}
via = str(details.get("telegram_via") or "").strip().lower()
return via == "agent"
def _dispatch_event_channels(event: Event, *, db: Session | None, policy) -> None:
if policy.use_telegram:
telegram_notify.notify_event(event, db=db, apply_policy_gate=False)
if policy.use_webhook:
webhook_notify.notify_event(event, db=db, apply_policy_gate=False)
if policy.use_email:
email_notify.notify_event(event, db=db, apply_policy_gate=False)
if policy.use_mobile:
mobile_notify.notify_event(event, db=db, apply_policy_gate=False)
def _dispatch_problem_channels(problem: Problem, event: Event | None, *, db: Session | None, policy) -> None:
if policy.use_telegram:
telegram_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
if policy.use_webhook:
webhook_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
if policy.use_email:
email_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
if policy.use_mobile:
mobile_notify.notify_problem(problem, event, db=db, apply_policy_gate=False)
def notify_event(event: Event, *, db: Session | None = None) -> None:
# Heartbeat — только для UI/статуса хоста, не для Telegram/email/push.
if event.type == HEARTBEAT_TYPE:
return
policy = get_effective_notification_policy(db)
if not severity_meets_minimum(event.severity, policy.min_severity):
return
if not should_notify_event(event, db):
return
_dispatch_event_channels(event, db=db, policy=policy)
def notify_problem(problem: Problem, event: Event | None = None, *, db: Session | None = None) -> None:
policy = get_effective_notification_policy(db)
if not severity_meets_minimum(problem.severity, policy.min_severity):
return
if not should_notify_problem(problem, db):
return
_dispatch_problem_channels(problem, event, db=db, policy=policy)
def _dispatch_lifecycle_channels(event: Event, *, db: Session | None, policy) -> None:
skip_telegram = _event_telegram_via_agent(event)
if policy.use_telegram and not skip_telegram:
telegram_notify.notify_event(event, db=db, apply_policy_gate=False)
if policy.use_webhook:
webhook_notify.notify_event(event, db=db, apply_policy_gate=False)
if policy.use_email:
email_notify.notify_event(event, db=db, apply_policy_gate=False)
if policy.use_mobile:
mobile_notify.notify_event(event, db=db, apply_policy_gate=False)
def notify_daily_report(event: Event, *, db: Session | None = None) -> None:
"""Оповещение по суточному отчёту (severity=info, вне порога policy).
При UseSAC=dual агент шлёт TG сам (telegram_via=agent) — SAC не дублирует.
"""
policy = get_effective_notification_policy(db)
if not should_notify_event(event, db):
return
_dispatch_lifecycle_channels(event, db=db, policy=policy)
def notify_lifecycle(event: Event, *, db: Session | None = None) -> None:
"""Старт/стоп/reload агента — всегда в каналы SAC (кроме TG, если telegram_via=agent)."""
policy = get_effective_notification_policy(db)
if not should_notify_event(event, db):
return
_dispatch_lifecycle_channels(event, db=db, policy=policy)
def notify_auth_login(event: Event, *, db: Session | None = None) -> None:
"""Успешный удалённый вход RDP/SSH — всегда в Telegram SAC (info вне min_severity).
При UseSAC=dual агент шлёт TG сам (telegram_via=agent) — SAC не дублирует.
"""
policy = get_effective_notification_policy(db)
if not should_notify_event(event, db):
return
_dispatch_lifecycle_channels(event, db=db, policy=policy)
def notify_rdg_connection(event: Event, *, db: Session | None = None) -> None:
"""RD Gateway 302/303 — ingest всегда; Telegram SAC вне min_severity (как auth login)."""
policy = get_effective_notification_policy(db)
if not should_notify_event(event, db):
return
_dispatch_lifecycle_channels(event, db=db, policy=policy)