feat: RDG session flap rule, qwinsta/logoff queue and dashboard UI (0.9.13)

Detect 302→303 within 1–10s, Problem with 30s dedup, rdg_flap flag.
Agent command poll API, admin qwinsta/logoff from Overview. Migration 016.
This commit is contained in:
PTah
2026-06-19 23:34:01 +10:00
parent b328d32f97
commit 2aec488226
24 changed files with 1012 additions and 25 deletions
+2 -2
View File
@@ -4,6 +4,6 @@ from app.version import APP_NAME, APP_VERSION, APP_VERSION_LABEL
def test_version_constants():
assert APP_VERSION == "0.9.12"
assert APP_VERSION == "0.9.13"
assert APP_NAME == "Security Alert Center"
assert APP_VERSION_LABEL == "Security Alert Center v.0.9.12"
assert APP_VERSION_LABEL == "Security Alert Center v.0.9.13"
+158
View File
@@ -0,0 +1,158 @@
"""Tests for RDG 302→303 session flap rule."""
import uuid
from datetime import datetime, timedelta, timezone
import pytest
from app.config import get_settings
from app.models import Problem
from app.services.ingest import ingest_event
from app.services.problems import maybe_create_problem
from app.services.problem_rules import RULE_RDG_SESSION_FLAP
from app.services.rdg_session_flap import (
evaluate_rdg_session_flap,
event_has_rdg_flap,
find_rdg_success_before_end,
)
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()
def test_rdg_flap_detects_302_then_303(db_session, rdg_settings):
t0 = datetime.now(timezone.utc)
user = "B26\\test.user"
details = {"user": user, "internal_ip": "192.168.163.32"}
_ingest(
db_session,
t0,
type="rdg.connection.success",
category="auth",
severity="info",
title="RD Gateway event 302",
summary="302",
details=details,
)
end = _ingest(
db_session,
t0 + timedelta(seconds=4),
type="rdg.connection.disconnected",
category="auth",
severity="info",
title="RD Gateway event 303",
summary="303",
details=details,
)
match = evaluate_rdg_session_flap(db_session, end)
assert match is not None
assert match.rule_id == RULE_RDG_SESSION_FLAP
assert event_has_rdg_flap(end) is True
problem, created = maybe_create_problem(db_session, end)
assert created is True
assert problem.rule_id == RULE_RDG_SESSION_FLAP
def test_rdg_flap_ignores_gap_over_10_sec(db_session, rdg_settings):
t0 = datetime.now(timezone.utc)
user = "B26\\slow.user"
details = {"user": user}
_ingest(
db_session,
t0,
type="rdg.connection.success",
category="auth",
severity="info",
title="302",
summary="302",
details=details,
)
end = _ingest(
db_session,
t0 + timedelta(seconds=15),
type="rdg.connection.disconnected",
category="auth",
severity="info",
title="303",
summary="303",
details=details,
)
assert find_rdg_success_before_end(db_session, end) is None
assert evaluate_rdg_session_flap(db_session, end) is None
def test_rdg_flap_dedup_within_30_sec(db_session, rdg_settings):
t0 = datetime.now(timezone.utc)
user = "B26\\dup.user"
details = {"user": user}
def pair(at: datetime):
_ingest(
db_session,
at,
type="rdg.connection.success",
category="auth",
severity="info",
title="302",
summary="302",
details=details,
)
return _ingest(
db_session,
at + timedelta(seconds=5),
type="rdg.connection.disconnected",
category="auth",
severity="info",
title="303",
summary="303",
details=details,
)
end1 = pair(t0)
maybe_create_problem(db_session, end1)
db_session.flush()
end2 = pair(t0 + timedelta(seconds=10))
maybe_create_problem(db_session, end2)
db_session.flush()
from sqlalchemy import func, select
open_count = db_session.scalar(
select(func.count()).select_from(Problem).where(
Problem.rule_id == RULE_RDG_SESSION_FLAP,
Problem.status == "open",
)
)
assert open_count == 1