From 976955fff836f3bd8b2f30e36d37b7e806666532 Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 26 Oct 2026 21:49:11 +0300 Subject: [PATCH] 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. --- console/novncd | 10 +--------- console/tests/test_views.py | 14 ++++++++++---- console/views.py | 7 +++++-- vrtManager/util.py | 9 +++++++++ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/console/novncd b/console/novncd index 93ff728a..7ae4299d 100755 --- a/console/novncd +++ b/console/novncd @@ -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.""" diff --git a/console/tests/test_views.py b/console/tests/test_views.py index f76cce0c..10c08dec 100644 --- a/console/tests/test_views.py +++ b/console/tests/test_views.py @@ -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): diff --git a/console/views.py b/console/views.py index f1c40e4c..007bf093 100644 --- a/console/views.py +++ b/console/views.py @@ -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 diff --git a/vrtManager/util.py b/vrtManager/util.py index 30d7b6d2..db87625b 100644 --- a/vrtManager/util.py +++ b/vrtManager/util.py @@ -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"