fix: VNC auth - add generate-password:1 to vncproxy, fallback to ticket

Root cause of 'Connection closed' / VNCAuth failure:
- vncproxy was called without generate-password=1
- vnc_data.password was None → noVNC sent empty DES-encrypted challenge → Proxmox rejected

Fix:
- Add generate-password=1 to both qemu and lxc vncproxy requests
- If Proxmox returns password field (Proxmox 8.x VNCAuth): use it
- If not (Proxmox 9.x NoAuth): fall back to ticket as password
- Add diagnostic logging (password_len, has_generate_password)
This commit is contained in:
markmorado
2026-03-27 21:08:40 +01:00
parent cd48c839f8
commit c189dab5e9

View File

@@ -1498,9 +1498,11 @@ def get_vm_vnc(
csrf_token = auth_data.get("CSRFPreventionToken")
# 2. Создаём VNC proxy с этим же ticket
# generate-password=1: Proxmox 8.x возвращает случайный VNC пароль для VNCAuth;
# в Proxmox 9.x (NoAuth) поле password пустое, но соединение проходит без пароля.
vnc_response = requests.post(
f"https://{server.ip_address}:8006/api2/json/nodes/{node}/qemu/{vmid}/vncproxy",
data={"websocket": 1},
data={"websocket": 1, "generate-password": 1},
headers={
"CSRFPreventionToken": csrf_token
},
@@ -1514,11 +1516,13 @@ def get_vm_vnc(
vnc_data = vnc_response.json().get("data", {})
# Формируем ответ - password это сгенерированный VNC пароль
# password = сгенерированный VNC пароль (Proxmox 8.x) или пустой (Proxmox 9.x NoAuth)
# Если password не вернулся — используем ticket как пароль (совместимость)
vnc_password = vnc_data.get('password') or vnc_data.get('ticket', '')
response_data = {
'port': vnc_data.get('port'),
'ticket': vnc_data.get('ticket'),
'password': vnc_data.get('password'), # Сгенерированный VNC пароль
'password': vnc_password,
'host': server.ip_address,
'node': node,
'vmid': vmid,
@@ -1526,7 +1530,7 @@ def get_vm_vnc(
'auth_ticket': auth_ticket
}
logger.info(f"User {current_user.username} opened VNC console for VM {vmid}")
logger.info(f"User {current_user.username} opened VNC console for VM {vmid} (password_len={len(vnc_password)}, has_generate_password={'password' in vnc_data})")
return JSONResponse(content=response_data)
except HTTPException:
raise
@@ -1582,7 +1586,7 @@ def get_container_vnc(
# 2. Создаём VNC proxy с этим же ticket
vnc_response = requests.post(
f"https://{server.ip_address}:8006/api2/json/nodes/{node}/lxc/{vmid}/vncproxy",
data={"websocket": 1},
data={"websocket": 1, "generate-password": 1},
headers={
"CSRFPreventionToken": csrf_token
},
@@ -1596,11 +1600,11 @@ def get_container_vnc(
vnc_data = vnc_response.json().get("data", {})
# Формируем ответ - password это сгенерированный VNC пароль
vnc_password = vnc_data.get('password') or vnc_data.get('ticket', '')
response_data = {
'port': vnc_data.get('port'),
'ticket': vnc_data.get('ticket'),
'password': vnc_data.get('password'), # Сгенерированный VNC пароль
'password': vnc_password,
'host': server.ip_address,
'node': node,
'vmid': vmid,