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