Обновление файла

This commit is contained in:
2026-07-24 23:54:18 +03:00
parent d113133140
commit 06817d1bcc
+30 -4
View File
@@ -7,6 +7,12 @@
- API-токен (заголовок `Authorization: PVEAPIToken=...`) — новые PVE ≥ 7.x
- Cookie PVEAuthCookie — если заданы PVE_USERNAME + PVE_PASSWORD
3. Проксирует бинарный VNC-поток между браузером клиента и Proxmox.
Особенности Proxmox:
- PVE может вернуть HTTP redirect (302) на `https://...` вместо `wss://...`
при WebSocket-handshake. Библиотека `websockets` не умеет следовать за
такими redirect (https ≠ ws/wss), поэтому перехватываем redirect
и переписываем scheme.
"""
import asyncio
@@ -15,6 +21,8 @@ from typing import Optional, Tuple
import httpx
import websockets
from websockets.datastructures import Headers
from websockets.http11 import Response
from fastapi import APIRouter, Query, WebSocket, WebSocketDisconnect
from ..config import settings
@@ -24,6 +32,23 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/console", tags=["console"])
def _process_redirect(request, response: Response) -> Optional[str]:
"""Перехватывает HTTP-redirect от Proxmox и переписывает https://→wss://.
Proxmox иногда возвращает Location со схемой https:// (хотя для WebSocket
положено wss://). Библиотека websockets в этом случае падает с
InvalidURI("scheme isn't ws or wss"). Возвращаем исправленный URL.
"""
location = response.headers.get("Location", "")
if not location:
return None
if location.startswith("https://"):
return "wss://" + location[len("https://"):]
if location.startswith("http://"):
return "ws://" + location[len("http://"):]
return location
async def _get_pve_auth_cookie() -> Optional[Tuple[str, str]]:
"""Аутентифицируется в Proxmox по логину/паролю и возвращает (cookie_header, csrf_token).
@@ -76,14 +101,14 @@ async def console_ws(
# Определяем способ аутентификации.
# Cookie-вариант приоритетнее, т.к. старые/строгие PVE не принимают API-токен на WS.
headers: dict = {}
headers: list = []
auth = await _get_pve_auth_cookie()
if auth:
cookie_header, _ = auth
headers["Cookie"] = cookie_header
headers.append(("Cookie", cookie_header))
auth_kind = "cookie"
else:
headers["Authorization"] = f"PVEAPIToken={settings.pve_token_name}={settings.pve_token_value}"
headers.append(("Authorization", f"PVEAPIToken={settings.pve_token_name}={settings.pve_token_value}"))
auth_kind = "api-token"
logger.info(
@@ -94,9 +119,10 @@ async def console_ws(
try:
async with websockets.connect(
upstream_url,
extra_headers=headers,
additional_headers=headers,
subprotocols=["binary"],
ssl=None if settings.pve_verify_ssl else False,
process_redirect=_process_redirect,
) as upstream:
logger.info("console_ws: upstream connected")