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

This commit is contained in:
2026-07-25 02:17:13 +03:00
parent 11133a116d
commit 2e88f5797c
+59 -13
View File
@@ -15,12 +15,16 @@
- vncticket привязан к origin (схема+хост+порт), в котором был
запрошен. Чтобы проксировать через внешний DNS, нужно передать
host=<public_host> в vncproxy.post() (см. proxmox_client.py).
- Proxmox в Location редиректа часто возвращает свой self-reported
origin (например, pve1.input.netcraze.pro — тот, что в сертификате).
Если backend работает внутри сети и должен стучаться на внутренний
IP (192.168.31.4), нужно заменить hostname в Location обратно.
"""
import asyncio
import logging
from typing import Optional, Tuple
from urllib.parse import quote
from urllib.parse import quote, urlparse, urlunparse
import aiohttp
import httpx
@@ -34,6 +38,51 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/console", tags=["console"])
def _rewrite_redirect(location: str, fallback_host: str) -> str:
"""Переписывает URL редиректа: заменяет https→wss и нежелательные хосты.
Args:
location: значение заголовка Location из Proxmox.
fallback_host: хост, на который нужно стучаться по сети
(например, 192.168.31.4:8006). Если в location хост
отличается — он будет заменён.
"""
if not location:
return location
# 1. ws/wss вместо http/https.
if location.startswith("https://"):
location = "wss://" + location[len("https://"):]
elif location.startswith("http://"):
location = "ws://" + location[len("http://"):]
# 2. Если хост в редиректе — не тот, через который мы работаем
# (например, Proxmox отдаёт свой публичный DNS, а backend сидит
# внутри сети), заменяем host:port на fallback_host.
parsed = urlparse(location)
if parsed.hostname:
# Берём host:port из fallback_host (вида "192.168.31.4:8006"
# или "pve1.input.netcraze.pro:8006").
fb_host = fallback_host
if ":" in fb_host and not fb_host.startswith("["):
# IPv6 — оборачиваем в скобки.
pass
if ":" in fb_host:
fb_hostname, _, fb_port = fb_host.partition(":")
else:
fb_hostname, fb_port = fb_host, ""
# Определяем, что Proxmox вернул «чужой» адрес (например,
# свой публичный DNS), а не тот, через который мы работаем.
# Сравниваем по hostname.
if parsed.hostname != fb_hostname:
new_netloc = fb_hostname
if fb_port:
new_netloc = f"{fb_hostname}:{fb_port}"
location = urlunparse(parsed._replace(netloc=new_netloc))
return location
async def _get_pve_auth_cookie() -> Optional[Tuple[str, str]]:
"""Аутентифицируется в Proxmox по логину/паролю через REST API.
@@ -80,16 +129,18 @@ async def console_ws(
await websocket.accept()
guest_path = "qemu" if guest_type == "vm" else "lxc"
# По умолчанию WebSocket идёт на тот же хост, что и PVE_HOST. Если
# vncticket был выписан для внешнего origin (PVE_PUBLIC_HOST), то и
# WebSocket нужно открывать к нему — иначе Proxmox отвергнет тикет.
pve_origin = settings.pve_public_host or settings.pve_host
pve_host_only = pve_origin.replace("http://", "").replace("https://", "")
# Хост, через который backend реально ходит к Proxmox внутри сети.
# Используем PVE_HOST без схемы и порта.
pve_backend_host = settings.pve_host.replace("http://", "").replace("https://", "").split(":")[0]
pve_backend_port = settings.pve_host.replace("http://", "").replace("https://", "").split(":")[-1]
if not pve_backend_port.isdigit():
pve_backend_port = "8006"
fallback_host = f"{pve_backend_host}:{pve_backend_port}"
safe_ticket = quote(ticket, safe="")
safe_port = quote(str(port), safe="")
upstream_url = (
f"wss://{pve_host_only}/api2/json/nodes/{node}/{guest_path}/{vmid}/vncwebsocket"
f"wss://{fallback_host}/api2/json/nodes/{node}/{guest_path}/{vmid}/vncwebsocket"
f"?port={safe_port}&vncticket={safe_ticket}"
)
@@ -131,12 +182,7 @@ async def console_ws(
)
if resp.status in (301, 302, 303, 307, 308):
location = resp.headers.get("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
upstream_url = _rewrite_redirect(location, fallback_host)
logger.info("console_ws: rewritten URL to %s", upstream_url)
await resp.release()
elif resp.status == 101: