mirror of
https://git.proxmox.com/git/qemu-server
synced 2025-05-03 14:38:31 +00:00
enable cluster mapped USB devices for guests
this patch allows configuring usb devices that are mapped via cluster resource mapping when the user has 'Mapping.Use' on the ACL path '/mapping/usb/{ID}' (in addition to the usual required vm config privileges) for now, this is only valid if there is exactly one mapping for the host, since we don't track passed through usb devices yet This now also checks permissions on clone/restore, meaning a 'non-mapped' device can only be cloned/restored as root@pam user. That is a breaking change. Refactor the checks for restoring into a sub, so we have central place where we can add such checks Signed-off-by: Dominik Csapak <d.csapak@proxmox.com> Tested-By: Markus Frank <m.frank@proxmox.com>
This commit is contained in:
parent
0cf8d56c6d
commit
e3971865b4
@ -32,6 +32,7 @@ use PVE::QemuServer::Drive;
|
||||
use PVE::QemuServer::ImportDisk;
|
||||
use PVE::QemuServer::Monitor qw(mon_cmd);
|
||||
use PVE::QemuServer::Machine;
|
||||
use PVE::QemuServer::USB qw(parse_usb_device);
|
||||
use PVE::QemuMigrate;
|
||||
use PVE::RPCEnvironment;
|
||||
use PVE::AccessControl;
|
||||
@ -588,11 +589,15 @@ my sub check_usb_perm {
|
||||
|
||||
return 1 if $authuser eq 'root@pam';
|
||||
|
||||
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
|
||||
|
||||
my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb', $value);
|
||||
if ($device->{host} =~ m/^spice$/i) {
|
||||
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
|
||||
} else {
|
||||
if ($device->{host} && $device->{host} !~ m/^spice$/i) {
|
||||
die "only root can set '$opt' config for real devices\n";
|
||||
} elsif ($device->{mapping}) {
|
||||
$rpcenv->check_full($authuser, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
|
||||
} else {
|
||||
die "either 'host' or 'mapping' must be set.\n";
|
||||
}
|
||||
|
||||
return 1;
|
||||
@ -3517,6 +3522,7 @@ __PACKAGE__->register_method({
|
||||
my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
|
||||
|
||||
my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
|
||||
PVE::QemuServer::check_mapping_access($rpcenv, $authuser, $oldconf);
|
||||
|
||||
PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $oldconf);
|
||||
|
||||
|
@ -6473,6 +6473,31 @@ sub check_bridge_access {
|
||||
return 1;
|
||||
};
|
||||
|
||||
sub check_mapping_access {
|
||||
my ($rpcenv, $user, $conf) = @_;
|
||||
|
||||
for my $opt (keys $conf->%*) {
|
||||
if ($opt =~ m/^usb\d+$/) {
|
||||
my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb', $conf->{$opt});
|
||||
if (my $host = $device->{host}) {
|
||||
die "only root can set '$opt' config for real devices\n"
|
||||
if $host !~ m/^spice$/i && $user ne 'root@pam';
|
||||
} elsif ($device->{mapping}) {
|
||||
$rpcenv->check_full($user, "/mapping/usb/$device->{mapping}", ['Mapping.Use']);
|
||||
} else {
|
||||
die "either 'host' or 'mapping' must be set.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
# FIXME: improve checks on restore by checking before actually extracing and
|
||||
# merging the new config
|
||||
sub check_restore_permissions {
|
||||
my ($rpcenv, $user, $conf) = @_;
|
||||
check_bridge_access($rpcenv, $user, $conf);
|
||||
check_mapping_access($rpcenv, $user, $conf);
|
||||
}
|
||||
# vzdump restore implementaion
|
||||
|
||||
sub tar_archive_read_firstfile {
|
||||
@ -7117,7 +7142,7 @@ sub restore_proxmox_backup_archive {
|
||||
}
|
||||
|
||||
my $new_conf = $restore_merge_config->($conffile, $new_conf_raw, $options->{override_conf});
|
||||
check_bridge_access($rpcenv, $user, $new_conf);
|
||||
check_restore_permissions($rpcenv, $user, $new_conf);
|
||||
PVE::QemuConfig->write_config($vmid, $new_conf);
|
||||
|
||||
eval { rescan($vmid, 1); };
|
||||
@ -7431,7 +7456,7 @@ sub restore_vma_archive {
|
||||
}
|
||||
|
||||
my $new_conf = $restore_merge_config->($conffile, $new_conf_raw, $opts->{override_conf});
|
||||
check_bridge_access($rpcenv, $user, $new_conf);
|
||||
check_restore_permissions($rpcenv, $user, $new_conf);
|
||||
PVE::QemuConfig->write_config($vmid, $new_conf);
|
||||
|
||||
eval { rescan($vmid, 1); };
|
||||
|
@ -6,6 +6,7 @@ use PVE::QemuServer::PCI qw(print_pci_addr);
|
||||
use PVE::QemuServer::Machine;
|
||||
use PVE::QemuServer::Helpers qw(min_version windows_version);
|
||||
use PVE::JSONSchema;
|
||||
use PVE::Mapping::USB;
|
||||
use base 'Exporter';
|
||||
|
||||
our @EXPORT_OK = qw(
|
||||
@ -24,6 +25,7 @@ my $USB_PATH_RE = qr/(\d+)\-(\d+(\.\d+)*)/;
|
||||
my $usb_fmt = {
|
||||
host => {
|
||||
default_key => 1,
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
pattern => qr/(?:(?:$USB_ID_RE)|(?:$USB_PATH_RE)|[Ss][Pp][Ii][Cc][Ee])/,
|
||||
format_description => 'HOSTUSBDEVICE|spice',
|
||||
@ -40,8 +42,18 @@ NOTE: This option allows direct access to host hardware. So it is no longer poss
|
||||
machines - use with special care.
|
||||
|
||||
The value 'spice' can be used to add a usb redirection devices for spice.
|
||||
|
||||
Either this or the 'mapping' key must be set.
|
||||
EODESCR
|
||||
},
|
||||
mapping => {
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
format_description => 'mapping-id',
|
||||
format => 'pve-configid',
|
||||
description => "The ID of a cluster wide mapping. Either this or the default-key 'host'"
|
||||
." must be set.",
|
||||
},
|
||||
usb3 => {
|
||||
optional => 1,
|
||||
type => 'boolean',
|
||||
@ -63,21 +75,38 @@ our $usbdesc = {
|
||||
PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
|
||||
|
||||
sub parse_usb_device {
|
||||
my ($value) = @_;
|
||||
my ($value, $mapping) = @_;
|
||||
|
||||
return if !$value;
|
||||
return if $value && $mapping; # not a valid configuration
|
||||
|
||||
my $res = {};
|
||||
if ($value =~ m/^$USB_ID_RE$/) {
|
||||
$res->{vendorid} = $2;
|
||||
$res->{productid} = $4;
|
||||
} elsif ($value =~ m/^$USB_PATH_RE$/) {
|
||||
$res->{hostbus} = $1;
|
||||
$res->{hostport} = $2;
|
||||
} elsif ($value =~ m/^spice$/i) {
|
||||
$res->{spice} = 1;
|
||||
} else {
|
||||
return;
|
||||
if (defined($value)) {
|
||||
if ($value =~ m/^$USB_ID_RE$/) {
|
||||
$res->{vendorid} = $2;
|
||||
$res->{productid} = $4;
|
||||
} elsif ($value =~ m/^$USB_PATH_RE$/) {
|
||||
$res->{hostbus} = $1;
|
||||
$res->{hostport} = $2;
|
||||
} elsif ($value =~ m/^spice$/i) {
|
||||
$res->{spice} = 1;
|
||||
}
|
||||
} elsif (defined($mapping)) {
|
||||
my $devices = PVE::Mapping::USB::find_on_current_node($mapping);
|
||||
die "USB device mapping not found for '$mapping'\n" if !$devices || !scalar($devices->@*);
|
||||
die "More than one USB mapping per host not supported\n" if scalar($devices->@*) > 1;
|
||||
eval {
|
||||
PVE::Mapping::USB::assert_valid($mapping, $devices->[0]);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
die "USB Mapping invalid (hardware probably changed): $err\n";
|
||||
}
|
||||
my $device = $devices->[0];
|
||||
|
||||
if ($device->{path}) {
|
||||
$res = parse_usb_device($device->{path});
|
||||
} else {
|
||||
$res = parse_usb_device($device->{id});
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
@ -199,7 +228,7 @@ sub print_usbdevice_full {
|
||||
$usbdevice .= ",port=$port" if defined($port);
|
||||
}
|
||||
|
||||
my $parsed = parse_usb_device($device->{host});
|
||||
my $parsed = parse_usb_device($device->{host}, $device->{mapping});
|
||||
|
||||
if (defined($parsed->{vendorid}) && defined($parsed->{productid})) {
|
||||
$usbdevice .= ",vendorid=0x$parsed->{vendorid},productid=0x$parsed->{productid}";
|
||||
|
Loading…
Reference in New Issue
Block a user