is_staff does not reveal console settings or VNC passwords (fixed on
fix/wave0-staff-console-password), but staff owners with is_change may
also change VMs marked as templates. Superuser-only pages answer 403 to
everyone else, and the console page answers 403 both for a missing VM and
for a VM the user may not open.
Describe the access model: superuser, the global permissions
(view_instances as a read-only auditor role, clone_instances,
snapshot_instances, passwordless_console, change_password) and per-VM
ownership flags (is_change, is_delete, is_vnc), with a table of which
VM actions each combination allows.
The table describes the state after all Wave 0 branches are merged:
disk/media views are superuser-only (fix/wave0-disk-tenant-isolation) and
clone requires is_change except for templates (fix/wave0-clone-permission).
novncd accepted signed tokens without the user part, which skipped the
per-user access check, and accepted unsigned "host-uuid" tokens whenever
"test" appeared in sys.argv. The console view always signs
host:uuid:user_id, so anything else is now rejected with PermissionError,
and the user check always runs.
Add webvirtcloud/test_authorization_matrix.py, which walks every named URL
and checks that a user without any VM/compute relation only reaches an
explicit allowlist, and that the global view_instances permission does not
open consoles. It found:
- /computes/<id>/... views without any check (compute_graph,
get_compute_*, get_dom_capabilities, storages.get_volumes): the whole
computes URL namespace is now wrapped with superuser_only, so a view
added there later cannot be exposed by a missing decorator.
- guess_mac_address (leaks dhcpd.conf host/MAC pairs), random_mac_address
and guess_clone_name: superuser-only, like the buttons that use them.
- check_instance: cross-tenant VM name lookup with no caller; removed.
- getvvfile and get_vdi_url: reachable with only view_instances, and the
.vv file contains the VNC password.
Console access now follows one rule, instances.utils.can_open_console:
an active superuser or an active owner of the VM. The global
view_instances permission is read-only. It is used by the console page,
novncd, getvvfile and get_vdi_url (getvvfile and the VDI tab are kept for
virt-viewer users). The console page answers 403 instead of 500 when
access is denied, with the same answer for a missing VM.
clone only required "view" access, so a user with clone_instances and the
global view_instances permission could clone anyone's VM and read its
disks. It also copied every POST field into clone_data, which let the
client override the server-set disk_owner_uid/gid, and disk-<dev> values
went unvalidated into volume XML and os.path.join.
- Require change permission on the source VM, except for templates,
which any user who can view them may clone.
- Build clone_data from an allowlist: name, title and description for
everyone; disk names and MAC addresses only from superusers.
- For non-superusers, derive disk names (get_clone_disk_name) and MAC
addresses (DHCP or random) on the server; their form only sent the
source VM's own values, which could never work.
- Validate every MAC address (not only the first) and every disk name;
a missing MAC is an error message instead of a KeyError.
Several views returned json.dumps() output through HttpResponse, i.e. as
text/html, and guess_mac_address reflects the URL's vname into it
(reflected XSS). Templates filled the log table and volume dropdowns by
concatenating server data into HTML strings, and log messages contain
user-entered snapshot/disk/clone names (stored XSS that runs when a
superuser opens the Logs tab).
- Use JsonResponse in guess_mac_address, random_mac_address,
guess_clone_name, sshkeys, storages.get_volumes,
datasource.os_metadata_json and logs.vm_logs; sshkeys?plain=true is
served as text/plain.
- stats_tab.html builds log rows with .text(); instance.html and
create_instance_w2.html build <option> elements with .val().text().
socketiod does not validate the console token, keeps a single global PTY
and broadcasts its output to every connected client, so anyone who can
reach port 6081 can read and write a serial console that someone opened.
It is also broken for legitimate users, since console/views.py now writes
signed tokens that socketiod cannot parse.
Until it is rewritten, turn the serial console off by default:
- Add SERIAL_CONSOLE_ENABLED = False to the settings template; installs
whose settings.py lacks it are treated as disabled.
- The console view shows an error instead of the xterm page for pty
consoles while the setting is off.
- socketiod refuses to start unless SERIAL_CONSOLE_ENABLED is True.
- Supervisor: autostart=false for socketiod; drop -d (debug) from the
supervisor and systemd definitions; fix the systemd unit description;
conf/systemd/install.sh no longer suggests enabling socketiod.
- README: stop opening port 6081 and document how to enable the serial
console on a trusted network.
Disk and media mutation views (add_new_vol, add_existing_vol, edit_volume,
delete_vol, detach_vol, add_cdrom, detach_cdrom, mount_iso, unmount_iso)
were reachable by any owner with is_change, although the UI only shows the
Disk tab to superusers. An ordinary user could delete any volume on the
compute or attach arbitrary host files to their VM.
- Require superuser for all disk/media views, matching the UI.
- delete_vol resolves the volume from the VM's own disk XML by dev and
ignores the posted storage/name.
- Validate bus/cache/io/discard/detect_zeroes against the hypervisor's
lists, and format/serial/volume name against strict patterns.
- add_existing_vol only accepts volumes that exist in the selected pool.
- edit_volume only accepts a dev attached to the VM.
- vrtManager: escape all values interpolated into disk XML and pass dev
as an XPath variable in detach_disk/edit_disk.
- Rewrite purpose/features intro around libvirt as single source of truth
- Generate SECRET_KEY into data/secret_key instead of editing settings.py
- Consolidate local development setup into one flow using
--system-site-packages, settings-dev and runserver --nostatic
- Document the generated admin password instead of admin/admin
- Use the shared supervisor config on RHEL (adds missing socketiod)
and add supervisor setup on openSUSE
- Document SOCKETIO_PUBLIC_PORT and CSRF_TRUSTED_ORIGINS for proxies
- Point test setup at the dev virtualenv
runserver imports the WSGI application from a worker thread, where
signal.signal() raises ValueError and the dev server fails to start.
Gunicorn imports it from the worker's main thread, so production
keeps reaping child processes as before.
- Computes API: mark password as write_only in ComputeSerializer, restrict ComputeViewSet and architectures to IsAdminUser.
- Instances API: enforce user ownership tenant isolation in InstanceViewSet.list, restrict MigrateViewSet and CreateInstanceViewSet to IsAdminUser, require IsAdminUser for mutating FlavorViewSet.
- Storage, Network, and Interface APIs: restrict StorageViewSet, VolumeViewSet, NetworkViewSet, InterfaceViewSet to IsAdminUser.
- AppSettings & NWFilters: protect appsettings and nwfilter views with @superuser_only (403 Forbidden for non-superusers).
- noVNC Console: sign tokens with TimestampSigner (salt='console.novnc', max_age=600), verify signature and user authorization in novncd with backward-compatible fallback for debug/test mode.
- Accounts: avoid hardcoded default admin password; generate cryptographically secure random password unless ADMIN_PASSWORD is set (preserve 'admin' in test runner).
- gstfsd daemon: bind to 127.0.0.1 by default instead of 0.0.0.0.
- Tests: added comprehensive test coverage for API security, token expiration/rejection, and view permissions (132 tests passing).
- Upgraded Django to 4.2.30 (latest Django 4.x LTS release)
- Upgraded production dependencies in conf/requirements.txt:
* django_bootstrap5 to 26.2 (latest supporting Django 4.2)
* django-otp to 1.7.3
* django-auth-ldap to 5.3.0
* djangorestframework to 3.17.2 (latest supporting Django 4.2)
* drf-nested-routers to 0.95.3
* drf-spectacular[sidecar] to 0.30.0
* eventlet to 0.41.2
* gunicorn to 26.2.0
* markdown to 3.10 (supporting Python 3.10+)
* paramiko to 5.0.0
* python-engineio to 4.14.0
* python-socketio to 5.17.0
* whitenoise to 6.12.0
* zipp to 4.1.0
- Upgraded development dependencies in dev/requirements.txt:
* coverage to 7.16.1
* django-debug-toolbar to 6.3.0 (latest supporting Django 4.2)
* pycodestyle to 2.15.0
* pyflakes to 4.0.0
* pylint to 4.0.9
* black to 26.5.1
- Replace hardcoded colors with Bootstrap & Bootswatch CSS custom properties
- Adapt background, card, borders, and inputs dynamically to active light and dark themes
- Inherit primary color, border-radius, and font-family from active theme
- Use native Bootswatch alert and button styles for consistent visual hierarchy
- Overhaul signin.css with responsive dark glassmorphic card and ambient lighting
- Add server virtualization branding badge and typography hierarchy
- Enhance input fields with leading Bootstrap icons and interactive password visibility toggle
- Modernize 2FA/OTP login and signed-out pages to match design language
- Add real-time loading feedback on form submission
- Remove obsolete spice-html5 static assets and SPICE console templates
- Restrict default console types to VNC with migration 0012
- Filter out SPICE in VM creation form and backend capabilities
- Return friendly deprecation notice if legacy VM is opened with SPICE
- Clean up docs, architecture references, and libvirt-bootstrap scripts
- Validate and coerce console query parameters (view_only, scale, resize_session, clip_viewport) to strict booleans via to_bool in console/views.py
- Sanitize view parameter to strictly 'lite' or 'full'
- Apply escapejs filter to all JS string interpolations in console-vnc-full.html, console-vnc-lite.html, console-spice-lite.html, and console-xterm.html
- Replace unsafe '| safe' filter in console-spice-lite.html with escapejs
- Add unit tests for XSS query param mitigation and escapejs character encoding
- Upgrade vendored noVNC library in static/js/novnc/ from 1.2.0 to 1.7.0
- Remove obsolete IE11 polyfills (promise.js, browser-es-module-loader)
- Preserve custom app/styles/lite.css for embedded lite view
- Update console-vnc-full.html stylesheets, modern icon set, and dialogs
- Modernize console-vnc-full.html startup via UI.start() with Django context mapping
- Dynamically resolve package.json and localization dictionaries via import.meta.url
- Prevent baking SECRET_KEY into Docker image during collectstatic
- Persist runtime SECRET_KEY in data/secret_key volume or read from environment
- Remove sed-based injection of CSRF_TRUSTED_ORIGINS in favor of native os.getenv
- Avoid overwriting system python3 symlink on SUSE; auto-detect python 3.10+ binaries
- Make /etc/hosts entry in installer idempotent
- Add security notice regarding compute node VNC/SPICE port firewall isolation
- Add python3-libvirt to apt and configure --system-site-packages for zero-compilation build
- Introduce 10_webvirtcloud_init.sh to handle container startup, migrations, and SQLite persistence
- Exclude host settings.py in .dockerignore for clean builds from settings.py.template
- Add docker-compose.yml with persistent data and SSH volume mounts
- Document Docker Compose deployment in README.md
- Configure matrix testing across Python 3.10, 3.11, and 3.12 on ubuntu-latest
- Install system libvirt, ldap, and C build dependencies with pip cache
- Run Django database migrations and test suite
- Run vrtManager unit test discovery with test_*.py pattern
- Fix vrtManager unittest discovery command in README.md