feat: RDG qwinsta/logoff via WinRM on client workstation (0.11.6)

SAC resolves internal_ip to a registered Windows host and runs qwinsta/logoff synchronously over WinRM instead of queueing commands to the gateway agent.
This commit is contained in:
PTah
2026-06-20 15:36:12 +10:00
parent 2eb06acb5b
commit 75f4c475df
13 changed files with 612 additions and 174 deletions
+9 -55
View File
@@ -1,17 +1,14 @@
"""Queue and resolve agent commands (qwinsta, logoff)."""
"""Queue and resolve agent commands (legacy poll path + helpers)."""
from __future__ import annotations
import uuid
from datetime import datetime, timezone
from fastapi import HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.config import get_settings
from app.models import AgentCommand, Event, Host
from app.services.rdg_session_flap import event_has_rdg_flap
from app.models import AgentCommand, Host
from app.services.win_admin_settings import get_effective_win_admin_config
@@ -47,56 +44,13 @@ def command_to_dict(cmd: AgentCommand, *, include_run_as: bool = False) -> dict:
return out
def queue_qwinsta(db: Session, event: Event, *, requested_by: str) -> AgentCommand:
if not event_has_rdg_flap(event):
raise HTTPException(status_code=400, detail="Event is not flagged as RDG session flap")
if not _win_admin_configured():
raise HTTPException(
status_code=503,
detail="Windows domain admin is not configured (Settings or SAC_WIN_ADMIN_*)",
)
details = event.details if isinstance(event.details, dict) else {}
user = details.get("user")
cmd = AgentCommand(
command_uuid=str(uuid.uuid4()),
host_id=event.host_id,
event_id=event.id,
command_type="qwinsta",
params={"user": user} if user else {},
status="pending",
requested_by=requested_by,
)
db.add(cmd)
db.flush()
return cmd
def queue_logoff(
db: Session,
event: Event,
*,
session_id: int,
requested_by: str,
) -> AgentCommand:
if not event_has_rdg_flap(event):
raise HTTPException(status_code=400, detail="Event is not flagged as RDG session flap")
if not _win_admin_configured():
raise HTTPException(
status_code=503,
detail="Windows domain admin is not configured (Settings or SAC_WIN_ADMIN_*)",
)
cmd = AgentCommand(
command_uuid=str(uuid.uuid4()),
host_id=event.host_id,
event_id=event.id,
command_type="logoff",
params={"session_id": session_id},
status="pending",
requested_by=requested_by,
)
db.add(cmd)
db.flush()
return cmd
def command_response_fields(cmd: AgentCommand) -> dict:
params = cmd.params if isinstance(cmd.params, dict) else {}
return {
"target": params.get("winrm_target") or params.get("client_hostname"),
"client_hostname": params.get("client_hostname"),
"internal_ip": params.get("internal_ip"),
}
def get_command_for_host_poll(db: Session, host: Host, *, limit: int = 5) -> list[AgentCommand]: