all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: "Fabian Grünbichler" <f.gruenbichler@proxmox.com>
To: Proxmox VE development discussion <pve-devel@lists.proxmox.com>
Subject: Re: [pve-devel] [PATCH v6 qemu-server 2/6] mtunnel: add API endpoints
Date: Mon, 03 Oct 2022 09:11:50 +0200	[thread overview]
Message-ID: <1664780846.jd1ah2uiwp.astroid@nora.none> (raw)
In-Reply-To: <7e10feea-3c99-232b-b556-8361b95886a8@proxmox.com>

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.gruenbichler@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
> 




  reply	other threads:[~2022-10-03  7:12 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-09-28 12:50 [pve-devel] [PATCH-SERIES v6 0/13] remote migration Fabian Grünbichler
2022-09-28 12:50 ` [pve-devel] [PATCH v6 access-control 1/1] privs: add Sys.Incoming Fabian Grünbichler
2022-11-07 15:38   ` [pve-devel] applied: " Thomas Lamprecht
2022-09-28 12:50 ` [pve-devel] [PATCH v6 common 1/1] schema: take over 'pve-targetstorage' option Fabian Grünbichler
2022-11-07 15:31   ` [pve-devel] applied: " Thomas Lamprecht
2022-09-28 12:50 ` [pve-devel] [PATCH v6 container 1/3] migration: add remote migration Fabian Grünbichler
2022-10-03 13:22   ` [pve-devel] [PATCH FOLLOW-UP " Fabian Grünbichler
2022-09-28 12:50 ` [pve-devel] [PATCH v6 container 2/3] pct: add 'remote-migrate' command Fabian Grünbichler
2022-09-28 12:50 ` [pve-devel] [PATCH v6 container 3/3] migrate: print mapped volume in error Fabian Grünbichler
2022-09-28 12:50 ` [pve-devel] [PATCH v6 docs 1/1] pveum: mention Sys.Incoming privilege Fabian Grünbichler
2022-11-07 15:45   ` [pve-devel] applied: " Thomas Lamprecht
2022-09-28 12:50 ` [pve-devel] [PATCH v6 qemu-server 1/6] schema: move 'pve-targetstorage' to pve-common Fabian Grünbichler
2022-11-07 15:31   ` [pve-devel] applied: " Thomas Lamprecht
2022-09-28 12:50 ` [pve-devel] [PATCH v6 qemu-server 2/6] mtunnel: add API endpoints Fabian Grünbichler
2022-09-30 11:52   ` Stefan Hanreich
2022-10-03  7:11     ` Fabian Grünbichler [this message]
2022-10-03 13:22   ` [pve-devel] [PATCH FOLLOW-UP " Fabian Grünbichler
2022-10-18  6:23   ` [pve-devel] [PATCH " DERUMIER, Alexandre
2022-09-28 12:50 ` [pve-devel] [PATCH v6 qemu-server 3/6] migrate: refactor remote VM/tunnel start Fabian Grünbichler
2022-09-28 12:50 ` [pve-devel] [PATCH v6 qemu-server 4/6] migrate: add remote migration handling Fabian Grünbichler
2022-09-28 12:50 ` [pve-devel] [PATCH v6 qemu-server 5/6] api: add remote migrate endpoint Fabian Grünbichler
2022-09-28 12:50 ` [pve-devel] [PATCH v6 qemu-server 6/6] qm: add remote-migrate command Fabian Grünbichler
2022-10-17 14:40   ` DERUMIER, Alexandre
2022-10-18  6:39     ` Thomas Lamprecht
2022-10-18  6:56       ` DERUMIER, Alexandre
2022-10-17 17:22   ` DERUMIER, Alexandre
2022-09-28 12:50 ` [pve-devel] [PATCH v6 storage 1/1] (remote) export: check and untaint format Fabian Grünbichler
2022-09-29 12:39   ` [pve-devel] applied: " Thomas Lamprecht
2022-10-04 15:29 ` [pve-devel] [PATCH-SERIES v6 0/13] remote migration DERUMIER, Alexandre

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1664780846.jd1ah2uiwp.astroid@nora.none \
    --to=f.gruenbichler@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal