1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00

fix(instances): snapshot and console settings requests without the permission get 403

Without instances.snapshot_instances (snapshots) or is_vnc (console
settings) the request was accepted and silently did nothing, although
the documented answer for a missing right is 403. delete_external_snapshot
also asked libvirt for the VM state before checking the permission.
This commit is contained in:
catborise 2026-10-07 23:57:10 +03:00
parent 44b3b6569a
commit 9fdb039977
2 changed files with 116 additions and 127 deletions

View file

@ -1297,19 +1297,18 @@ def mount_iso(request, pk):
@serialize_instance_mutation
def snapshot(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
if request.user.has_perm(
"instances.snapshot_instances"
):
name = request.POST.get("name", "")
desc = request.POST.get("description", "")
try:
instance.proxy.create_snapshot(name, desc)
except libvirtError as err:
# e.g. UEFI firmware whose NVRAM format does not allow internal snapshots
messages.error(request, _("Snapshot was not created: %(err)s") % {"err": error_text(request, err)})
else:
msg = _("Create snapshot: %(snap)s") % {"snap": name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
if not request.user.has_perm("instances.snapshot_instances"):
raise PermissionDenied
name = request.POST.get("name", "")
desc = request.POST.get("description", "")
try:
instance.proxy.create_snapshot(name, desc)
except libvirtError as err:
# e.g. UEFI firmware whose NVRAM format does not allow internal snapshots
messages.error(request, _("Snapshot was not created: %(err)s") % {"err": error_text(request, err)})
else:
msg = _("Create snapshot: %(snap)s") % {"snap": name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
return _back(request, pk, "managesnapshot")
@ -1317,13 +1316,12 @@ def snapshot(request, pk):
@serialize_instance_mutation
def delete_snapshot(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
if request.user.has_perm(
"instances.snapshot_instances"
):
snap_name = request.POST.get("name", "")
instance.proxy.snapshot_delete(snap_name)
msg = _("Delete snapshot: %(snap)s") % {"snap": snap_name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
if not request.user.has_perm("instances.snapshot_instances"):
raise PermissionDenied
snap_name = request.POST.get("name", "")
instance.proxy.snapshot_delete(snap_name)
msg = _("Delete snapshot: %(snap)s") % {"snap": snap_name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
return _back(request, pk, "managesnapshot")
@ -1331,20 +1329,19 @@ def delete_snapshot(request, pk):
@serialize_instance_mutation
def revert_snapshot(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
if request.user.has_perm(
"instances.snapshot_instances"
):
snap_name = request.POST.get("name", "")
try:
instance.proxy.snapshot_revert(snap_name)
except libvirtError as err:
messages.error(request, _("Snapshot was not reverted: %(err)s") % {"err": error_text(request, err)})
return _back(request, pk, "managesnapshot")
msg = _("Successful revert snapshot: ")
msg += snap_name
messages.success(request, msg)
msg = _("Revert snapshot: %(snap)s") % {"snap": snap_name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
if not request.user.has_perm("instances.snapshot_instances"):
raise PermissionDenied
snap_name = request.POST.get("name", "")
try:
instance.proxy.snapshot_revert(snap_name)
except libvirtError as err:
messages.error(request, _("Snapshot was not reverted: %(err)s") % {"err": error_text(request, err)})
return _back(request, pk, "managesnapshot")
msg = _("Successful revert snapshot: ")
msg += snap_name
messages.success(request, msg)
msg = _("Revert snapshot: %(snap)s") % {"snap": snap_name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
return _back(request, pk, "managesnapshot")
@ -1352,14 +1349,13 @@ def revert_snapshot(request, pk):
@serialize_instance_mutation
def create_external_snapshot(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
if request.user.has_perm(
"instances.snapshot_instances"
):
name = request.POST.get("name", "")
desc = request.POST.get("description", "")
instance.proxy.create_external_snapshot("s1." + name, instance, desc=desc)
msg = _("Create external snapshot: %(snap)s") % {"snap": name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
if not request.user.has_perm("instances.snapshot_instances"):
raise PermissionDenied
name = request.POST.get("name", "")
desc = request.POST.get("description", "")
instance.proxy.create_external_snapshot("s1." + name, instance, desc=desc)
msg = _("Create external snapshot: %(snap)s") % {"snap": name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
return _back(request, pk, "managesnapshot")
@ -1367,18 +1363,17 @@ def create_external_snapshot(request, pk):
@serialize_instance_mutation
def revert_external_snapshot(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
if request.user.has_perm(
"instances.snapshot_instances"
):
instance_state = True if instance.proxy.get_status() != 5 else False
name = request.POST.get("name", "")
date = request.POST.get("date", "")
desc = request.POST.get("desc", "")
instance.proxy.force_shutdown() if instance_state else None
instance.proxy.revert_external_snapshot(name, date, desc)
instance.proxy.start() if instance_state else None
msg = _("Revert external snapshot: %(snap)s") % {"snap": name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
if not request.user.has_perm("instances.snapshot_instances"):
raise PermissionDenied
instance_state = True if instance.proxy.get_status() != 5 else False
name = request.POST.get("name", "")
date = request.POST.get("date", "")
desc = request.POST.get("desc", "")
instance.proxy.force_shutdown() if instance_state else None
instance.proxy.revert_external_snapshot(name, date, desc)
instance.proxy.start() if instance_state else None
msg = _("Revert external snapshot: %(snap)s") % {"snap": name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
return _back(request, pk, "managesnapshot")
@ -1386,20 +1381,19 @@ def revert_external_snapshot(request, pk):
@serialize_instance_mutation
def delete_external_snapshot(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
if not request.user.has_perm("instances.snapshot_instances"):
raise PermissionDenied
instance_state = True if instance.proxy.get_status() == 5 else False
if request.user.has_perm(
"instances.snapshot_instances"
):
name = request.POST.get("name", "")
name = request.POST.get("name", "")
instance.proxy.start(VIR_DOMAIN_START_PAUSED) if instance_state else None
instance.proxy.start(VIR_DOMAIN_START_PAUSED) if instance_state else None
try:
instance.proxy.delete_external_snapshot(name)
msg = _("Delete external snapshot: %(snap)s") % {"snap": name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
finally:
instance.proxy.force_shutdown() if instance_state else None
try:
instance.proxy.delete_external_snapshot(name)
msg = _("Delete external snapshot: %(snap)s") % {"snap": name}
addlogmsg(request.user.username, instance.compute.name, instance.name, msg)
finally:
instance.proxy.force_shutdown() if instance_state else None
return _back(request, pk, "managesnapshot")
@ -1925,64 +1919,65 @@ def clone(request, pk):
def update_console(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
if utils.can_manage_console(request.user, instance):
form = ConsoleForm(request.POST or None, can_set_listener=request.user.is_superuser)
if form.is_valid():
if not utils.can_manage_console(request.user, instance):
raise PermissionDenied
form = ConsoleForm(request.POST or None, can_set_listener=request.user.is_superuser)
if form.is_valid():
if (
"generate_password" in form.changed_data
or "clear_password" in form.changed_data
or "password" in form.changed_data
):
if form.cleaned_data["generate_password"]:
password = randomPasswd()
elif form.cleaned_data["clear_password"]:
password = ""
else:
password = form.cleaned_data["password"]
# A console that listens beyond localhost (set by an
# administrator) keeps its password unless an administrator
# removes it. The definition being changed is the persistent one.
if (
"generate_password" in form.changed_data
or "clear_password" in form.changed_data
or "password" in form.changed_data
not password
and not request.user.is_superuser
and not instance.proxy.console_listens_locally()
):
if form.cleaned_data["generate_password"]:
password = randomPasswd()
elif form.cleaned_data["clear_password"]:
password = ""
else:
password = form.cleaned_data["password"]
# A console that listens beyond localhost (set by an
# administrator) keeps its password unless an administrator
# removes it. The definition being changed is the persistent one.
if (
not password
and not request.user.is_superuser
and not instance.proxy.console_listens_locally()
):
messages.error(
request,
_("The console listens beyond localhost; only an administrator can remove its password."),
)
elif not instance.proxy.set_console_passwd(password):
msg = _(
"Error setting console password. "
+ "You should check that your instance have an graphic device."
)
messages.error(request, msg)
else:
msg = _("Set VNC password")
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
if "keymap" in form.changed_data or "clear_keymap" in form.changed_data:
if form.cleaned_data["clear_keymap"]:
instance.proxy.set_console_keymap("")
else:
instance.proxy.set_console_keymap(form.cleaned_data["keymap"])
msg = _("Set VNC keymap")
messages.error(
request,
_("The console listens beyond localhost; only an administrator can remove its password."),
)
elif not instance.proxy.set_console_passwd(password):
msg = _(
"Error setting console password. "
+ "You should check that your instance have an graphic device."
)
messages.error(request, msg)
else:
msg = _("Set VNC password")
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
if "listen_on" in form.changed_data:
instance.proxy.set_console_listener_addr(form.cleaned_data["listen_on"])
msg = _("Set VNC listen address")
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
else:
messages.error(request, _("Console settings were not saved: %(errors)s") % {"errors": form.errors.as_text()})
if "keymap" in form.changed_data or "clear_keymap" in form.changed_data:
if form.cleaned_data["clear_keymap"]:
instance.proxy.set_console_keymap("")
else:
instance.proxy.set_console_keymap(form.cleaned_data["keymap"])
msg = _("Set VNC keymap")
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
if "listen_on" in form.changed_data:
instance.proxy.set_console_listener_addr(form.cleaned_data["listen_on"])
msg = _("Set VNC listen address")
addlogmsg(
request.user.username, instance.compute.name, instance.name, msg
)
else:
messages.error(request, _("Console settings were not saved: %(errors)s") % {"errors": form.errors.as_text()})
return get_safe_redirect(
request, default=reverse("instances:instance", args=[instance.id]) + "#vncsettings"

View file

@ -100,16 +100,10 @@ CHANGE_VM = {
"instances:rootpasswd",
"instances:add_public_key",
"instances:change_options",
# Reached, but a no-op without is_vnc (can_manage_console).
"instances:update_console",
# Reached, but a no-op without instances.snapshot_instances.
"instances:snapshot",
"instances:delete_snapshot",
"instances:revert_snapshot",
"instances:create_external_snapshot",
"instances:delete_external_snapshot",
"instances:revert_external_snapshot",
}
# Also needs is_vnc (can_manage_console). Snapshots also need
# instances.snapshot_instances, which no role here has: refused to all.
CONSOLE_SETTINGS = {"instances:update_console"}
DELETE_VM = {"instances:destroy"}
ROLE_EXPECTATIONS = {
@ -123,7 +117,7 @@ ROLE_EXPECTATIONS = {
True,
False,
{"is_change": True, "is_vnc": True},
VIEW_VM | POWER_AND_CONSOLE | CHANGE_VM,
VIEW_VM | POWER_AND_CONSOLE | CHANGE_VM | CONSOLE_SETTINGS,
),
}