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

This commit is contained in:
2026-07-25 00:31:27 +03:00
parent 997b74625d
commit 6387292448
+7 -26
View File
@@ -8,11 +8,12 @@
- Cookie PVEAuthCookie — если заданы PVE_USERNAME + PVE_PASSWORD - Cookie PVEAuthCookie — если заданы PVE_USERNAME + PVE_PASSWORD
3. Проксирует бинарный VNC-поток между браузером клиента и Proxmox. 3. Проксирует бинарный VNC-поток между браузером клиента и Proxmox.
Особенности Proxmox: Особенности:
- В Proxmox 8.x+ vncticket может содержать спецсимволы (#, /, %, :), - В Proxmox 8.x+ vncticket может содержать спецсимволы (#, /, %, :),
которые ломают парсинг URL (RFC 3986). Кодируем их через quote(). которые ломают парсинг URL (RFC 3986). Кодируем через quote().
- PVE может вернуть HTTP redirect (302) на `https://...` вместо `wss://...`; - process_redirect в websockets не работает на uvloop (используется
перехватываем и переписываем scheme через process_redirect. uvicorn[standard]), поэтому редиректы не обрабатываем — Proxmox
обычно их и не делает при корректном URL.
""" """
import asyncio import asyncio
@@ -22,7 +23,6 @@ from urllib.parse import quote
import httpx import httpx
import websockets import websockets
from websockets.http11 import Response
from fastapi import APIRouter, Query, WebSocket, WebSocketDisconnect from fastapi import APIRouter, Query, WebSocket, WebSocketDisconnect
from ..config import settings from ..config import settings
@@ -32,23 +32,6 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/console", tags=["console"]) 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]]: async def _get_pve_auth_cookie() -> Optional[Tuple[str, str]]:
"""Аутентифицируется в Proxmox по логину/паролю и возвращает (cookie_header, csrf_token). """Аутентифицируется в Proxmox по логину/паролю и возвращает (cookie_header, csrf_token).
@@ -94,9 +77,8 @@ async def console_ws(
guest_path = "qemu" if guest_type == "vm" else "lxc" guest_path = "qemu" if guest_type == "vm" else "lxc"
pve_host_only = settings.pve_host.replace("http://", "").replace("https://", "") pve_host_only = settings.pve_host.replace("http://", "").replace("https://", "")
# quote() экранирует спецсимволы в тикете и порте по правилам RFC 3986, # quote() экранирует спецсимволы в тикете и порте по правилам RFC 3986.
# не затрагивая уже экранированные символы. Без этого в Proxmox 8.x+ # Без этого в Proxmox 8.x+ vncticket может содержать # / % : которые ломают URL.
# vncticket может содержать # / % : которые ломают URL.
safe_ticket = quote(ticket, safe="") safe_ticket = quote(ticket, safe="")
safe_port = quote(str(port), safe="") safe_port = quote(str(port), safe="")
upstream_url = ( upstream_url = (
@@ -129,7 +111,6 @@ async def console_ws(
extra_headers=headers, extra_headers=headers,
subprotocols=["binary"], subprotocols=["binary"],
ssl=None if settings.pve_verify_ssl else False, ssl=None if settings.pve_verify_ssl else False,
process_redirect=_process_redirect,
) as upstream: ) as upstream:
logger.info("console_ws: upstream connected") logger.info("console_ws: upstream connected")