From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id B0FF96161C for ; Thu, 9 Jul 2020 10:26:16 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id A613A10660 for ; Thu, 9 Jul 2020 10:25:46 +0200 (CEST) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 5D0411064E for ; Thu, 9 Jul 2020 10:25:45 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 2771443103 for ; Thu, 9 Jul 2020 10:25:45 +0200 (CEST) From: Wolfgang Bumiller To: pve-devel@pve.proxmox.com Date: Thu, 9 Jul 2020 10:25:42 +0200 Message-Id: <20200709082544.14550-1-w.bumiller@proxmox.com> X-Mailer: git-send-email 2.20.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pve-devel] [PATCH storage 1/3] pbs: encryption support, split "raw client command" API X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 09 Jul 2020 08:26:16 -0000 (And deprecate it...) Signed-off-by: Wolfgang Bumiller --- PVE/Storage/PBSPlugin.pm | 126 +++++++++++++++++++++++++++++++++------ 1 file changed, 109 insertions(+), 17 deletions(-) diff --git a/PVE/Storage/PBSPlugin.pm b/PVE/Storage/PBSPlugin.pm index 092d800..31af450 100644 --- a/PVE/Storage/PBSPlugin.pm +++ b/PVE/Storage/PBSPlugin.pm @@ -4,12 +4,12 @@ package PVE::Storage::PBSPlugin; use strict; use warnings; -use POSIX qw(strftime); -use IO::File; +use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC); use HTTP::Request; -use LWP::UserAgent; +use IO::File; use JSON; -use Data::Dumper; # fixme: remove +use LWP::UserAgent; +use POSIX qw(strftime ENOENT); use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach); use PVE::Storage::Plugin; @@ -37,6 +37,10 @@ sub properties { }, # openssl s_client -connect :8007 2>&1 |openssl x509 -fingerprint -sha256 fingerprint => get_standard_option('fingerprint-sha256'), + encryption_key => { + description => "Encryption key.", + type => 'string', + }, }; } @@ -49,6 +53,7 @@ sub options { content => { optional => 1}, username => { optional => 1 }, password => { optional => 1}, + encryption_key => { optional => 1 }, maxfiles => { optional => 1 }, fingerprint => { optional => 1 }, }; @@ -87,6 +92,52 @@ sub pbs_get_password { return PVE::Tools::file_read_firstline($pwfile); } +sub pbs_encryption_key_file_name { + my ($scfg, $storeid) = @_; + + return "/etc/pve/priv/storage/${storeid}.enc"; +} + +sub pbs_set_encryption_key { + my ($scfg, $storeid, $key) = @_; + + my $pwfile = pbs_encryption_key_file_name($scfg, $storeid); + mkdir "/etc/pve/priv/storage"; + + PVE::Tools::file_set_contents($pwfile, "$key\n"); +} + +sub pbs_delete_encryption_key { + my ($scfg, $storeid) = @_; + + my $pwfile = pbs_encryption_key_file_name($scfg, $storeid); + + unlink $pwfile; +} + +sub pbs_get_encryption_key { + my ($scfg, $storeid) = @_; + + my $pwfile = pbs_encryption_key_file_name($scfg, $storeid); + + return PVE::Tools::file_get_contents($pwfile); +} + +# Returns a file handle if there is an encryption key, or `undef` if there is not. Dies on error. +sub pbs_open_encryption_key { + my ($scfg, $storeid) = @_; + + my $encryption_key_file = pbs_encryption_key_file_name($scfg, $storeid); + + my $keyfd; + if (!open($keyfd, '<', $encryption_key_file)) { + return undef if $! == ENOENT; + die "failed to open encryption key: $encryption_key_file: $!\n"; + } + + return $keyfd; +} + sub print_volid { my ($storeid, $btype, $bid, $btime) = @_; @@ -96,8 +147,8 @@ sub print_volid { return "${storeid}:${volname}"; } -sub run_raw_client_cmd { - my ($scfg, $storeid, $client_cmd, $param, %opts) = @_; +my sub do_raw_client_cmd { + my ($scfg, $storeid, $client_cmd, $param, $can_encrypt, %opts) = @_; my $client_exe = '/usr/bin/proxmox-backup-client'; die "executable not found '$client_exe'! Proxmox backup client not installed?\n" @@ -115,6 +166,20 @@ sub run_raw_client_cmd { push @$cmd, $client_exe, $client_cmd; + # This must live in the top scope to not get closed before the `run_command` + my $keyfd; + if ($can_encrypt) { + if (defined($keyfd = pbs_open_encryption_key($scfg, $storeid))) { + my $flags = fcntl($keyfd, F_GETFD, 0) + // die "failed to get file descriptor flags: $!\n"; + fcntl($keyfd, F_SETFD, $flags & ~FD_CLOEXEC) + or die "failed to remove FD_CLOEXEC from encryption key file descriptor\n"; + push @$cmd, '--crypt-mode=encrypt', '--keyfd='.fileno($keyfd); + } else { + push @$cmd, '--crypt-mode=none'; + } + } + push @$cmd, @$param if defined($param); push @$cmd, "--repository", "$username\@$server:$datastore"; @@ -134,6 +199,18 @@ sub run_raw_client_cmd { run_command($cmd, %opts); } +# FIXME: External perl code should NOT have access to this. +# +# There should be separate functions to +# - make backups +# - restore backups +# - restore files +# with a sane API +sub run_raw_client_cmd{ + my ($scfg, $storeid, $client_cmd, $param, %opts) = @_; + return do_raw_client_cmd($scfg, $storeid, $client_cmd, $param, 1, %opts); +} + sub run_client_cmd { my ($scfg, $storeid, $client_cmd, $param, $no_output) = @_; @@ -145,8 +222,8 @@ sub run_client_cmd { $param = [@$param, '--output-format=json'] if !$no_output; - run_raw_client_cmd($scfg, $storeid, $client_cmd, $param, - outfunc => $outfunc, errmsg => 'proxmox-backup-client failed'); + do_raw_client_cmd($scfg, $storeid, $client_cmd, $param, 0, + outfunc => $outfunc, errmsg => 'proxmox-backup-client failed'); return undef if $no_output; @@ -174,8 +251,8 @@ sub extract_vzdump_config { die "unable to extract configuration for backup format '$format'\n"; } - run_raw_client_cmd($scfg, $storeid, 'restore', [ $name, $config_name, '-' ], - outfunc => $outfunc, errmsg => 'proxmox-backup-client failed'); + do_raw_client_cmd($scfg, $storeid, 'restore', [ $name, $config_name, '-' ], 0, + outfunc => $outfunc, errmsg => 'proxmox-backup-client failed'); return $config; } @@ -183,22 +260,36 @@ sub extract_vzdump_config { sub on_add_hook { my ($class, $storeid, $scfg, %param) = @_; - if (defined($param{password})) { - pbs_set_password($scfg, $storeid, $param{password}); + if (defined(my $password = $param{password})) { + pbs_set_password($scfg, $storeid, $password); } else { pbs_delete_password($scfg, $storeid); } + + if (defined(my $encryption_key = delete($scfg->{encryption_key}))) { + pbs_set_encryption_key($scfg, $storeid, $encryption_key); + } else { + pbs_delete_encryption_key($scfg, $storeid); + } } sub on_update_hook { my ($class, $storeid, $scfg, %param) = @_; - return if !exists($param{password}); + if (exists($param{password})) { + if (defined($param{password})) { + pbs_set_password($scfg, $storeid, $param{password}); + } else { + pbs_delete_password($scfg, $storeid); + } + } - if (defined($param{password})) { - pbs_set_password($scfg, $storeid, $param{password}); - } else { - pbs_delete_password($scfg, $storeid); + if (exists($scfg->{encryption_key})) { + if (defined(my $encryption_key = delete($scfg->{encryption_key}))) { + pbs_set_encryption_key($scfg, $storeid, $encryption_key); + } else { + pbs_delete_encryption_key($scfg, $storeid); + } } } @@ -206,6 +297,7 @@ sub on_delete_hook { my ($class, $storeid, $scfg) = @_; pbs_delete_password($scfg, $storeid); + pbs_delete_encryption_key($scfg, $storeid); } sub parse_volname { -- 2.20.1