"""Tests for RDG 303 → workstation rdp.login.success correlation.""" import uuid from datetime import datetime, timedelta, timezone import pytest from app.config import get_settings from app.models import Host from app.services.event_summary import event_to_summary from app.services.host_sessions import event_session_terminated from app.services.ingest import ingest_event from app.services.rdg_workstation_session import ( SESSION_CLOSED_BY_RDG_AT_KEY, SESSION_CLOSED_BY_RDG_EVENT_ID_KEY, close_workstation_session_for_rdg_end, find_rdg_end_after_workstation_login, resolve_workstation_login_closed, ) from tests.test_ingest import VALID_EVENT def _payload(**overrides): base = { **VALID_EVENT, "event_id": str(uuid.uuid4()), "occurred_at": datetime.now(timezone.utc).isoformat(), } base.update(overrides) return base def _ingest(db, occurred_at: datetime, **overrides): payload = _payload(**overrides) payload["occurred_at"] = occurred_at.isoformat() event, _ = ingest_event(db, payload) db.flush() return event @pytest.fixture def rdg_settings(monkeypatch): monkeypatch.setenv("SAC_RDG_FLAP_WINDOW_MIN_SEC", "1") monkeypatch.setenv("SAC_RDG_FLAP_WINDOW_MAX_SEC", "10") monkeypatch.setenv("SAC_RDG_FLAP_DEDUP_SEC", "30") get_settings.cache_clear() yield get_settings.cache_clear() @pytest.fixture def rdg_hosts(db_session): ws = Host( hostname="TSA-PC", os_family="windows", product="rdp-login-monitor", ipv4="192.168.163.100", ) gw = Host( hostname="K6A-DC3", os_family="windows", product="rdp-login-monitor", ipv4="192.168.160.40", ) db_session.add_all([ws, gw]) db_session.commit() return ws, gw def test_rdg_end_marks_workstation_login_closed_on_ingest(db_session, rdg_settings, rdg_hosts): ws, _gw = rdg_hosts t0 = datetime.now(timezone.utc) user = r"B26\TSA" internal_ip = ws.ipv4 login = _ingest( db_session, t0 + timedelta(seconds=1), host={"hostname": ws.hostname, "os_family": "windows", "ipv4": internal_ip}, source={"product": "rdp-login-monitor", "product_version": "2.1.8-SAC"}, type="rdp.login.success", category="auth", severity="info", title="RDP login", summary="1149", details={"user": user}, ) end = _ingest( db_session, t0 + timedelta(hours=2), host={"hostname": "K6A-DC3", "os_family": "windows"}, source={"product": "rdp-login-monitor", "product_version": "2.1.8-SAC"}, type="rdg.connection.disconnected", category="auth", severity="info", title="RD Gateway event 303", summary="303", details={"user": user, "internal_ip": internal_ip}, ) assert login.details[SESSION_CLOSED_BY_RDG_AT_KEY] == end.occurred_at.isoformat() assert login.details[SESSION_CLOSED_BY_RDG_EVENT_ID_KEY] == end.id assert event_session_terminated(login, db=db_session) is True summary = event_to_summary(login, db_session) assert summary.session_terminated is True def test_rdg_flap_does_not_close_workstation_login(db_session, rdg_settings, rdg_hosts): ws, _gw = rdg_hosts t0 = datetime.now(timezone.utc) user = r"B26\TSA" internal_ip = ws.ipv4 details = {"user": user, "internal_ip": internal_ip} login = _ingest( db_session, t0 + timedelta(seconds=1), host={"hostname": ws.hostname, "os_family": "windows", "ipv4": internal_ip}, source={"product": "rdp-login-monitor", "product_version": "2.1.8-SAC"}, type="rdp.login.success", category="auth", severity="info", title="RDP login", summary="1149", details={"user": user}, ) _ingest( db_session, t0, host={"hostname": "K6A-DC3", "os_family": "windows"}, source={"product": "rdp-login-monitor", "product_version": "2.1.8-SAC"}, type="rdg.connection.success", category="auth", severity="info", title="302", summary="302", details=details, ) _ingest( db_session, t0 + timedelta(seconds=4), host={"hostname": "K6A-DC3", "os_family": "windows"}, source={"product": "rdp-login-monitor", "product_version": "2.1.8-SAC"}, type="rdg.connection.disconnected", category="auth", severity="info", title="303", summary="303", details=details, ) db_session.refresh(login) assert SESSION_CLOSED_BY_RDG_AT_KEY not in (login.details or {}) assert event_session_terminated(login, db=db_session) is False assert event_to_summary(login, db_session).session_terminated is False def test_runtime_resolve_for_historical_login_without_flag(db_session, rdg_settings, rdg_hosts): ws, gw = rdg_hosts t0 = datetime.now(timezone.utc) user = r"B26\TSA" internal_ip = ws.ipv4 from app.models import Event login = Event( event_id=str(uuid.uuid4()), host_id=ws.id, occurred_at=t0 + timedelta(seconds=1), received_at=t0, category="auth", type="rdp.login.success", severity="info", title="RDP login", summary="1149", payload={}, details={"user": "TSA"}, ) end = Event( event_id=str(uuid.uuid4()), host_id=gw.id, occurred_at=t0 + timedelta(hours=2), received_at=t0, category="auth", type="rdg.connection.disconnected", severity="info", title="303", summary="303", payload={}, details={"user": user, "internal_ip": internal_ip}, ) db_session.add_all([login, end]) db_session.commit() assert resolve_workstation_login_closed(db_session, login) is True assert find_rdg_end_after_workstation_login(db_session, login) is not None assert event_to_summary(login, db_session).session_terminated is True def test_user_mismatch_does_not_close_login(db_session, rdg_settings, rdg_hosts): ws, _gw = rdg_hosts t0 = datetime.now(timezone.utc) internal_ip = ws.ipv4 login = _ingest( db_session, t0, host={"hostname": ws.hostname, "os_family": "windows", "ipv4": internal_ip}, source={"product": "rdp-login-monitor", "product_version": "2.1.8-SAC"}, type="rdp.login.success", category="auth", severity="info", title="RDP login", summary="1149", details={"user": r"B26\Alice"}, ) end = _ingest( db_session, t0 + timedelta(hours=1), host={"hostname": "K6A-DC3", "os_family": "windows"}, source={"product": "rdp-login-monitor", "product_version": "2.1.8-SAC"}, type="rdg.connection.disconnected", category="auth", severity="info", title="303", summary="303", details={"user": r"B26\Bob", "internal_ip": internal_ip}, ) db_session.refresh(login) assert close_workstation_session_for_rdg_end(db_session, end) is None assert event_session_terminated(login, db=db_session) is False def test_ip_mismatch_does_not_close_login(db_session, rdg_settings, rdg_hosts): ws, _gw = rdg_hosts t0 = datetime.now(timezone.utc) user = r"B26\TSA" login = _ingest( db_session, t0, host={"hostname": ws.hostname, "os_family": "windows", "ipv4": ws.ipv4}, source={"product": "rdp-login-monitor", "product_version": "2.1.8-SAC"}, type="rdp.login.success", category="auth", severity="info", title="RDP login", summary="1149", details={"user": user}, ) end = _ingest( db_session, t0 + timedelta(hours=1), host={"hostname": "K6A-DC3", "os_family": "windows"}, source={"product": "rdp-login-monitor", "product_version": "2.1.8-SAC"}, type="rdg.connection.disconnected", category="auth", severity="info", title="303", summary="303", details={"user": user, "internal_ip": "192.168.163.200"}, ) db_session.refresh(login) assert close_workstation_session_for_rdg_end(db_session, end) is None assert event_session_terminated(login, db=db_session) is False def test_empty_rcm1149_user_enriched_from_prior_rdg302(db_session, rdg_settings, rdg_hosts): """COMM-PC class: EventLog 1149 has empty Param1; RDG 302 already has the account.""" ws, _gw = rdg_hosts t0 = datetime.now(timezone.utc) user = r"B26\s.shelkovaya" internal_ip = ws.ipv4 _ingest( db_session, t0, host={"hostname": "K6A-DC3", "os_family": "windows"}, source={"product": "rdp-login-monitor", "product_version": "2.1.13-SAC"}, type="rdg.connection.success", category="auth", severity="warning", title="RDG 302", summary="302", details={"user": user, "internal_ip": internal_ip}, ) login = _ingest( db_session, t0 + timedelta(seconds=3), host={"hostname": ws.hostname, "os_family": "windows", "ipv4": internal_ip}, source={"product": "rdp-login-monitor", "product_version": "2.1.13-SAC"}, type="rdp.login.success", category="auth", severity="warning", title="RDP connection (RCM 1149)", summary="RCM 1149 - 192.168.160.40", details={"user": "-", "ip_address": "192.168.160.40", "event_id_windows": 1149}, ) db_session.refresh(login) assert login.details["user"] == user assert login.details.get("user_enriched_from_rdg_event_id") summary = event_to_summary(login, db_session) assert summary.actor_user == user def test_empty_rcm1149_backfilled_when_rdg302_arrives_later(db_session, rdg_settings, rdg_hosts): ws, _gw = rdg_hosts t0 = datetime.now(timezone.utc) user = r"B26\s.shelkovaya" internal_ip = ws.ipv4 login = _ingest( db_session, t0 + timedelta(seconds=1), host={"hostname": ws.hostname, "os_family": "windows", "ipv4": internal_ip}, source={"product": "rdp-login-monitor", "product_version": "2.1.13-SAC"}, type="rdp.login.success", category="auth", severity="info", title="RDP connection (RCM 1149)", summary="RCM 1149 - 1.2.3.4", details={"user": "-", "event_id_windows": 1149}, ) assert login.details["user"] == "-" _ingest( db_session, t0, host={"hostname": "K6A-DC3", "os_family": "windows"}, source={"product": "rdp-login-monitor", "product_version": "2.1.13-SAC"}, type="rdg.connection.success", category="auth", severity="info", title="RDG 302", summary="302", details={"user": user, "internal_ip": internal_ip}, ) db_session.refresh(login) assert login.details["user"] == user def test_empty_user_login_still_closed_by_rdg303(db_session, rdg_settings, rdg_hosts): ws, _gw = rdg_hosts t0 = datetime.now(timezone.utc) user = r"B26\s.shelkovaya" internal_ip = ws.ipv4 login = _ingest( db_session, t0, host={"hostname": ws.hostname, "os_family": "windows", "ipv4": internal_ip}, source={"product": "rdp-login-monitor", "product_version": "2.1.13-SAC"}, type="rdp.login.success", category="auth", severity="info", title="RDP connection (RCM 1149)", summary="RCM 1149", details={"user": "-", "event_id_windows": 1149}, ) end = _ingest( db_session, t0 + timedelta(minutes=5), host={"hostname": "K6A-DC3", "os_family": "windows"}, source={"product": "rdp-login-monitor", "product_version": "2.1.13-SAC"}, type="rdg.connection.disconnected", category="auth", severity="info", title="303", summary="303", details={"user": user, "internal_ip": internal_ip}, ) db_session.refresh(login) assert login.details.get(SESSION_CLOSED_BY_RDG_AT_KEY) == end.occurred_at.isoformat() assert event_session_terminated(login, db=db_session) is True