Обновление файла
This commit is contained in:
@@ -7,11 +7,12 @@
|
|||||||
- Cookie PVEAuthCookie — если заданы PVE_USERNAME + PVE_PASSWORD
|
- Cookie PVEAuthCookie — если заданы PVE_USERNAME + PVE_PASSWORD
|
||||||
3. Проксирует бинарный VNC-поток между браузером клиента и Proxmox.
|
3. Проксирует бинарный VNC-поток между браузером клиента и Proxmox.
|
||||||
|
|
||||||
Используется aiohttp вместо websockets, потому что:
|
Особенности Proxmox 8.x+:
|
||||||
- Proxmox 8.x+ делает 302 Redirect с Location со схемой https:// вместо wss://,
|
- При WebSocket-handshake на vncwebsocket Proxmox отвечает 302 Redirect
|
||||||
а websockets на uvloop не умеет правильно следовать за такими редиректами
|
на тот же URL, но со схемой https:// вместо wss://. aiohttp при
|
||||||
(raise InvalidURI "scheme isn't ws or wss").
|
перенаправлении теряет Cookie-заголовок (или не может следовать за
|
||||||
- aiohttp корректно обрабатывает HTTP→WS-редиректы с заменой scheme.
|
редиректом без TLS-валидации). Поэтому делаем редирект вручную:
|
||||||
|
перехватываем ответ, переписываем scheme, открываем WS сами.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -76,7 +77,6 @@ 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://", "")
|
||||||
|
|
||||||
# Экранируем спецсимволы в тикете/порте (vncticket может содержать # / % :).
|
|
||||||
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 = (
|
||||||
@@ -84,8 +84,6 @@ async def console_ws(
|
|||||||
f"?port={safe_port}&vncticket={safe_ticket}"
|
f"?port={safe_port}&vncticket={safe_ticket}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Определяем способ аутентификации: cookie приоритетнее (некоторые PVE
|
|
||||||
# не принимают API-токен на WebSocket).
|
|
||||||
headers: dict = {}
|
headers: dict = {}
|
||||||
auth = await _get_pve_auth_cookie()
|
auth = await _get_pve_auth_cookie()
|
||||||
if auth:
|
if auth:
|
||||||
@@ -106,9 +104,38 @@ async def console_ws(
|
|||||||
upstream: Optional[aiohttp.ClientWebSocketResponse] = None
|
upstream: Optional[aiohttp.ClientWebSocketResponse] = None
|
||||||
session: Optional[ClientSession] = None
|
session: Optional[ClientSession] = None
|
||||||
try:
|
try:
|
||||||
# aiohttp сам следует за HTTP-редиректами и при этом корректно
|
|
||||||
# меняет схему с https на wss (что websockets на uvloop не делает).
|
|
||||||
session = ClientSession()
|
session = ClientSession()
|
||||||
|
# Отключаем автоматический redirect, чтобы самим обработать https→wss
|
||||||
|
# с сохранением Cookie-заголовка.
|
||||||
|
resp = await session.get(
|
||||||
|
upstream_url,
|
||||||
|
headers=headers,
|
||||||
|
allow_redirects=False,
|
||||||
|
ssl=False if not settings.pve_verify_ssl else None,
|
||||||
|
)
|
||||||
|
if resp.status in (301, 302, 303, 307, 308):
|
||||||
|
location = resp.headers.get("Location", "")
|
||||||
|
logger.info("console_ws: redirect to %s", location)
|
||||||
|
if location.startswith("https://"):
|
||||||
|
upstream_url = "wss://" + location[len("https://"):]
|
||||||
|
elif location.startswith("http://"):
|
||||||
|
upstream_url = "ws://" + location[len("http://"):]
|
||||||
|
else:
|
||||||
|
upstream_url = location
|
||||||
|
await resp.release()
|
||||||
|
elif resp.status == 101:
|
||||||
|
# Proxmox уже сделал WS-handshake (редкий случай без редиректа).
|
||||||
|
await resp.release()
|
||||||
|
else:
|
||||||
|
await resp.release()
|
||||||
|
raise aiohttp.ClientResponseError(
|
||||||
|
request_info=resp.request_info,
|
||||||
|
history=resp.history,
|
||||||
|
status=resp.status,
|
||||||
|
message=f"unexpected status {resp.status}",
|
||||||
|
headers=resp.headers,
|
||||||
|
)
|
||||||
|
|
||||||
upstream = await session.ws_connect(
|
upstream = await session.ws_connect(
|
||||||
upstream_url,
|
upstream_url,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
|
|||||||
Reference in New Issue
Block a user