On September 30, 2022 1:52 pm, Stefan Hanreich wrote:
> 
> 
> On 9/28/22 14:50, Fabian Grünbichler wrote:
>> the following two endpoints are used for migration on the remote side
>> 
>> POST /nodes/NODE/qemu/VMID/mtunnel
>> 
>> which creates and locks an empty VM config, and spawns the main qmtunnel
>> worker which binds to a VM-specific UNIX socket.
>> 
>> this worker handles JSON-encoded migration commands coming in via this
>> UNIX socket:
>> - config (set target VM config)
>> -- checks permissions for updating config
>> -- strips pending changes and snapshots
>> -- sets (optional) firewall config
>> - disk (allocate disk for NBD migration)
>> -- checks permission for target storage
>> -- returns drive string for allocated volume
>> - disk-import, query-disk-import, bwlimit
>> -- handled by PVE::StorageTunnel
>> - start (returning migration info)
>> - fstrim (via agent)
>> - ticket (creates a ticket for a WS connection to a specific socket)
>> - resume
>> - stop
>> - nbdstop
>> - unlock
>> - quit (+ cleanup)
>> 
>> this worker serves as a replacement for both 'qm mtunnel' and various
>> manual calls via SSH. the API call will return a ticket valid for
>> connecting to the worker's UNIX socket via a websocket connection.
>> 
>> GET+WebSocket upgrade /nodes/NODE/qemu/VMID/mtunnelwebsocket
>> 
>> gets called for connecting to a UNIX socket via websocket forwarding,
>> i.e. once for the main command mtunnel, and once each for the memory
>> migration and each NBD drive-mirror/storage migration.
>> 
>> access is guarded by a short-lived ticket binding the authenticated user
>> to the socket path. such tickets can be requested over the main mtunnel,
>> which keeps track of socket paths currently used by that
>> mtunnel/migration instance.
>> 
>> each command handler should check privileges for the requested action if
>> necessary.
>> 
>> both mtunnel and mtunnelwebsocket endpoints are not proxied, the
>> client/caller is responsible for ensuring the passed 'node' parameter
>> and the endpoint handling the call are matching.
>> 
>> Signed-off-by: Fabian Grünbichler <f.gruenbich...@proxmox.com>
>> ---
>> 
>> Notes:
>>      v6:
>>      - check for Sys.Incoming in mtunnel
>>      - add definedness checks in 'config' command
>>      - switch to vm_running_locally in 'resume' command
>>      - moved $socket_addr closer to usage
>>      v5:
>>      - us vm_running_locally
>>      - move '$socket_addr' declaration closer to usage
>>      v4:
>>      - add timeout to accept()
>>      - move 'bwlimit' to PVE::StorageTunnel and extend it
>>      - mark mtunnel(websocket) as non-proxied, and check $node accordingly
>>      v3:
>>      - handle meta and vmgenid better
>>      - handle failure of 'config' updating
>>      - move 'disk-import' and 'query-disk-import' handlers to 
>> pve-guest-common
>>      - improve tunnel exit by letting client close the connection
>>      - use strict VM config parser
>>      v2: incorporated Fabian Ebner's feedback, mainly:
>>      - use modified nbd alloc helper instead of duplicating
>>      - fix disk cleanup, also cleanup imported disks
>>      - fix firewall-conf vs firewall-config mismatch
>>      
>>      requires
>>      - pve-access-control with tunnel ticket support (already marked in 
>> d/control)
>>      - pve-access-control with Sys.Incoming privilege (not yet 
>> applied/bumped!)
>>      - pve-http-server with websocket fixes (could be done via breaks? or 
>> bumped in
>>        pve-manager..)
>> 
>>   PVE/API2/Qemu.pm | 527 ++++++++++++++++++++++++++++++++++++++++++++++-
>>   debian/control   |   2 +-
>>   2 files changed, 527 insertions(+), 2 deletions(-)
>> 
>> diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
>> index 3ec31c26..9270ca74 100644
>> --- a/PVE/API2/Qemu.pm
>> +++ b/PVE/API2/Qemu.pm
>> @@ -4,10 +4,13 @@ use strict;
>>   use warnings;
>>   use Cwd 'abs_path';
>>   use Net::SSLeay;
>> -use POSIX;
>>   use IO::Socket::IP;
>> +use IO::Socket::UNIX;
>> +use IPC::Open3;
>> +use JSON;
>>   use URI::Escape;
>>   use Crypt::OpenSSL::Random;
>> +use Socket qw(SOCK_STREAM);
>>   
>>   use PVE::Cluster qw (cfs_read_file cfs_write_file);;
>>   use PVE::RRD;
>> @@ -38,6 +41,7 @@ use PVE::VZDump::Plugin;
>>   use PVE::DataCenterConfig;
>>   use PVE::SSHInfo;
>>   use PVE::Replication;
>> +use PVE::StorageTunnel;
>>   
>>   BEGIN {
>>       if (!$ENV{PVE_GENERATING_DOCS}) {
>> @@ -1087,6 +1091,7 @@ __PACKAGE__->register_method({
>>          { subdir => 'spiceproxy' },
>>          { subdir => 'sendkey' },
>>          { subdir => 'firewall' },
>> +        { subdir => 'mtunnel' },
>>          ];
>>   
>>      return $res;
>> @@ -4965,4 +4970,524 @@ __PACKAGE__->register_method({
>>      return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, 
>> $param->{vmid}, $param->{type});
>>       }});
>>   
>> +__PACKAGE__->register_method({
>> +    name => 'mtunnel',
>> +    path => '{vmid}/mtunnel',
>> +    method => 'POST',
>> +    protected => 1,
>> +    description => 'Migration tunnel endpoint - only for internal use by VM 
>> migration.',
>> +    permissions => {
>> +    check =>
>> +    [ 'and',
>> +      ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
>> +      ['perm', '/', [ 'Sys.Incoming' ]],
>> +    ],
>> +    description => "You need 'VM.Allocate' permissions on '/vms/{vmid}' and 
>> Sys.Incoming" .
>> +                   " on '/'. Further permission checks happen during the 
>> actual migration.",
>> +    },
>> +    parameters => {
>> +    additionalProperties => 0,
>> +    properties => {
>> +        node => get_standard_option('pve-node'),
>> +        vmid => get_standard_option('pve-vmid'),
>> +        storages => {
>> +            type => 'string',
>> +            format => 'pve-storage-id-list',
>> +            optional => 1,
>> +            description => 'List of storages to check permission and 
>> availability. Will be checked again for all actually used storages during 
>> migration.',
>> +        },
>> +    },
>> +    },
>> +    returns => {
>> +    additionalProperties => 0,
>> +    properties => {
>> +        upid => { type => 'string' },
>> +        ticket => { type => 'string' },
>> +        socket => { type => 'string' },
>> +    },
>> +    },
>> +    code => sub {

[...]

>> +            my $cmd = delete $parsed->{cmd};
>> +            if (!defined($cmd)) {
>> +                $reply_err->("'cmd' missing");
>> +            } elsif ($state->{exit}) {
>> +                $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd 
>> not possible");
>> +                next;
>> +            } elsif (my $handler = $cmd_handlers->{$cmd}) {
>> +                print "received command '$cmd'\n";
>> +                eval {
>> +                    if ($cmd_desc->{$cmd}) {
>> +                        PVE::JSONSchema::validate($cmd_desc->{$cmd}, 
>> $parsed);
> 
> might the params be flipped here?
> 

yes! thanks for catching (and wow - our schema handling is flexible that 
it never choked on that!).

I'll do some tests and see whether flipping breaks anything (the same is 
also true for pve-container, since this part is duplicated there).

>> +                    } else {
>> +                        $parsed = {};
>> +                    }
>> +                    my $res = $run_locked->($handler, $parsed);
>> +                    $reply_ok->($res);
>> +                };
>> +                $reply_err->("failed to handle '$cmd' command - $@")
>> +                    if $@;
>> +            } else {
>> +                $reply_err->("unknown command '$cmd' given");
>> +            }
>> +        }
>> +
>> +        if ($state->{exit}) {
>> +            print "mtunnel exited\n";
>> +        } else {
>> +            die "mtunnel exited unexpectedly\n";
>> +        }
>> +    };
>> +
>> +    my $socket_addr = "/run/qemu-server/$vmid.mtunnel";
>> +    my $ticket = PVE::AccessControl::assemble_tunnel_ticket($authuser, 
>> "/socket/$socket_addr");
>> +    my $upid = $rpcenv->fork_worker('qmtunnel', $vmid, $authuser, $realcmd);
>> +
>> +    return {
>> +        ticket => $ticket,
>> +        upid => $upid,
>> +        socket => $socket_addr,
>> +    };
>> +    }});
>> +
>> +__PACKAGE__->register_method({
>> +    name => 'mtunnelwebsocket',
>> +    path => '{vmid}/mtunnelwebsocket',
>> +    method => 'GET',
>> +    permissions => {
>> +    description => "You need to pass a ticket valid for the selected 
>> socket. Tickets can be created via the mtunnel API call, which will check 
>> permissions accordingly.",
>> +        user => 'all', # check inside
>> +    },
>> +    description => 'Migration tunnel endpoint for websocket upgrade - only 
>> for internal use by VM migration.',
>> +    parameters => {
>> +    additionalProperties => 0,
>> +    properties => {
>> +        node => get_standard_option('pve-node'),
>> +        vmid => get_standard_option('pve-vmid'),
>> +        socket => {
>> +            type => "string",
>> +            description => "unix socket to forward to",
>> +        },
>> +        ticket => {
>> +            type => "string",
>> +            description => "ticket return by initial 'mtunnel' API call, or 
>> retrieved via 'ticket' tunnel command",
>> +        },
>> +    },
>> +    },
>> +    returns => {
>> +    type => "object",
>> +    properties => {
>> +        port => { type => 'string', optional => 1 },
>> +        socket => { type => 'string', optional => 1 },
>> +    },
>> +    },
>> +    code => sub {
>> +    my ($param) = @_;
>> +
>> +    my $rpcenv = PVE::RPCEnvironment::get();
>> +    my $authuser = $rpcenv->get_user();
>> +
>> +    my $nodename = PVE::INotify::nodename();
>> +    my $node = extract_param($param, 'node');
>> +
>> +    raise_param_exc({ node => "node needs to be 'localhost' or local 
>> hostname '$nodename'" })
>> +        if $node ne 'localhost' && $node ne $nodename;
>> +
>> +    my $vmid = $param->{vmid};
>> +    # check VM exists
>> +    PVE::QemuConfig->load_config($vmid);
>> +
>> +    my $socket = $param->{socket};
>> +    PVE::AccessControl::verify_tunnel_ticket($param->{ticket}, $authuser, 
>> "/socket/$socket");
>> +
>> +    return { socket => $socket };
>> +    }});
>> +
>>   1;
>> diff --git a/debian/control b/debian/control
>> index a90ecd6f..ce469cbd 100644
>> --- a/debian/control
>> +++ b/debian/control
>> @@ -33,7 +33,7 @@ Depends: dbus,
>>            libjson-perl,
>>            libjson-xs-perl,
>>            libnet-ssleay-perl,
>> -         libpve-access-control (>= 5.0-7),
>> +         libpve-access-control (>= 7.0-7),
>>            libpve-cluster-perl,
>>            libpve-common-perl (>= 7.1-4),
>>            libpve-guest-common-perl (>= 4.1-1),
> 
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
> 


_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to