1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
Commit graph

474 commits

Author SHA1 Message Date
catborise
cdbff38223 fix(instances): the create page offers the UEFI loader of the chosen machine
create_instance passed the loader list where find_uefi_path_for_arch
expects the machine type. The domain capabilities were then read for
the default machine (pc) instead of the chosen one: on a RHEL host a q35
VM was offered /usr/share/OVMF/OVMF_CODE.secboot.fd, which q35 does not
list, instead of /usr/share/edk2/ovmf/OVMF_CODE.fd. The machine type is
passed now.

The page also read dom_caps["loader_enums"] into an unused variable;
on a host without loader support the key is missing and the page failed
with a KeyError. The line is removed.

A unit test covers the call and the host without loader support (it
fails on the previous code). A live test creates a q35 VM with the
offered UEFI firmware, checks that libvirt lists that loader for q35 and
starts the VM; it fails on the previous code.
2026-10-04 17:22:54 +03:00
catborise
16e1a334d0 fix(instances): disk and agent channel changes on paused VMs
attach_disk (new or existing volume, CD-ROM), add_guest_agent and
remove_guest_agent acted only when the status was running (1) or shut
off (5). On a paused VM they changed neither definition and the request
reported success. They now use the same rule as the NIC edits: the
running definition while the VM is active, paused included, and the
persistent one always. remove_guest_agent reads each definition from its
own XML, so a channel present only in the persistent one is removed too.

Unit tests check paused, running and stopped VMs (paused fails on the
previous code). The live effects test adds attach_disk and
add_guest_agent, and now also checks that edits meant for the running VM
reach it, which showed that an ejected CD-ROM keeps an empty <source>
in the live XML; NIC unplug is left to the guest tests, as these VMs
have no OS to acknowledge it.
2026-10-04 17:10:49 +03:00
catborise
9931b73b2c test(instances): the guest sees the hardware WebVirtCloud configures
New live guest tests compare what the guest agent reports (online vCPUs,
memory blocks, disk serials and buses, NIC MACs) with the persistent
definition, through the web views:

- at boot, with a data disk;
- after force off, CPU and memory resize and power on (2 vCPUs, 3 GiB);
- a hotplugged disk at once; a bus and serial change after powercycle;
- a NIC MAC change after powercycle, not before;
- a clone boots with its own MAC and disk copy;
- reverting a running snapshot removes a NIC added after it, in the
  guest and in the definition, and leaves the VM running;
- a suspended guest does not answer and answers again after resume.

Every request in these tests must redirect (302), so a failing view is
not mistaken for a no-op. Only NICs with QEMU's 52:54: prefix are
compared, as a guest may create its own interfaces. All 13 guest tests
pass against a Rocky Linux 9 guest.
2026-10-04 16:39:02 +03:00
catborise
a47fb9d825 test(instances): live tests with a running guest OS
The live tests so far ran VMs without an OS, so everything that needs the
guest's cooperation was untested. With TEST_LIBVIRT_GUEST_IMAGE set to an
installed Linux image with qemu-guest-agent, instances/test_live_guest.py
boots wvc-test VMs from qcow2 overlays of it (the image is only read) and
checks through the web views:

- delete_vol on a running VM: the guest releases the disk, so it leaves
  the live and persistent definitions and the volume is deleted;
- detach_vol removes the disk from the running guest and keeps the volume;
- add_network/delete_network: the guest sees the hotplugged NIC, and the
  unplug completes in both definitions;
- set_vcpu brings a hotplugged vCPU online in the guest;
- osinfo returns the guest agent's OS, time zone and host name, and the
  agent reports the VM's NIC by MAC;
- poweroff shuts the guest down through libvirt.

The image must be a BIOS-bootable qcow2 volume in a storage pool other
than wvc-test; the tests are skipped while a running VM uses it. They pass
against a Rocky Linux 9 guest on a RHEL qemu-kvm host; the guest had no
address on the NAT network (its network configuration is static), so only
the agent's NIC report is checked, not an address.
2026-10-04 15:39:45 +03:00
catborise
b150a9dd36 fix: stop ignoring SIGCHLD process-wide
The web app (wsgi.py), novncd and socketiod set SIGCHLD to SIG_IGN at
import. The kernel then reaps every child at once, so subprocess loses
the exit status and a failing command reports 0, in the server and in
anything else run in that process: the test suite had to reset the
signal in two tests.

