From 4252ad92806553743cda5270ab15eaba2ec0ed24 Mon Sep 17 00:00:00 2001 From: catborise Date: Fri, 2 Oct 2026 22:27:39 +0300 Subject: [PATCH] fix(console): keep socketiod behind nginx and off by default there too - Comment out the nginx /socket.io/ location: a firewall on 6081 does not protect a console that nginx proxies on 80/443. - Default SOCKETIO_HOST to 127.0.0.1. - README: short enable steps and the changes existing installs need. - test_socketiod_disabled writes its settings module to a temporary directory instead of the source tree. --- README.md | 13 +++++++------ conf/nginx/webvirtcloud.conf | 15 +++++++++------ console/test_socketiod_disabled.py | 27 +++++++++------------------ webvirtcloud/settings.py.template | 4 +++- 4 files changed, 28 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 52c4fe9f..d82a4d6b 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ python manage.py runserver 0.0.0.0:8000 --settings=webvirtcloud.settings-dev --n Open `http://127.0.0.1:8000`. `settings-dev` enables `DEBUG` and the Django Debug Toolbar. `--nostatic` is required: the CSS/JS live in `static/` (`STATIC_ROOT`), which WhiteNoise serves but runserver's own static handler does not. -For the browser console, start the websocket proxy in a separate terminal: `python console/novncd` (VNC, port 6080). The serial console (`python console/socketiod`, port 6081) is disabled by default; see [Serial Console](#serial-console-disabled-by-default). +For the browser console, run `python console/novncd` (VNC, port 6080) in a separate terminal. ## Compute Node (Hypervisor) Setup @@ -381,16 +381,17 @@ datasource: ### Serial Console (disabled by default) -The xterm.js serial console is served by `console/socketiod`, which does **not authenticate connections yet**: anyone who can reach its port (6081) can read and write a serial console that someone has opened. Until this is fixed, the serial console is disabled: +`console/socketiod` does not authenticate connections yet, so the serial (xterm.js) console is disabled. To enable it on a trusted network: -- the console page shows "Serial console is disabled" for VMs with a `pty` console, and -- `socketiod` refuses to start, and is not started by the bundled supervisor/systemd configs. +1. In `webvirtcloud/settings.py`: `SERIAL_CONSOLE_ENABLED = True` and `SOCKETIO_PUBLIC_PORT = 80` (or `443`). +2. Uncomment `location /socket.io/` in the nginx config. +3. Set `autostart=true` for `[program:socketiod]` in the supervisor config, or `systemctl enable --now webvirt-socketiod`. -To enable it anyway on a trusted network, set `SERIAL_CONSOLE_ENABLED = True` in `webvirtcloud/settings.py`, set `autostart=true` for `[program:socketiod]` in the supervisor config (or `systemctl enable --now webvirt-socketiod`), and restrict access to port 6081 with a firewall. Existing installs whose `settings.py` predates this setting are treated as disabled. +Existing installs: set `autostart=false` for `[program:socketiod]`, comment out `location /socket.io/` in nginx, and set `SOCKETIO_HOST = "127.0.0.1"` in `settings.py`. ### Reverse-Proxy & Port Forwarding -If WebVirtCloud runs behind a reverse proxy terminating SSL or forwarding port 80/443, set the public console ports in `webvirtcloud/settings.py` (defaults: 6080 for noVNC, 6081 for Socket.IO). The bundled nginx config already proxies `/novncd/` and `/socket.io/`: +If WebVirtCloud runs behind a reverse proxy terminating SSL or forwarding port 80/443, set the public console ports in `webvirtcloud/settings.py` (defaults: 6080 for noVNC, 6081 for Socket.IO). The bundled nginx config already proxies `/novncd/`: ```python WS_PUBLIC_PORT = 80 # or 443 diff --git a/conf/nginx/webvirtcloud.conf b/conf/nginx/webvirtcloud.conf index d2ca4ccf..0da9a2f3 100644 --- a/conf/nginx/webvirtcloud.conf +++ b/conf/nginx/webvirtcloud.conf @@ -27,12 +27,15 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } - location /socket.io/ { - proxy_pass http://wssocketiod; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } + # Serial console (socketiod). Disabled by default: socketiod does not + # authenticate connections yet. Uncomment only together with + # SERIAL_CONSOLE_ENABLED = True, and only on a trusted network. + #location /socket.io/ { + # proxy_pass http://wssocketiod; + # proxy_http_version 1.1; + # proxy_set_header Upgrade $http_upgrade; + # proxy_set_header Connection "upgrade"; + #} location /websockify { proxy_pass http://wsnovncd; proxy_http_version 1.1; diff --git a/console/test_socketiod_disabled.py b/console/test_socketiod_disabled.py index 6f4fc591..d28619d6 100644 --- a/console/test_socketiod_disabled.py +++ b/console/test_socketiod_disabled.py @@ -2,6 +2,7 @@ import os import signal import subprocess import sys +import tempfile import unittest from pathlib import Path @@ -13,11 +14,10 @@ class SocketiodDisabledTestCase(unittest.TestCase): """ROADMAP S-01: socketiod must refuse to start unless explicitly enabled.""" def test_socketiod_exits_when_serial_console_disabled(self): - settings_dir = ROOT / "console" / "_test_settings" - settings_dir.mkdir(exist_ok=True) - try: - (settings_dir / "__init__.py").write_text("") - (settings_dir / "disabled.py").write_text( + # The settings module lives in a temporary directory, so the test does + # not write into the source tree (read-only checkouts, --parallel). + with tempfile.TemporaryDirectory() as tmp: + (Path(tmp) / "socketiod_disabled_settings.py").write_text( "from webvirtcloud.settings import * # noqa\n" "SERIAL_CONSOLE_ENABLED = False\n" "SOCKETIO_HOST = '127.0.0.1'\n" @@ -25,8 +25,8 @@ class SocketiodDisabledTestCase(unittest.TestCase): ) env = dict( os.environ, - DJANGO_SETTINGS_MODULE="console._test_settings.disabled", - PYTHONPATH=str(ROOT), + DJANGO_SETTINGS_MODULE="socketiod_disabled_settings", + PYTHONPATH=os.pathsep.join([tmp, str(ROOT)]), ) # Other test modules import novncd, which sets SIGCHLD to SIG_IGN in # this process; with that, the child's exit status is lost (always 0). @@ -44,14 +44,5 @@ class SocketiodDisabledTestCase(unittest.TestCase): self.fail("socketiod kept running although SERIAL_CONSOLE_ENABLED=False") finally: signal.signal(signal.SIGCHLD, previous_sigchld) - self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr) - self.assertIn("SERIAL_CONSOLE_ENABLED", result.stderr) - finally: - for f in settings_dir.glob("*"): - if f.is_file(): - f.unlink() - for d in settings_dir.glob("__pycache__"): - for f in d.glob("*"): - f.unlink() - d.rmdir() - settings_dir.rmdir() + self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr) + self.assertIn("SERIAL_CONSOLE_ENABLED", result.stderr) diff --git a/webvirtcloud/settings.py.template b/webvirtcloud/settings.py.template index 5c5b3a61..0e5da068 100644 --- a/webvirtcloud/settings.py.template +++ b/webvirtcloud/settings.py.template @@ -225,7 +225,9 @@ WS_CERT = None SERIAL_CONSOLE_ENABLED = False SOCKETIO_PORT = 6081 -SOCKETIO_HOST = "0.0.0.0" +# Listen on localhost only and reach socketiod through the nginx /socket.io/ +# location (set SOCKETIO_PUBLIC_PORT to 80 or 443 then). +SOCKETIO_HOST = "127.0.0.1" # Socketio public host SOCKETIO_PUBLIC_HOST = None