diff --git a/backend/app/api/v1/events.py b/backend/app/api/v1/events.py index b331978..eb9d9cc 100644 --- a/backend/app/api/v1/events.py +++ b/backend/app/api/v1/events.py @@ -18,15 +18,16 @@ from app.services.ingest import ingest_event from app.services.problems import maybe_create_problem from app.services.schema_validate import validate_event_payload from app.services.notify_dispatch import ( + AUTH_LOGIN_SUCCESS_TYPES, + DAILY_REPORT_EVENT_TYPES, LIFECYCLE_EVENT_TYPE, + notify_auth_login, notify_daily_report, notify_event, notify_lifecycle, notify_problem, ) -DAILY_REPORT_EVENT_TYPES = frozenset({"report.daily.ssh", "report.daily.rdp"}) - router = APIRouter(prefix="/events", tags=["events"]) logger = logging.getLogger("sac.ingest") @@ -76,6 +77,8 @@ def post_event( notify_daily_report(event, db=db) elif event.type == LIFECYCLE_EVENT_TYPE: notify_lifecycle(event, db=db) + elif event.type in AUTH_LOGIN_SUCCESS_TYPES: + notify_auth_login(event, db=db) else: notify_event(event, db=db) if problem is not None and problem_created: diff --git a/backend/app/services/notify_dispatch.py b/backend/app/services/notify_dispatch.py index 817f235..05cf25b 100644 --- a/backend/app/services/notify_dispatch.py +++ b/backend/app/services/notify_dispatch.py @@ -13,6 +13,8 @@ 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"}) def _event_telegram_via_agent(event: Event) -> bool: @@ -81,3 +83,14 @@ def notify_lifecycle(event: Event, *, db: Session | None = None) -> None: 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) diff --git a/backend/tests/test_notify_dispatch.py b/backend/tests/test_notify_dispatch.py index f0bc571..c33e52b 100644 --- a/backend/tests/test_notify_dispatch.py +++ b/backend/tests/test_notify_dispatch.py @@ -89,6 +89,62 @@ def test_notify_event_skipped_by_cooldown(): mock_tg.notify_event.assert_not_called() +def test_notify_auth_login_bypasses_min_severity(): + event = Event( + event_id="00000000-0000-4000-8000-000000000601", + host_id=1, + occurred_at=datetime(2026, 5, 31, 18, 36, tzinfo=timezone.utc), + category="auth", + type="rdp.login.success", + severity="info", + title="RDP login event 4624", + summary="papatramp from 192.168.160.3", + details={"telegram_via": "sac"}, + payload={}, + ) + policy = NotificationPolicyConfig( + min_severity="warning", + use_telegram=True, + use_webhook=False, + use_email=False, + source="db", + ) + with patch.object(notify_dispatch, "get_effective_notification_policy", return_value=policy): + with patch.object(notify_dispatch, "should_notify_event", return_value=True): + with patch.object(notify_dispatch, "telegram_notify") as mock_tg: + notify_dispatch.notify_auth_login(event) + mock_tg.notify_event.assert_called_once() + + +def test_notify_auth_login_skips_telegram_when_via_agent(): + event = Event( + event_id="00000000-0000-4000-8000-000000000602", + host_id=1, + occurred_at=datetime(2026, 5, 31, 18, 36, tzinfo=timezone.utc), + category="auth", + type="ssh.login.success", + severity="info", + title="SSH login", + summary="user from 10.0.0.1", + details={"telegram_via": "agent"}, + payload={}, + ) + policy = NotificationPolicyConfig( + min_severity="warning", + use_telegram=True, + use_webhook=True, + use_email=False, + source="db", + ) + with patch.object(notify_dispatch, "get_effective_notification_policy", return_value=policy): + with patch.object(notify_dispatch, "should_notify_event", return_value=True): + with patch.object(notify_dispatch, "telegram_notify") as mock_tg: + with patch.object(notify_dispatch, "webhook_notify") as mock_wh: + notify_dispatch.notify_auth_login(event) + mock_tg.notify_event.assert_not_called() + mock_wh.notify_event.assert_called_once() + + def test_notify_lifecycle_bypasses_min_severity(): event = Event( event_id="00000000-0000-4000-8000-000000000501",