It was added in 2023 against one defunct bash per gunicorn worker and
per novncd. That bash came from the settings template of the time,
which ran .dec_ldap_pwd.sh with Popen and never waited for it; the
template stopped doing that in 561fedf. A settings.py still holding
that line leaves at most one zombie per process, not a growing number.

- wsgi.py, novncd, socketiod: the global SIG_IGN is removed. subprocess
  reaps its own children, websockify installs its own SIGCHLD reaper
  for novncd's connection processes, and novncd's SSH tunnels wait for
  their ssh process.
- socketiod: the pty child execs consolecallback instead of running it
  and then returning into the server's code; disconnect reaps that
  child (waitpid) instead of any child (wait).
- The settings template drops its unused subprocess import; the two
  test workarounds are removed.

New tests load the web app and novncd in a fresh interpreter and check
that SIGCHLD is untouched and a failing command still returns 1; both
fail on the previous code. A patched novncd in the test container
served five connections and left no child behind.
2026-10-04 13:40:53 +03:00
catborise
792487e65e fix(instances): failures reach the user; no redirect to other sites
Web views:
- 34 views returned to request.META["HTTP_REFERER"] + "#tab": without
  a Referer they failed with a TypeError (500), and a Referer from
  another site was followed (open redirect). They return through
  _back(), which keeps a same-site Referer and otherwise opens the VM
  page at the tab. get_safe_redirect uses the same check. Rejected disk
  options also return to the VM's disks tab instead of the VM list.
- migrate_instance returned silently when the target was the current
  compute or unreachable, so the web view logged "Instance is migrated"
  and the API answered "instance migrate is started". It now raises;
  the view reports the error and logs only a completed migration, and
  an unknown compute id is a 404 instead of a 500. The automatic
  migration after a clone skips a target that is the current compute.
- add_owner and del_owner failed with a 500 without their ids, add_owner
  also when the user was already an owner, and del_owner deleted the
  given ownership record even when it belonged to another VM. Ids are
  validated, a record of another VM is a 404, and a repeated owner is
  reported.
- Broad handlers that hid programming errors are narrowed: clone catches
  libvirt, value and OS errors (a lock timeout included), ownership
  lookups catch DoesNotExist. The DRBD status no longer needs its bare
  except: the VM name is escaped in the regex, which was the only thing
  that could fail there.

API:
- A libvirt error under /api/ is JSON with status 400, and a lock
  timeout 409, instead of the HTML error page. This lives in the
  existing ExceptionMiddleware, so existing settings need no change.
- Volume create/delete on an inactive pool answer 409, and invalid
  volume data 400 with the serializer errors, instead of 200.

The new tests cover every view that returns with _back() without a
Referer and with a foreign one, the migrate failures on web and API,
the owner ids, the API error format and the volume statuses; nine of
the ten fail on the previous code.
2026-10-04 12:23:54 +03:00
catborise
f94b3cee7a chore: drop references to the local roadmap from code and tests
Comments, docstrings and live test names cited item IDs of a roadmap
that is not part of the repository. They now say what is meant, or
nothing where the sentence stands without them. Test assertions and
behavior are unchanged.
2026-10-04 11:09:25 +03:00
catborise
d054b13a87 fix(computes): VM ownership survives a move or a short absence
The sync deleted the Instance of every VM missing from its compute, and
its owners and rights went with it. A VM moved with virsh, or undefined
and defined again, came back with no owner.

Now, when a VM disappears from a compute:
- if the same UUID is recorded on another compute, it moved: its owners
  and template flag go there;
- otherwise they are kept in an InstanceTombstone and restored when the
  UUID appears again within INSTANCE_OWNERSHIP_RETENTION_DAYS (30).
