1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 03:36:48 +00:00

fix(jobs): VM create and volume clone check the user and the name again in the worker

Every other job re-checks its permission and state in precheck before any
effect; these two checked only when queued. A queued create of a user who
is no longer a superuser, or whose VM name a host took meanwhile, ran; so
did a volume clone whose name a volume made outside the panel took.
This commit is contained in:
catborise 2026-11-05 16:00:32 +03:00
parent 1089b914ed
commit cad392f79c
4 changed files with 49 additions and 0 deletions

View file

@ -792,6 +792,16 @@ class CreateInstance(Kind):
label=f"{compute.name}/{params['name']}",
intent={"compute": compute.pk, "uuid": util.randomUUID(), "name": params["name"]})
def precheck(self, op, ctx):
if op.user is None or not op.user.is_active or not op.user.is_superuser:
raise PermissionDenied
# a VM found on a host since the job was queued may have taken the name
if Instance.objects.filter(name__exact=op.params["name"]).exists():
raise util.OperationError(_("There is an instance with same name. Remove it and try again!"))
c = Compute.objects.get(pk=op.params["compute"])
if op.params["name"] in wvmCreate(c.hostname, c.login, c.password, c.type).get_instances():
raise util.OperationError(_("A virtual machine with this name already exists"))
def run(self, op, ctx):
data = op.params
c = Compute.objects.get(pk=data["compute"])

View file

@ -6,6 +6,7 @@ from unittest.mock import MagicMock, patch
import libvirt
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied
from django.test import TransactionTestCase
from appsettings.settings import get_settings
@ -114,3 +115,19 @@ class CreateJobTestCase(TransactionTestCase):
self.assertEqual(CreateInstance().reconcile(op).state, Operation.FAILED)
pool.listVolumes.return_value = ["api-vm.qcow2"]
self.assertEqual(CreateInstance().reconcile(op).state, Operation.UNCERTAIN)
def test_the_worker_checks_the_user_and_the_name_again(self):
response = self.client.post(f"/api/v1/computes/{self.compute.pk}/instances/create/x86_64/q35/", BODY,
content_type="application/json")
self.assertEqual(response.status_code, 202)
op = Operation.objects.get()
CreateInstance().precheck(op, None)
# a VM of that name found on a host meanwhile
self.conn.get_instances.return_value = ["api-vm"]
with self.assertRaisesMessage(libvirt.libvirtError, "A virtual machine with this name already exists"):
CreateInstance().precheck(op, None)
# the user is no longer a superuser
op.user.is_superuser = False
op.user.save()
with self.assertRaises(PermissionDenied):
CreateInstance().precheck(op, None)

View file

@ -51,6 +51,13 @@ class CloneVolume(Kind):
if not user.is_superuser:
raise PermissionDenied
def precheck(self, op, ctx):
if op.user is None or not op.user.is_active or not op.user.is_superuser:
raise PermissionDenied
# a volume made outside the panel since the job was queued may have taken the name
if op.intent["target"] in _storage(op.params)[1].get_volumes():
raise util.OperationError(gettext_noop("Name of volume already in use"))
def plan(self, user, params):
compute, stg = _storage(params)
volumes = stg.get_volumes()

View file

@ -4,12 +4,14 @@ from types import SimpleNamespace
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied
from django.test import TransactionTestCase
from django.urls import reverse
from computes.models import Compute
from operations.jobs import Context
from operations.models import Claim, Operation, acquire
from libvirt import libvirtError
from storages.jobs import CloneVolume
@ -72,3 +74,16 @@ class VolumeCloneJobTestCase(TransactionTestCase):
self.assertEqual(CloneVolume().reconcile(op).state, Operation.FAILED)
op.intent["created"] = "copy.qcow2"
self.assertEqual(CloneVolume().reconcile(op).state, Operation.SUCCEEDED)
def test_the_worker_checks_the_user_and_the_name_again(self):
user = get_user_model().objects.get(username="vol_admin")
op = Operation(kind="volume.clone", user=user, intent={"target": "copy.qcow2"},
params={"compute": self.compute.pk, "pool": "pool-a", "image": "base.qcow2", "name": "copy",
"format": "", "prealloc": False})
CloneVolume().precheck(op, None)
self.stg.get_volumes.return_value = ["base.qcow2", "copy.qcow2"] # made outside the panel meanwhile
with self.assertRaisesMessage(libvirtError, "Name of volume already in use"):
CloneVolume().precheck(op, None)
user.is_superuser = False
with self.assertRaises(PermissionDenied):
CloneVolume().precheck(op, None)