From 554e3a7995ac3e057022871193571b65fba4ff20 Mon Sep 17 00:00:00 2001 From: catborise Date: Mon, 26 Oct 2026 21:17:20 +0300 Subject: [PATCH] fix(console): a VM with a QEMU websocket port opens through novncd A VM defined with QEMU's own VNC websocket (graphics websocket='...', which the panel never sets) made the console page connect to that port on the panel's host name: the port is on the compute, so the console did not open unless the panel ran on that compute, and then it bypassed novncd and the panel's console token. Such a VM has its ordinary VNC port too, and novncd now reaches it there like any other VM. --- console/templates/console-vnc-full.html | 5 ++--- console/templates/console-vnc-lite.html | 5 ++--- console/tests/test_views.py | 17 ++--------------- console/views.py | 2 -- vrtManager/instance.py | 8 -------- 5 files changed, 6 insertions(+), 31 deletions(-) diff --git a/console/templates/console-vnc-full.html b/console/templates/console-vnc-full.html index f69113a6..44a8979a 100755 --- a/console/templates/console-vnc-full.html +++ b/console/templates/console-vnc-full.html @@ -66,8 +66,7 @@ bindSendKeys(() => UI.rfb); - // novncd behind the panel's own address (nginx's /novncd/); a VM with a - // QEMU websocket port is reached on that port of the panel's host name + // novncd behind the panel's own address (nginx's /novncd/) let fullPath = 'novncd/'; const token = '{{ token|escapejs }}'; if (token) { @@ -87,7 +86,7 @@ const https = window.location.protocol === 'https:'; const mandatory = { host: window.location.hostname, - port: Number('{{ console_websocket_port|default_if_none:""|escapejs }}' || window.location.port || (https ? 443 : 80)), + port: Number(window.location.port || (https ? 443 : 80)), path: fullPath, encrypt: https, }; diff --git a/console/templates/console-vnc-lite.html b/console/templates/console-vnc-lite.html index 8147311a..7da37b2a 100755 --- a/console/templates/console-vnc-lite.html +++ b/console/templates/console-vnc-lite.html @@ -149,10 +149,9 @@ bindSendKeys(() => rfb); // novncd behind the panel's own address (nginx's /novncd/), never a host - // from the link: the page sends it a console token. A VM with a QEMU - // websocket port is reached on that port of the panel's host name. + // from the link: the page sends it a console token const host = window.location.hostname; - let port = '{{ console_websocket_port|default_if_none:""|escapejs }}' || window.location.port; + let port = window.location.port; {% if perms.instances.passwordless_console and console_passwd %} const password = '{{ console_passwd|escapejs }}'; diff --git a/console/tests/test_views.py b/console/tests/test_views.py index 41741bf9..02e3b506 100644 --- a/console/tests/test_views.py +++ b/console/tests/test_views.py @@ -75,7 +75,6 @@ class ConsoleViewsTestCase(TestCase): def test_console_superuser_vnc_lite(self, mock_wvm): mock_conn = MagicMock() mock_conn.get_console_type.return_value = "vnc" - mock_conn.get_console_websocket_port.return_value = None mock_conn.get_console_passwd.return_value = "vncsecret" mock_wvm.return_value = mock_conn @@ -96,7 +95,6 @@ class ConsoleViewsTestCase(TestCase): def test_console_superuser_vnc_full(self, mock_wvm): mock_conn = MagicMock() mock_conn.get_console_type.return_value = "vnc" - mock_conn.get_console_websocket_port.return_value = 5900 mock_wvm.return_value = mock_conn self.client.force_login(self.admin_user) @@ -105,12 +103,12 @@ class ConsoleViewsTestCase(TestCase): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "console-vnc-full.html") - self.assertEqual(response.context["console_websocket_port"], 5900) + # a VM with a QEMU websocket port goes through novncd too, on its VNC port + self.assertContains(response, "port: Number(window.location.port") @patch("console.views.wvmInstance") def test_the_lite_console_switches_scaling_from_its_menu(self, mock_wvm): mock_wvm.return_value.get_console_type.return_value = "vnc" - mock_wvm.return_value.get_console_websocket_port.return_value = None self.client.force_login(self.admin_user) for query, pressed in (("", "false"), ("&scale=true", "true")): with self.subTest(query=query): @@ -126,7 +124,6 @@ class ConsoleViewsTestCase(TestCase): # Only VNC is supported; a VM defined elsewhere may use another type. mock_conn = MagicMock() mock_conn.get_console_type.return_value = "rdp" - mock_conn.get_console_websocket_port.return_value = None mock_wvm.return_value = mock_conn self.client.force_login(self.admin_user) @@ -173,7 +170,6 @@ class ConsoleViewsTestCase(TestCase): def test_console_user_assigned_instance_access(self, mock_wvm): mock_conn = MagicMock() mock_conn.get_console_type.return_value = "vnc" - mock_conn.get_console_websocket_port.return_value = None mock_wvm.return_value = mock_conn self.client.force_login(self.alice) @@ -189,7 +185,6 @@ class ConsoleViewsTestCase(TestCase): # (letters and digits only: escapejs keeps them as they are in the lite view) mock_conn = MagicMock() mock_conn.get_console_type.return_value = "vnc" - mock_conn.get_console_websocket_port.return_value = None mock_conn.get_console_passwd.return_value = "vncpw7f3a" mock_wvm.return_value = mock_conn permission = Permission.objects.get(codename="passwordless_console") @@ -217,7 +212,6 @@ class ConsoleViewsTestCase(TestCase): def test_console_lite_uses_the_signed_token(self, mock_wvm): mock_conn = MagicMock() mock_conn.get_console_type.return_value = "vnc" - mock_conn.get_console_websocket_port.return_value = None mock_wvm.return_value = mock_conn self.client.force_login(self.admin_user) @@ -241,7 +235,6 @@ class ConsoleViewsTestCase(TestCase): def test_the_lite_console_connects_only_to_the_servers_proxy(self, mock_wvm): # a link naming another host would send it the signed console token mock_wvm.return_value.get_console_type.return_value = "vnc" - mock_wvm.return_value.get_console_websocket_port.return_value = None self.client.force_login(self.alice) response = self.client.get(self.url + "?host=attacker.example&port=443&path=x") self.assertNotContains(response, "attacker.example") @@ -253,7 +246,6 @@ class ConsoleViewsTestCase(TestCase): def test_the_page_sets_no_token_cookie(self, mock_wvm): # one cookie for every tab would name the last console opened mock_wvm.return_value.get_console_type.return_value = "vnc" - mock_wvm.return_value.get_console_websocket_port.return_value = None self.client.force_login(self.admin_user) response = self.client.get(self.url) self.assertNotIn("token", response.cookies) @@ -264,7 +256,6 @@ class ConsoleViewsTestCase(TestCase): # is for superusers and VM owners only. mock_conn = MagicMock() mock_conn.get_console_type.return_value = "vnc" - mock_conn.get_console_websocket_port.return_value = None mock_wvm.return_value = mock_conn perm = Permission.objects.get(codename="view_instances") @@ -291,7 +282,6 @@ class ConsoleViewsTestCase(TestCase): # nginx proxies /novncd/ on the panel's own scheme, host and port: no # console address to configure, HTTPS gives wss mock_wvm.return_value.get_console_type.return_value = "vnc" - mock_wvm.return_value.get_console_websocket_port.return_value = None self.client.force_login(self.admin_user) lite = self.client.get(self.url + "?view=lite") self.assertContains(lite, "const host = window.location.hostname;") @@ -304,7 +294,6 @@ class ConsoleViewsTestCase(TestCase): def test_console_xss_protection_query_parameters(self, mock_wvm): mock_conn = MagicMock() mock_conn.get_console_type.return_value = "vnc" - mock_conn.get_console_websocket_port.return_value = None mock_wvm.return_value = mock_conn self.client.force_login(self.admin_user) @@ -329,7 +318,6 @@ class ConsoleViewsTestCase(TestCase): def test_console_password_escapejs_escaping(self, mock_wvm): mock_conn = MagicMock() mock_conn.get_console_type.return_value = "vnc" - mock_conn.get_console_websocket_port.return_value = None mock_conn.get_console_passwd.return_value = "O'Brian