While HMP (human monitor protocol) commands beside 'info' and 'help' already require the 'Sys.Modify' permission on '/', certain commands are better further restricted to be root-only.
Command list and descriptions taken from the output of 'help' and shortened the descriptions where appropriate. Many related commands for root-only commands were also made root-only, for example 'drive_del', because 'drive_add' is or the NBD commands, because 'nbd_server_start' is. That is being able to only do certain parts of command groups that are not that useful by themselves. An exception here is 'qom-get' which is just too useful to be root-only. Signed-off-by: Fiona Ebner <f.eb...@proxmox.com> --- src/PVE/API2/Qemu.pm | 32 ++++-- src/PVE/API2/Qemu/HMPPerms.pm | 207 ++++++++++++++++++++++++++++++++++ src/PVE/API2/Qemu/Makefile | 2 +- 3 files changed, 231 insertions(+), 10 deletions(-) create mode 100644 src/PVE/API2/Qemu/HMPPerms.pm diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm index dbc08737..82cdc742 100644 --- a/src/PVE/API2/Qemu.pm +++ b/src/PVE/API2/Qemu.pm @@ -56,6 +56,7 @@ use PVE::Network; use PVE::Firewall; use PVE::API2::Firewall::VM; use PVE::API2::Qemu::Agent; +use PVE::API2::Qemu::HMPPerms; use PVE::VZDump::Plugin; use PVE::DataCenterConfig; use PVE::ProcFSTools; @@ -5582,8 +5583,7 @@ __PACKAGE__->register_method({ proxyto => 'node', description => "Execute QEMU monitor commands.", permissions => { - description => - "Sys.Modify is required for (sub)commands which are not read-only ('info *' and 'help')", + description => PVE::API2::Qemu::HMPPerms::generate_description(), check => ['perm', '/vms/{vmid}', ['VM.Monitor']], }, parameters => { @@ -5604,14 +5604,28 @@ __PACKAGE__->register_method({ my $rpcenv = PVE::RPCEnvironment::get(); my $authuser = $rpcenv->get_user(); - my $is_ro = sub { - my $command = shift; - return $command =~ m/^\s*info(\s+|$)/ - || $command =~ m/^\s*help\s*$/; - }; + my $command = $param->{command} or die "no command specified\n"; + die "unexpected command '$command'\n" if $command !~ m/^\s*(\S+)/; + my $command_name = $1; + my $required_perm = $PVE::API2::Qemu::HMPPerms::hmp_command_perms->{$command_name}; + if (!$required_perm) { + my $msg = + "command '$command_name' non-existent or not assigned a required permission" + . " yet, limiting to root user\n"; + die $msg if $authuser ne 'root@pam'; + warn $msg; + $required_perm = 'root'; + } - $rpcenv->check_full($authuser, "/", ['Sys.Modify']) - if !&$is_ro($param->{command}); + if ($required_perm eq 'root') { + die "root-only command '$command_name'\n" if $authuser ne 'root@pam'; + } elsif ($required_perm eq 'Sys.Modify') { + $rpcenv->check_full($authuser, "/", ['Sys.Modify']); + } elsif ($required_perm eq 'none') { + # nothing to check + } else { + die "unexpected required permission '$required_perm' for command '$command_name'\n"; + } my $vmid = $param->{vmid}; diff --git a/src/PVE/API2/Qemu/HMPPerms.pm b/src/PVE/API2/Qemu/HMPPerms.pm new file mode 100644 index 00000000..f6b32891 --- /dev/null +++ b/src/PVE/API2/Qemu/HMPPerms.pm @@ -0,0 +1,207 @@ +package PVE::API2::Qemu::HMPPerms; + +use strict; +use warnings; + +# List of monitor commands and associated required permission. Listed explicitly to be future-proof. +# +# Currently permissions are: +# 'root' - for root-only commands +# 'Sys.Modify' - commands that can be issued with 'Sys.Modify' on '/' +# 'none' - no permissions required (i.e. help and info) +our $hmp_command_perms = { + help => 'none', # show the help + '?' => 'none', # short-form of 'help' + info => 'none', # show various information about the system state + + # root-only: backup to arbitrary target file (although currently, not overwriting existing file) + backup => 'root', # create a VM backup (VMA format). + # root-only: requires the stream source in the backing chain currently, but better be safe + block_stream => 'root', # copy data from a backing file into a block device + # root-only: allows changing the path a removable medium points to + change => 'root', # change a removable medium + # root-only: among others, there is a 'file' driver + 'chardev-add' => 'root', # add chardev + # root-only: among others, there is a 'file' driver (e.g. modify backend for serial device) + 'chardev-change' => 'root', # change chardev + # root-only: because chardev-add is + 'chardev-remove' => 'root', # remove chardev + # root-only: after migration SPICE client will attempt to connect to arbitrarily set host + client_migrate_info => 'root', # set migration information for remote display + # root-only: like '-device' on the commandline + device_add => 'root', # add device, like -device on the command line + # root-only: because device_add is + device_del => 'root', # remove device + # root-only: like '-drive' on the commandline + drive_add => 'root', # add drive to PCI storage controller + # root-only: backup to arbitrary target file + drive_backup => 'root', # initiates a point-in-time copy for a device. + # root-only: because drive_add is + drive_del => 'root', # remove host block device + # root-only: mirror to arbitrary target file + drive_mirror => 'root', # initiates live storage migration for a device. + # root-only: dump guest memory into arbitrary target file + 'dump-guest-memory' => 'root', # dump guest memory into file 'filename'. + # root-only: dumps into arbitrary target file + dumpdtb => 'root', # dump the FDT in dtb format to 'filename' + # root-only: starts GDB server on the host + gdbserver => 'root', # start gdbserver on given device (default 'tcp::1234'), stop with 'none' + # root-only: host information leak + gpa2hpa => 'Sys.Modify', # print the host physical address corresponding to a guest physical address + # root-only: host information leak + gpa2hva => 'Sys.Modify', # print the host virtual address corresponding to a guest physical address + # root-only: redirect TCP or UDP connections from host to guest + hostfwd_add => 'root', # redirect TCP or UDP connections from host to guest (requires -net user) + # root-only: because hostfwd_add is + hostfwd_remove => 'root', # remove host-to-guest TCP or UDP redirection + # root-only: read from IO adress space (e.g. PCI devices) + i => 'Sys.Modify', # I/O port read + # root-only: log to arbitrary target file + logfile => 'root', # output logs to 'filename' + # root-only: no guarantee there are no KVM bugs that could afffect the real CPU + mce => 'root', # inject a MCE on the given CPU [and broadcast to other CPUs with -b option] + # root-only: allows to save to arbitrary file + memsave => 'root', # save to disk virtual memory dump starting at 'addr' of size 'size' + # root-only: could specify arbitrary host, also there is 'exec' and 'file' migrations + migrate => 'root', # migrate to URI (using -d to not wait for completion) + # root-only: allows setting arbitrary URI + migrate_incoming => 'root', # Continue an incoming migration from an -incoming defer + # root-only: allows setting arbitrary URI + migrate_recover => 'root', # Continue a paused incoming postcopy migration + # root-only: because nbd_server_start is + nbd_server_add => 'root', # export a block device via NBD + # root-only: because nbd_server_start is + nbd_server_remove => 'root', # remove an export previously exposed via NBD + # root-only: start NBD server on the host + nbd_server_start => 'root', # serve block devices on the given host and port + # root-only: because nbd_server_start is + nbd_server_stop => 'root', # stop serving block devices using the NBD protocol + # root-only: add host network device + netdev_add => 'root', # add host network device + # root-only: because netdev_add is + netdev_del => 'root', # remove host network device + # root-only: no guarantee there are no KVM bugs that could afffect the real CPU + nmi => 'root', # inject an NMI + # root-only: write to IO adress space (e.g. PCI devices) + o => 'root', # I/O port write + # root-only: create arbitrary objects, e.g. serial + object_add => 'root', # create QOM object + # root-only: because object_del is + object_del => 'root', # destroy QOM object + # root-only: inject error on PCIe devices + pcie_aer_inject_error => 'root', # inject pcie aer error + # root-only: save to arbitrary file + pmemsave => 'root', # save to disk physical memory dump starting at 'addr' of size 'size' + # root-only: modify arbitrary object properties + 'qom-set' => 'root', # set QOM property. + # root-only: because savevm-start is + 'savevm-end' => 'root', # Resume VM after snaphot. + # root-only: save VM state to arbitrary target file + 'savevm-start' => 'root', # Prepare for snapshot and halt VM. Save VM state to statefile. + # root-only: dump to arbitrary target file + screendump => 'root', # save screen + # root-only: allows specifying arbitrary target file + snapshot_blkdev => 'root', # initiates a live snapshot of device + # root-only: allows inject-nmi + watchdog_action => 'root', # change watchdog action + # root-only: saves to arbitrary target file + wavcapture => 'root', # capture audio to a wave file + # root-only: not relevant for Proxmox VE + 'xen-event-inject' => 'root', # inject event channel + # root-only: not relevant for Proxmox VE + 'xen-event-list' => 'root', # list event channel state + + announce_self => 'Sys.Modify', # Trigger GARP/RARP announcements + backup_cancel => 'Sys.Modify', # cancel the current VM backup + balloon => 'Sys.Modify', # request VM to change its memory allocation (in MB) + block_job_cancel => 'Sys.Modify', # stop an active background block operation + block_job_complete => 'Sys.Modify', # stop an active background block operation + block_job_pause => 'Sys.Modify', # pause an active background block operation + block_job_resume => 'Sys.Modify', # resume a paused background block operation + block_job_set_speed => 'Sys.Modify', # set maximum speed for a background block operation + block_resize => 'Sys.Modify', # resize a block image + block_set_io_throttle => 'Sys.Modify', # change I/O throttle limits for a block drive + boot_set => 'Sys.Modify', # define new values for the boot device list + calc_dirty_rate => 'Sys.Modify', # start a round of guest dirty rate measurement + cancel_vcpu_dirty_limit => 'Sys.Modify', # cancel dirty page rate limit + 'chardev-send-break' => 'Sys.Modify', # send a break on chardev + closefd => 'Sys.Modify', # close a file descriptor previously passed via SCM rights + commit => 'Sys.Modify', # commit changes to the disk images or backing files + cont => 'Sys.Modify', # resume emulation + c => 'Sys.Modify', # short-form of 'cont' + cpu => 'Sys.Modify', # set the default CPU + delvm => 'Sys.Modify', # delete a VM snapshot from its tag + eject => 'Sys.Modify', # eject a removable medium (use -f to force it) + exit_preconfig => 'Sys.Modify', # exit the preconfig state + expire_password => 'Sys.Modify', # set spice/vnc password expire-time + getfd => 'Sys.Modify', # receive a file descriptor via SCM rights and assign it a name + gva2gpa => 'Sys.Modify', # print the guest physical address corresponding to a guest virtual address + loadvm => 'Sys.Modify', # restore a VM snapshot from its tag + log => 'Sys.Modify', # activate logging of the specified items + migrate_cancel => 'Sys.Modify', # cancel the current VM migration + migrate_continue => 'Sys.Modify', # Continue migration from the given paused state + migrate_pause => 'Sys.Modify', # Pause an ongoing migration (postcopy-only) + migrate_set_capability => 'Sys.Modify', # Enable/Disable the usage of a capability for migration + migrate_set_parameter => 'Sys.Modify', # Set the parameter for migration + migrate_start_postcopy => 'Sys.Modify', # Switch the migration to postcopy mode. + mouse_button => 'Sys.Modify', # change mouse button state (1=L, 2=M, 4=R) + mouse_move => 'Sys.Modify', # send mouse move events + mouse_set => 'Sys.Modify', # set which mouse device receives events + 'one-insn-per-tb' => 'Sys.Modify', # run emulation with one guest instruction per translation block + print => 'Sys.Modify', # print expression value (use $reg for CPU register access) + p => 'Sys.Modify', # alias for 'print' + 'qemu-io' => 'Sys.Modify', # run a qemu-io command on a block device + # decidedly not root-only even if qom-set ist, because it is just too useful + 'qom-get' => 'Sys.Modify', # print QOM property + 'qom-list' => 'Sys.Modify', # list QOM properties + quit => 'Sys.Modify', # quit the emulator + q => 'Sys.Modify', # short-form of 'quit' + replay_break => 'Sys.Modify', # set breakpoint at the specified instruction count + replay_delete_break => 'Sys.Modify', # remove replay breakpoint + replay_seek => 'Sys.Modify', # replay execution to the specified instruction count + ringbuf_read => 'Sys.Modify', # Read from a ring buffer character device + ringbuf_write => 'Sys.Modify', # Write to a ring buffer character device + savevm => 'Sys.Modify', # save a VM snapshot. If no tag is provided, a new snapshot is created + sendkey => 'Sys.Modify', # send keys to the VM + set_link => 'Sys.Modify', # change the link status of a network adapter + set_password => 'Sys.Modify', # set spice/vnc password + set_vcpu_dirty_limit => 'Sys.Modify', # set dirty page rate limit + snapshot_blkdev_internal => 'Sys.Modify', # take an internal snapshot of device. + snapshot_delete_blkdev_internal => 'Sys.Modify', # delete an internal snapshot of device. + stopcapture => 'Sys.Modify', # stop capture + stop => 'Sys.Modify', # stop emulation + s => 'Sys.Modify', # short-form of 'stop' + sum => 'Sys.Modify', # compute the checksum of a memory region + 'sync-profile' => 'Sys.Modify', # enable, disable or reset synchronization profiling. + system_powerdown => 'Sys.Modify', # send system power down event + system_reset => 'Sys.Modify', # reset the system + system_wakeup => 'Sys.Modify', # wakeup guest from suspend + 'trace-event' => 'Sys.Modify', # changes status of a specific trace event + x => 'Sys.Modify', # virtual memory dump starting at 'addr' + x_colo_lost_heartbeat => 'Sys.Modify', # Tell COLO that heartbeat is lost + xp => 'Sys.Modify', # physical memory dump starting at 'addr' +}; + +sub generate_description { + my $cmd_by_priv = {}; + for my $cmd (sort keys $hmp_command_perms->%*) { + push $cmd_by_priv->{$hmp_command_perms->{$cmd}}->@*, $cmd; + } + my $none_cmds = delete($cmd_by_priv->{none}) + or die "internal error - no commands for 'none' found"; + my $root_only_cmds = delete($cmd_by_priv->{'root'}) + or die "internal error no commands for 'root' found"; + + my $text = ''; + $text .= "The following commands do not require any additional privilege: " + . join(', ', $none_cmds->@*) . "\n\n"; + + for my $priv (sort keys $cmd_by_priv->%*) { + $text .= "The following commands require '$priv': " + . join(', ', $cmd_by_priv->{$priv}->@*) . "\n\n"; + } + + $text .= "The following commands are root-only: " . join(', ', $root_only_cmds->@*) . "\n"; +} + +1; diff --git a/src/PVE/API2/Qemu/Makefile b/src/PVE/API2/Qemu/Makefile index e64aa278..7c539702 100644 --- a/src/PVE/API2/Qemu/Makefile +++ b/src/PVE/API2/Qemu/Makefile @@ -2,7 +2,7 @@ DESTDIR= PREFIX=/usr PERLDIR=$(PREFIX)/share/perl5 -SOURCES=Agent.pm CPU.pm Machine.pm +SOURCES=Agent.pm CPU.pm HMPPerms.pm Machine.pm .PHONY: install install: -- 2.47.2 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel