1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 03:36:48 +00:00

fix(console): the console page judges a TCP/TLS console by the running VM

The page's "listens only on its host" check read the VM's next-start
definition, while novncd connects to the running VM's listener: after a
listen address change on a running VM, the page refused a console novncd
could reach, or let through one it could not. The page now reads the
running VM's listener, as novncd does, with the same loopback test, which
moves to vrtManager.util.
This commit is contained in:
catborise 2026-10-26 21:49:11 +03:00
parent 1d2de7404b
commit 976955fff8
4 changed files with 25 additions and 15 deletions

View file

@ -4,7 +4,6 @@
# through an SSH tunnel or a local Unix socket.
import argparse
import ipaddress
import logging
import os
import socket
@ -23,6 +22,7 @@ from websockify import ProxyRequestHandler, WebSocketProxy # noqa: E402
from console.sshtunnels import open_tunnel, tunnel_target # noqa: E402
from vrtManager.connection import CONN_SOCKET, CONN_SSH # noqa: E402
from vrtManager.util import is_loopback # noqa: E402
from webvirtcloud.settings import WS_HOST, WS_PORT # noqa: E402
@ -77,14 +77,6 @@ def get_connection_infos(token):
WILDCARD = (None, "", "0.0.0.0", "::")
def is_loopback(address):
"""127.0.0.0/8, ::1 or localhost: reachable only on the compute itself."""
try:
return ipaddress.ip_address(address).is_loopback
except ValueError:
return address == "localhost"
class ConsoleClient:
"""What a console websocket is joined to; ConsoleProxyHandler is the
websockify handler that runs it."""

View file

@ -270,12 +270,18 @@ class ConsoleViewsTestCase(TestCase):
@patch("console.views.wvmInstance")
def test_a_tcp_hosts_loopback_console_says_why_it_cannot_open(self, mock_wvm):
# the running VM's listener decides, as for novncd, not its next-start settings
Compute.objects.filter(pk=self.compute.pk).update(type=1)
mock_wvm.return_value.get_console_type.return_value = "vnc"
mock_wvm.return_value.console_listens_locally.return_value = True
conn = mock_wvm.return_value
conn.get_console_type.return_value = "vnc"
conn.get_console_socket.return_value = None
conn.console_listens_locally.return_value = False # next start: all interfaces
self.client.force_login(self.admin_user)
response = self.client.get(self.url)
self.assertContains(response, "This console listens only on its host")
for live, refused in (("127.0.0.1", True), ("::1", True), ("0.0.0.0", False), ("192.0.2.40", False)):
with self.subTest(live=live):
conn.get_console_listener_addr.return_value = live
response = self.client.get(self.url)
self.assertEqual("This console listens only on its host" in response.content.decode(), refused)
@patch("console.views.wvmInstance")
def test_both_pages_connect_to_novncd_behind_the_panels_own_address(self, mock_wvm):

View file

@ -11,6 +11,7 @@ from instances.utils import can_open_console
from libvirt import libvirtError
from vrtManager.connection import CONN_TCP, CONN_TLS
from vrtManager.instance import wvmInstance
from vrtManager.util import is_loopback
def to_bool(val, default=False):
@ -57,8 +58,10 @@ def console(request, compute_id, uuid):
console_passwd = conn.get_console_passwd()
running = conn.instance.isActive()
# novncd reaches a console that listens on its host's loopback only
# through SSH (or on this host)
unreachable = instance.compute.type in (CONN_TCP, CONN_TLS) and conn.console_listens_locally()
# through SSH (or on this host); the running VM's listener, as novncd sees it
unreachable = instance.compute.type in (CONN_TCP, CONN_TLS) and bool(
conn.get_console_socket() or is_loopback(conn.get_console_listener_addr())
)
except (libvirtError, ValueError, IndexError, Instance.DoesNotExist):
running = unreachable = False
console_type = None

View file

@ -1,3 +1,4 @@
import ipaddress
import random
import secrets
import re
@ -224,3 +225,11 @@ def vol_driver_type(vol_format, pool_type=None):
if pool_type == "disk" or vol_format in (None, "", "none", "unknown", "iso"):
return "raw"
return vol_format
def is_loopback(address):
"""127.0.0.0/8, ::1 or localhost: reachable only on that host itself."""
try:
return ipaddress.ip_address(address).is_loopback
except ValueError:
return address == "localhost"