A UUID recorded on two computes at the same time is a copy and gets no
owners. VMs deleted through WebVirtCloud leave no tombstone.
2026-10-04 01:08:11 +03:00
catborise
abdd229a1f fix(instances): a failed VM creation leaves no volumes behind
Web form: the cache mode and the UEFI firmware value were checked only
after the disk volume had been created; an invalid one answered 500 and
left the volume. All checks now run before any allocation. Volumes the
request creates are removed on any failure up to the definition; ones
that cannot be removed are reported. After the definition the VM uses
them, so a later failure keeps them.

API: the new volume was never removed when the definition failed, and
the error was a 500. It is removed now and a libvirt error returns 400.
2026-10-04 01:08:11 +03:00
catborise
5def4e41fd fix(instances): NIC edits keep their settings; edits keep metadata
- change_network detached the NIC and attached one built from scratch,
  dropping its QoS, link state and other settings. It now edits the NIC
  of the persistent definition in place and defines once; a running VM
  gets the change at its next start, as the UI already says.
- add_network, delete_network and set_link_state did nothing on a
  paused VM, and set_link_state rewrote the persistent NIC from the live
  XML. They now update the running definition when the VM is active and
  the persistent one always, each from its own XML.
- Boot and console edits used xml.etree, which renamed the namespace
  prefixes of other tools' metadata (<app:info> became <ns0:info>); they
  use lxml now.
- The NIC table and the clone form are filled from the persistent
  definition (Instance.config_networks, config_disks), so a pending NIC
  change is shown and can be edited again.

A live test runs 18 domain edits on stopped, running and paused VMs
whose definition has elements WebVirtCloud does not manage, and checks
for each the effect, that nothing else in the persistent definition
changed, and that persistent-only edits leave the running VM alone.
2026-10-04 01:08:11 +03:00
catborise
57cf49262a fix(instances): clones roll back their copies; own error messages stay
Clone:
- only shut-off VMs are cloned, as the UI already required: a running
  guest keeps writing while its disks are copied; the definition comes
  from the persistent XML;
- MAC addresses and disk destinations are checked before anything is
  allocated;
- every volume the clone creates (NVRAM and disks) is tracked; if a
  later copy or the definition fails, only those are deleted, and any
  that cannot be deleted are named in the error. Before, a failed clone
  left its earlier copies behind.

libvirtError(msg) reports the thread's last libvirt error instead of
msg when there is one, so WebVirtCloud's own errors could show an
earlier, unrelated failure. util.OperationError, a libvirtError
subclass that keeps its message, replaces those raises in vrtManager,
instances and nwfilters; 'except libvirtError' still catches it.
2026-10-04 01:08:11 +03:00
catborise
e5fda79f5a feat(console): VNC is the only console type
SPICE was removed earlier; its leftovers and the console type choice go
too:
- the console settings form, update_console and the backend no longer
  offer or change the type; an invalid console form now shows its
  errors instead of being ignored;
- the create form, the API serializer and create_instance no longer take
  a graphics type, new VMs get VNC; get_graphics_types and the graphics
  entries of the domain capabilities are gone;
- migration appsettings.0013 deletes QEMU_CONSOLE_DEFAULT_TYPE;
- a VM defined elsewhere with another graphics type gets "Console type
  ... is not supported".

The locale catalogs still reference the deleted SPICE templates in
source comments; regenerating them is left to the translation work.
2026-10-04 01:08:11 +03:00
catborise
9f86330552 fix(instances): ISO mount/unmount for every VM state and CD-ROM
The old code edited the live XML, called attachDevice and redefined the
domain from it: a stopped VM lost its VNC password, a paused VM crashed
with UnboundLocalError, an unknown CD-ROM was silently ignored (on a
running VM the last disk of the loop could be updated instead), and a
missing ISO raised AttributeError.

Media changes now use updateDeviceFlags on the requested CD-ROM, built
from the live and the persistent definition separately. A CD-ROM
missing from one definition, an unknown ISO, an ISO name present in
more than one pool, or a change that reached only the running VM is
reported to the user. The mount and unmount log messages were swapped.
2026-10-04 01:08:11 +03:00
catborise
08bf39ef61 fix(instances): domain edits start from the persistent definition
Edits that redefine a domain read the wrong XML:
- boot menu/order, vCPU hotplug and QoS read XMLDesc(0), which has no
  secrets, so every such edit, even on a stopped VM, removed the VNC
  password from the definition;
