Обновление файла
This commit is contained in:
@@ -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")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user