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

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 - API-токен (заголовок `Authorization: PVEAPIToken=...`) — новые PVE ≥ 7.x
- Cookie PVEAuthCookie — если заданы PVE_USERNAME + PVE_PASSWORD - Cookie PVEAuthCookie — если заданы PVE_USERNAME + PVE_PASSWORD
3. Проксирует бинарный VNC-поток между браузером клиента и Proxmox. 3. Проксирует бинарный VNC-поток между браузером клиента и Proxmox.
Особенности Proxmox:
- PVE может вернуть HTTP redirect (302) на `https://...` вместо `wss://...`
при WebSocket-handshake. Библиотека `websockets` не умеет следовать за
такими redirect (https ≠ ws/wss), поэтому перехватываем redirect
и переписываем scheme.
""" """
import asyncio import asyncio
@@ -15,6 +21,8 @@ from typing import Optional, Tuple
import httpx import httpx
import websockets import websockets
from websockets.datastructures import Headers
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
@@ -24,6 +32,23 @@ 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).
@@ -76,14 +101,14 @@ async def console_ws(
# Определяем способ аутентификации. # Определяем способ аутентификации.
# Cookie-вариант приоритетнее, т.к. старые/строгие PVE не принимают API-токен на WS. # Cookie-вариант приоритетнее, т.к. старые/строгие PVE не принимают API-токен на WS.
headers: dict = {} headers: list = []
auth = await _get_pve_auth_cookie() auth = await _get_pve_auth_cookie()
if auth: if auth:
cookie_header, _ = auth cookie_header, _ = auth
headers["Cookie"] = cookie_header headers.append(("Cookie", cookie_header))
auth_kind = "cookie" auth_kind = "cookie"
else: 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" auth_kind = "api-token"
logger.info( logger.info(
@@ -94,9 +119,10 @@ async def console_ws(
try: try:
async with websockets.connect( async with websockets.connect(
upstream_url, upstream_url,
extra_headers=headers, additional_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")