- console, video, CPU, memory and title/description edits read the live
  XML, so on a running VM pending changes (e.g. a new memory size) were
  reverted and runtime state was written into the definition.

All of them now read VIR_DOMAIN_XML_INACTIVE | SECURE (PERSISTENT_XML).
The console setters take the first graphics device of that definition
instead of looking it up by the live type.

Disk resize no longer redefines the domain. A running VM's image files
grow through blockResize, since qemu-img cannot take the lock QEMU
holds; block volumes (e.g. LVM) are grown by the storage pool first and
QEMU is then told the new size.

Live tests reproduce the losses on a RHEL qemu-kvm host; unit tests
check the XML flags, the console setters and the resize paths in CI.
2026-10-04 01:08:11 +03:00
catborise
21f0777e76 fix(instances): internal snapshots no longer rewrite UEFI loaders
Taking or reverting an internal snapshot redefined a pflash VM with a
rom loader around the operation. libvirt 10.10 rejects that definition,
so the request ended in a 500; where it was accepted, the snapshot
recorded the fake loader, and a failure between rewrite and restore
left the VM unable to boot.

The definition is no longer touched: libvirt decides, and its error is
shown (for a running VM with raw NVRAM: 'internal snapshots of a VM
with pflash based firmware require QCOW2 nvram format'). A stopped VM's
disk-only internal snapshot is taken, as libvirt allows it. Revert
errors are shown too. The external snapshot path keeps its rewrite for
now.

Live tests cover running and stopped UEFI VMs; both failed with a 500
on the previous code.
2026-10-04 01:08:11 +03:00
catborise
3e427a41b8 fix(instances): find known VMs by UUID, not by name
Instance.proxy looked the domain up by its stored name. After a rename
or a name swap outside WebVirtCloud, an operation on one VM changed
another; after a VM was replaced under the same name, operations hit
the new VM.

wvmInstance takes the UUID and uses lookupByUUIDString, with no
fallback to the name: a missing UUID fails instead of picking another
domain. Every caller that has an Instance passes it: proxy, console,
quota, post-migration autostart and the VDI URL. Migration uses the
domain the proxy already resolved. getvvfile and the gstfsd requests
use the host's current name of that UUID.

Live tests cover a name swap and a VM replaced under the same name;
both fail on the previous code.
2026-10-04 01:08:11 +03:00
catborise
ffb2ce6bfa fix(instances): destroy and volume delete no longer lose disks
Destroy:
- Stops every active state, not only running; a paused VM used to keep
  running as a transient domain after the undefine.
- Undefines with MANAGED_SAVE and SNAPSHOTS_METADATA, before any disk is
  deleted. A managed-save image used to make the undefine fail after the
  disks were already gone; now a failed undefine deletes nothing.
- Keeps disks that another domain references, directly or as a backing
  file anywhere in its chain (running domains' backingStore, and the
  backingStore of the volumes in the active pools for stopped ones), and
  says so; they used to be deleted with the VM.

Volume delete:
- Detaches from the live domain in every active state, including paused.
- Deletes the volume only after neither the live nor the persistent
  definition has the disk (bounded wait for the guest to release it),
  and refuses a volume another VM references. Otherwise the volume is
  kept and the user is told why.

Both hold the compute exclusively for the usage scan and the deletion,
so another VM cannot attach the disk in between; the destroy
confirmation page (GET) takes no lock.

Live tests reproduce each case on a RHEL qemu-kvm host and fail on the
previous code; unit tests cover the destroy ordering and the locks.
2026-10-04 01:08:11 +03:00
catborise
dbbf2ce2e8 test(instances): isolate the live libvirt suite on shared hosts
- Runs only when TEST_LIBVIRT_HOST is set, so manage.py test on a
  developer machine no longer creates VMs on the local libvirtd.
- Every VM and volume is named wvc-test-*, and volumes live in a
  temporary wvc-test pool (/var/lib/libvirt/wvc-test) instead of the
  default pool next to real images.
- Cleanup removes only prefixed domains and the test pool's volumes;
  it also clears leftovers of an interrupted run before starting, and
  tolerates domains that turned transient.
- tearDownClass fails if any domain or volume outside the test
  namespace appeared, disappeared or was replaced.
- livetest gains create_volume/define_vm helpers and
  test_live_dataloss.py, whose setUpClass opens the class transaction
  last, so a failed libvirt connection cannot lock the test database
  for later test classes.

The first live tests reproduce two known external-snapshot data losses
and are marked expectedFailure: reverting deletes a disk added after
the snapshot, and deleting the older of two snapshots deletes the
newer one's overlay.
2026-10-04 01:08:11 +03:00
catborise
fbc6c4976f fix(instances): change the edited NIC instead of adding a new one
The edit form posts net-*-<n> fields for NIC n and carries only the
new MAC. The backend read index 0 and looked the NIC up by the new MAC,
so editing a MAC detached nothing and attached a second NIC, and edits
of any NIC but the first were applied with empty values.

The form now also posts the current MAC (net-old-mac-<n>). The view
reads the submitted NIC's fields, validates the new MAC and reports
errors. The backend replaces the NIC with the old MAC, refuses an
unknown one, and on a failed attach restores the old NIC and raises
instead of reporting success.

The view crashed with a TypeError without a Referer header; it now
returns to the instance page, and a Referer from another site is
ignored.
2026-10-04 01:08:11 +03:00
catborise
9e92d208db fix(instances): flavor redirects and create-from-XML crashed
- Flavor create/update/delete fell back to the nonexistent
  instances:flavors URL; reverse() ran on every request, so each of
  them raised NoReverseMatch. Fall back to instances:index.
- Creating a VM from XML called instances.utils.refresh_instance_database,
  which does not exist; use utils.refr.

Both found by running the live libvirt suite.
2026-10-04 01:08:11 +03:00
catborise
88ec3c5f6a test(instances): make the live libvirt suite runnable on real hosts
- Pick the test VM by name; .first() could select any VM already on
  the compute, and tearDownClass deletes the chosen VM's disks.
- Delete the test volume when setUpClass fails after creating it.
- test-vm.xml: no hard-coded emulator, machine alias q35 instead of
  pc-q35-5.1, so it defines on RHEL qemu-kvm hosts too.
- Follow current behavior: power actions are POST-only, the create
  form needs add_cdrom/add_input and VNC, cloning needs change
  permission on the source VM.
- README: the tests read TEST_LIBVIRT_HOST/TYPE/LOGIN/PASSWORD;
  WEBVIRTCLOUD_TEST_LIBVIRT_URI never existed.
2026-10-04 01:08:11 +03:00
catborise
b04f656d2b ci: replace super-linter with ruff
Super-linter ran from the unpinned :latest image and, because of
branches-ignore: [master], never on pull requests to master. Ruff now
checks syntax errors and pyflakes rules (E9, F) on every push and PR.

Fixes the existing findings: unused imports and variables, an unused
duplicate import in instances/tests.py, and the star import in
accounts/views.py. settings-dev.py keeps its star import of the base
settings.
2026-10-04 01:08:11 +03:00
catborise
51dedb7b8f fix(instances): disk edits keep their XML; changes lock per VM
Disk edits:
- edit_disk built a new <disk> from the form and replaced the old one,
  losing what the form does not show (e.g. encryption and virtio
  driver options); a bus change detached and re-attached the disk.
  The disk of the persistent definition is now edited in place and the
  domain defined once; a bus change picks a free device name and drops
  the old address and virtio-only options. Pending persistent changes of
  a running VM are kept.
- Clone omits empty disk devices, refuses a disk without a supported
  source and destination, and checks the name before allocating storage.

Locks:
- Every VM change held its compute's exclusive lock, so one slow
  operation blocked all VMs on that compute. A VM change now holds its
  own VM lock plus a shared compute lock; reconciliation, migration and
  cloning hold the compute exclusively (two clones of different VMs
  must not pass the name check together).
- Quota-checked changes of one user (CPU, memory and disk resize,
  clone) run one at a time under a per-user lock, also across computes,
  so concurrent requests cannot all pass the check against the old
  allocation. It is the outermost lock; superusers, who have no quota,
  skip it.
- A busy lock answers 409 to the API and a message in the web UI. API
  power and destroy actions return the view's error instead of success.
2026-10-04 01:08:11 +03:00
catborise
3b8869c72f fix(api): typed URLs and accurate response schemas
- Routers use path converters (use_regex_path=False): computes, instances
  and flavors take <int:...> ids, so non-numeric ids get 404 and
  drf-spectacular derives the parameter types itself.
- Each method declares what it really returns: detail serializers,
  a shared StatusSerializer for status-only replies, no request body for
  actions. Instance properties used by the details serializer get return
  type hints.
- The create-instance API drops the firmware field. Its only accepted
  value was "" (BIOS), which produced an empty, invalid enum, and the
  UEFI branch behind it was unreachable. Clients that still send it are
  unaffected; DRF ignores unknown fields.

The schema now passes spectacular --validate --fail-on-warn; a test
keeps it that way.
2026-10-04 01:08:11 +03:00
catborise
a6364f5d9b fix(gstfsd): append SSH keys, IPv6 loopback bind, server-side timeout
- Add the public key to authorized_keys instead of replacing the file.
- Use AF_INET6 when GSTFSD_BIND_HOST is an IPv6 address (binding ::1
  crashed with the AF_INET server).
- Close idle client connections after 30 s.

Tests run the real daemon against a small fake guestfs module over a
socket; this also exercises the earlier sendall() fix.
2026-10-03 00:02:01 +03:00
catborise
32d7f279e7 test: inventory HTML sinks in templates, realistic datasource params in the matrix
- instances/test_xss.py: scan all templates for .html(, innerHTML = and
  append('<...') and require each line to be in a reviewed allowlist, so
  new sinks fail the test (the exact-string tests only caught the lines
  they named).
- Authorization matrix: fill the datasource "version" parameter with
  "latest" instead of "x". The datasource views then answer 200 to any
  logged-in user; they are listed in the allowlist as known F-14.
2026-10-03 00:00:27 +03:00
catborise
479faaa4ca fix(instances): allow editing disks that have no driver format
A disk without driver/@type could not be edited: the empty format was
rejected, and edit_disk would have written type=''. An empty format is
now accepted and edit_disk leaves the type attribute out.
2026-10-02 23:58:55 +03:00
catborise
e626da5002 fix(instances): do not refuse clones for volume-less disks or unusual names
Clone refused VMs that cloned fine before: a disk without a volume got a
None disk name, and a source image with characters such as a space or "@"
produced a derived name outside the allowed pattern. Disks without a
volume (which clone_instance does not copy) are no longer validated, and
derived disk names are sanitized to the allowed charset. Names typed by a
superuser are still validated strictly.
2026-10-02 23:14:28 +03:00
catborise
9205e56e3b fix(instances): change a disk's bus without rebuilding it as a file disk
The edit_volume bus change detached the disk and attached a new one built
as a file disk, so block and rbd disks lost their source and auth, and a
failed attach left the disk detached. Add wvmInstance.change_disk_bus,
which reuses the disk element and only changes <target> (dropping the old
bus address), and re-attaches the original disk if attaching fails.

edit_disk also rebuilt every disk with <source file=...>; it now keeps the
existing <source> and <auth> of block and network disks.
2026-10-02 23:06:42 +03:00
catborise
7286ba079d fix(gstfsd): random password salt, timeouts, loopback-only daemon
- Hash the root password with a random SHA-512 salt instead of the fixed
  "$6$kgPoiREy".
- Talk to gstfsd through one helper with a 120 s socket timeout; an
  unreachable daemon or a bad reply is an error message, not a 500.
- gstfsd refuses to listen on a non-loopback address unless
  GSTFSD_ALLOW_REMOTE=1: it is an unauthenticated root service.
- gstfsd: encode replies (sendall(str) raised TypeError on Python 3, so
  the success path never worked) and use octal chmod modes.
- README: what gstfsd does and how to allow it on a remote compute.
2026-10-02 22:52:09 +03:00
catborise
e76149c71a fix: serve compute JSON endpoints as application/json, build chipset list safely
compute_graph (text/javascript) and the machine type, disk bus, video
model and domain capability views (text/html) return JSON; send it as
application/json. create_instance_w1.html builds the chipset <option>
elements with .val().text() instead of string concatenation.
2026-10-02 22:34:05 +03:00
catborise
d250915e17 fix(instances): edit_volume bus change and format, full-string validation
- edit_volume passed driver_type to attach_disk, which has no such
  parameter, so every bus change failed after the disk was detached; pass
  format_type.
- A disk without a driver type shows "None" in the edit form; keep the
  disk's current format instead of rejecting it.
- Validate with fullmatch, so a trailing newline is no longer accepted,
  and use one volume-name pattern for disk views and clone.
2026-10-02 22:31:25 +03:00
catborise
634e9acb69 Merge branch 'fix/wave0-template-flag' into wave0-integration 2026-10-02 22:14:40 +03:00
catborise
34851b8dc2 Merge branch 'fix/wave0-staff-console-password' into wave0-integration 2026-10-02 22:14:40 +03:00
catborise
51db12acfe Merge branch 'fix/wave0-authorization-matrix' into wave0-integration
# Conflicts:
#	datasource/views.py
2026-10-02 22:14:40 +03:00
catborise
fbb48fb3ea Merge branch 'fix/wave0-clone-permission' into wave0-integration 2026-10-02 22:14:29 +03:00
catborise
3df2e68431 Merge branch 'fix/wave0-xss' into wave0-integration 2026-10-02 22:14:29 +03:00
catborise
385a332197 fix(instances): only superusers and staff may change the template flag
change_options set is_template for any owner with is_change; the
"staff only" rule existed only as a disabled checkbox in the UI. A
non-staff owner could mark or unmark templates with a POST, and because a
disabled checkbox is not submitted, a non-staff owner who only edited a
template's title or description silently unmarked it, which lifted the
template-only restrictions.

Only change is_template for superusers and staff (for them an unchecked,
unsubmitted checkbox still means "not a template"); leave it alone for
everyone else.
2026-10-02 22:13:46 +03:00
catborise
8e23699987 fix(instances): show console settings and the VNC password only to console managers
The console settings form on the VM detail page renders the VNC password
(PasswordInput(render_value=True)) and was shown to every is_staff user.
A staff user with the global view_instances permission, or a staff owner
without is_vnc, could read the VNC password of every VM they could see.

Add instances.utils.can_manage_console (active superuser, or an active
owner with is_change and is_vnc), which matches what update_console
already enforced. The detail view only builds the form, and so only puts
the password into the page, when it returns True; the template and
update_console use the same rule.
2026-10-02 22:04:05 +03:00
catborise
4d34d9d07c fix: add an authorization matrix test and close the endpoints it finds
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.
2026-10-02 20:33:12 +03:00
catborise
5758073e82 fix(instances): require change permission to clone and stop trusting clone POST data
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.
2026-10-02 19:07:53 +03:00
catborise
4546653b5a fix: serve JSON endpoints as application/json and build DOM safely
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().
2026-10-02 18:29:11 +03:00
catborise
8a3ee13b41 fix(instances): restrict disk views to superusers and validate disk input
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.
2026-10-02 17:11:12 +03:00
catborise
73d8fd71fb fix(instances): guard mutations and refresh lists after sync 2026-09-28 14:56:19 +03:00
catborise
2cdf5dfca1 fix(reconciliation): serialize compute sync and enforce UUID identity 2026-09-28 14:55:49 +03:00
catborise
b3e6fb2b7a feat(security): enforce require_POST on power actions, prevent open redirects, and isolate instance permissions 2026-09-27 13:20:45 +03:00
catborise
3f8eeb04bd fix(reconciliation): implement UUID-based domain reconciliation and connection safety 2026-09-27 13:13:43 +03:00
catborise
708b986e8e feat(security): apply Codex review hardening to console tokens and superuser authorization 2026-09-27 11:30:03 +03:00
catborise
1227674d8f feat(security): implement P0 critical security fixes for API, console, and access control
- 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).
2026-09-27 10:50:09 +03:00
catborise
e980aca2ff feat(console): remove deprecated SPICE support in favor of noVNC
- 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
2026-09-26 22:43:40 +03:00