connectiopn fix round 2 3
This commit is contained in:
@ -56,7 +56,9 @@ async def _reject(websocket: WebSocket, code: int, reason: str, *, accepted: boo
|
||||
await websocket.close(code=code, reason=reason_bytes.decode("utf-8", errors="ignore"))
|
||||
|
||||
|
||||
async def _pump_ssh_to_ws(process: asyncssh.SSHClientProcess, websocket: WebSocket, recorder: SessionRecorder):
|
||||
async def _pump_ssh_to_ws(
|
||||
process: asyncssh.SSHClientProcess, websocket: WebSocket, recorder: SessionRecorder, session_id: int
|
||||
):
|
||||
try:
|
||||
while True:
|
||||
data = await process.stdout.read(65536)
|
||||
@ -65,6 +67,11 @@ async def _pump_ssh_to_ws(process: asyncssh.SSHClientProcess, websocket: WebSock
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf-8", errors="replace")
|
||||
recorder.record("output", base64.b64encode(data).decode())
|
||||
# Live-Mitschau (GET /ws/sessions/{id}/watch, siehe unten): jeder
|
||||
# Chunk geht zusaetzlich an alle aktuell zuschauenden Superadmins.
|
||||
# Best-effort/read-only -- ein Beobachter kann diese Sitzung nicht
|
||||
# beeinflussen, und sein Fehlen/Trennen hat keinen Einfluss hierauf.
|
||||
active_sessions.broadcast(session_id, data)
|
||||
await websocket.send_json({"type": "output", "data": base64.b64encode(data).decode()})
|
||||
except (asyncssh.Error, ConnectionResetError):
|
||||
pass
|
||||
@ -133,7 +140,7 @@ async def ssh_terminal(websocket: WebSocket, host_id: int):
|
||||
ssh_conn = await connect_to_host(conn, host_id)
|
||||
logger.debug("SSH-Sitzung %s: Verbindung zu %s hergestellt", session_id, host["hostname"])
|
||||
process = await ssh_conn.create_process(term_type="xterm-256color")
|
||||
pump_task = asyncio.create_task(_pump_ssh_to_ws(process, websocket, recorder))
|
||||
pump_task = asyncio.create_task(_pump_ssh_to_ws(process, websocket, recorder, session_id))
|
||||
|
||||
while True:
|
||||
try:
|
||||
@ -237,3 +244,60 @@ async def ssh_terminal(websocket: WebSocket, host_id: int):
|
||||
await websocket.close()
|
||||
except Exception:
|
||||
logger.debug("WebSocket war beim Schliessen bereits getrennt", exc_info=True)
|
||||
|
||||
|
||||
@router.websocket("/ws/sessions/{session_id}/watch")
|
||||
async def watch_ssh_session(websocket: WebSocket, session_id: int):
|
||||
"""Live-Mitschau einer laufenden SSH-Sitzung fuer Superadmins ('Aktive
|
||||
Session mitschauen', Sessionview im Adminbereich).
|
||||
|
||||
Rein lesend: der Beobachter bekommt exakt dieselben "output"-Frames wie
|
||||
der Sitzungsinhaber selbst (siehe _pump_ssh_to_ws()/active_sessions.
|
||||
broadcast()), kann aber keine Eingaben in die fremde Sitzung schicken --
|
||||
dafuer gibt es hier bewusst keinen "input"-Pfad. Eigener Top-Level-Router
|
||||
unter /ws/... (siehe app/admin/log_ws.py fuer die ausfuehrliche
|
||||
Begruendung: der nginx-Reverse-Proxy setzt WebSocket-Upgrade-Header nur
|
||||
fuer /ws/-Pfade). Nur auf demselben Server-Prozess moeglich wie
|
||||
'Beenden' -- active_sessions.add_watcher() gibt None zurueck, wenn die
|
||||
Sitzung hier nicht (mehr) laeuft."""
|
||||
user = await get_current_user_ws(websocket)
|
||||
if user is None or not user.is_admin:
|
||||
await websocket.close(code=4403)
|
||||
return
|
||||
|
||||
conn = get_db()
|
||||
row = await (await conn.execute(
|
||||
"SELECT protocol, ended_at FROM sessions WHERE id = ?", (session_id,)
|
||||
)).fetchone()
|
||||
if row is None or row[0] != "ssh" or row[1] is not None:
|
||||
await websocket.close(code=4404)
|
||||
return
|
||||
|
||||
queue = active_sessions.add_watcher(session_id)
|
||||
if queue is None:
|
||||
# Sitzung existiert, laeuft aber nicht (mehr) auf diesem Worker-
|
||||
# Prozess -- analog zum 'killable'-Feld bei 'Beenden'.
|
||||
await websocket.close(code=4409)
|
||||
return
|
||||
|
||||
await websocket.accept()
|
||||
client_ip = websocket.client.host if websocket.client else "unknown"
|
||||
await write_audit_event(
|
||||
conn, event_type="session_watch_started", user_id=user.id, client_ip=client_ip,
|
||||
details={"session_id": session_id},
|
||||
)
|
||||
await conn.commit()
|
||||
logger.debug("Superadmin %s beobachtet SSH-Sitzung %s live", user.username, session_id)
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = await queue.get()
|
||||
await websocket.send_json({"type": "output", "data": base64.b64encode(data).decode()})
|
||||
except WebSocketDisconnect:
|
||||
pass
|
||||
finally:
|
||||
active_sessions.remove_watcher(session_id, queue)
|
||||
try:
|
||||
await websocket.close()
|
||||
except Exception:
|
||||
logger.debug("Watch-WebSocket war beim Schliessen bereits getrennt", exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user