feat: close rdp.login.success on rdp.session.logoff (v0.5.12)
Ingest correlates direct RDP logoff with open login events on the same host and user. Terminate-session treats missing qwinsta as already logged off.
This commit is contained in:
@@ -84,6 +84,10 @@ def _event_login_user(event: Event) -> str:
|
||||
|
||||
|
||||
def event_session_terminated(event: Event, db: Session | None = None) -> bool:
|
||||
from app.services.rdp_session_logoff import (
|
||||
event_closed_by_logoff,
|
||||
resolve_workstation_login_closed_by_logoff,
|
||||
)
|
||||
from app.services.rdg_workstation_session import (
|
||||
event_closed_by_rdg,
|
||||
resolve_workstation_login_closed,
|
||||
@@ -97,7 +101,11 @@ def event_session_terminated(event: Event, db: Session | None = None) -> bool:
|
||||
return True
|
||||
if event_closed_by_rdg(event):
|
||||
return True
|
||||
if event_closed_by_logoff(event):
|
||||
return True
|
||||
if db is not None and event.type == "rdp.login.success":
|
||||
if resolve_workstation_login_closed_by_logoff(db, event):
|
||||
return True
|
||||
return resolve_workstation_login_closed(db, event)
|
||||
return False
|
||||
|
||||
@@ -376,6 +384,31 @@ def terminate_linux_session(
|
||||
return last
|
||||
|
||||
|
||||
def _normalize_sam_account(user: str) -> str:
|
||||
text = (user or "").strip()
|
||||
if "\\" in text:
|
||||
return text.split("\\")[-1].strip().casefold()
|
||||
if "@" in text:
|
||||
return text.split("@")[0].strip().casefold()
|
||||
return text.casefold()
|
||||
|
||||
|
||||
def windows_user_matches_session(login_user: str, session_user: str) -> bool:
|
||||
login = _normalize_sam_account(login_user)
|
||||
session = _normalize_sam_account(session_user)
|
||||
return bool(login and session and login == session)
|
||||
|
||||
|
||||
def filter_windows_sessions_for_user(
|
||||
sessions: list[HostSessionRow],
|
||||
login_user: str,
|
||||
) -> list[HostSessionRow]:
|
||||
user = (login_user or "").strip()
|
||||
if not user:
|
||||
return sessions
|
||||
return [s for s in sessions if windows_user_matches_session(user, s.user)]
|
||||
|
||||
|
||||
def parse_qwinsta_sessions(stdout: str, *, filter_user: str | None = None) -> list[HostSessionRow]:
|
||||
rows: list[HostSessionRow] = []
|
||||
norm_filter = (filter_user or "").strip().lower()
|
||||
@@ -475,11 +508,17 @@ def terminate_session_for_event(
|
||||
sessions, qwinsta = list_windows_sessions(host, win_cfg)
|
||||
if not qwinsta or not qwinsta.ok:
|
||||
raise ValueError(qwinsta.message if qwinsta else "qwinsta failed")
|
||||
matched = [s for s in sessions if user.lower() in s.user.lower()] if user else sessions
|
||||
matched = filter_windows_sessions_for_user(sessions, user) if user else sessions
|
||||
if len(matched) == 1:
|
||||
sid = matched[0].session_id
|
||||
elif not matched:
|
||||
raise ValueError("No matching Windows session for user")
|
||||
# Пользователь уже вышел из RDP — qwinsta пуст, событие входа в SAC ещё «открыто».
|
||||
return WinRmCmdResult(
|
||||
ok=True,
|
||||
message="На хосте нет активной сессии пользователя (уже вышел из RDP)",
|
||||
target=qwinsta.target,
|
||||
stdout=qwinsta.stdout,
|
||||
)
|
||||
else:
|
||||
raise ValueError("Multiple sessions; specify session_id")
|
||||
result = terminate_windows_session(host, win_cfg, sid)
|
||||
|
||||
Reference in New Issue
Block a user