feat: close workstation login on RDG 303 disconnect (0.4.6)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 08:16:06 +10:00
parent fd1be5e7e4
commit 606bf53019
8 changed files with 448 additions and 6 deletions
@@ -0,0 +1,268 @@
"""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