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 889776058F for ; Fri, 5 Feb 2021 16:35:59 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 86939AEAC for ; Fri, 5 Feb 2021 16:35:59 +0100 (CET) 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 BF920AEA2 for ; Fri, 5 Feb 2021 16:35:58 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 8B175461F3 for ; Fri, 5 Feb 2021 16:35:58 +0100 (CET) From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= To: pbs-devel@lists.proxmox.com Date: Fri, 5 Feb 2021 16:35:27 +0100 Message-Id: <20210205153535.2578184-3-f.gruenbichler@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210205153535.2578184-1-f.gruenbichler@proxmox.com> References: <20210205153535.2578184-1-f.gruenbichler@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.026 Adjusted score from AWL reputation of From: address 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [config.pm, pvesm.pm, pbsplugin.pm] Subject: [pbs-devel] [PATCH storage] pbs: allow setting up a master key X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 05 Feb 2021 15:35:59 -0000 similar to the existing encryption key handling, but without auto-generation since we only have the public part here. Signed-off-by: Fabian Grünbichler --- requires a versioned dep on PBS client supporting these features. PVE/API2/Storage/Config.pm | 2 +- PVE/CLI/pvesm.pm | 14 +++++- PVE/Storage/PBSPlugin.pm | 89 +++++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 4 deletions(-) diff --git a/PVE/API2/Storage/Config.pm b/PVE/API2/Storage/Config.pm index 00abd13..ea655c5 100755 --- a/PVE/API2/Storage/Config.pm +++ b/PVE/API2/Storage/Config.pm @@ -112,7 +112,7 @@ __PACKAGE__->register_method ({ return &$api_storage_config($cfg, $param->{storage}); }}); -my $sensitive_params = [qw(password encryption-key)]; +my $sensitive_params = [qw(password encryption-key master-pubkey)]; __PACKAGE__->register_method ({ name => 'create', diff --git a/PVE/CLI/pvesm.pm b/PVE/CLI/pvesm.pm index 3594774..fe147d9 100755 --- a/PVE/CLI/pvesm.pm +++ b/PVE/CLI/pvesm.pm @@ -6,6 +6,7 @@ use warnings; use POSIX qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC); use Fcntl ':flock'; use File::Path; +use MIME::Base64 qw(encode_base64); use PVE::SafeSyslog; use PVE::Cluster; @@ -50,13 +51,22 @@ sub param_mapping { } }; + my $master_key_map = { + name => 'master-pubkey', + desc => 'a file containing a PEM-formatted master public key', + func => sub { + my ($value) = @_; + return encode_base64(PVE::Tools::file_get_contents($value), ''); + } + }; + my $mapping = { 'cifsscan' => [ $password_map ], 'cifs' => [ $password_map ], 'pbs' => [ $password_map ], - 'create' => [ $password_map, $enc_key_map ], - 'update' => [ $password_map, $enc_key_map ], + 'create' => [ $password_map, $enc_key_map, $master_key_map ], + 'update' => [ $password_map, $enc_key_map, $master_key_map ], }; return $mapping->{$name}; } diff --git a/PVE/Storage/PBSPlugin.pm b/PVE/Storage/PBSPlugin.pm index 6c6816e..ba6e197 100644 --- a/PVE/Storage/PBSPlugin.pm +++ b/PVE/Storage/PBSPlugin.pm @@ -8,6 +8,7 @@ use warnings; use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC); use IO::File; use JSON; +use MIME::Base64 qw(decode_base64); use POSIX qw(strftime ENOENT); use PVE::APIClient::LWP; @@ -43,6 +44,10 @@ sub properties { description => "Encryption key. Use 'autogen' to generate one automatically without passphrase.", type => 'string', }, + 'master-pubkey' => { + description => "Public part of a master key pair. An encrypted copy of the encryption-key will be added to each encrypted backup.", + type => 'string', + }, port => { description => "For non default port.", type => 'integer', @@ -64,6 +69,7 @@ sub options { username => { optional => 1 }, password => { optional => 1 }, 'encryption-key' => { optional => 1 }, + 'master-pubkey' => { optional => 1 }, maxfiles => { optional => 1 }, 'prune-backups' => { optional => 1 }, fingerprint => { optional => 1 }, @@ -153,6 +159,56 @@ sub pbs_open_encryption_key { return $keyfd; } +sub pbs_master_pubkey_file_name { + my ($scfg, $storeid) = @_; + + return "/etc/pve/priv/storage/${storeid}.master.pem"; +} + +sub pbs_set_master_pubkey { + my ($scfg, $storeid, $key) = @_; + + my $pwfile = pbs_master_pubkey_file_name($scfg, $storeid); + mkdir "/etc/pve/priv/storage"; + + PVE::Tools::file_set_contents($pwfile, "$key\n"); +} + +sub pbs_delete_master_pubkey { + my ($scfg, $storeid) = @_; + + my $pwfile = pbs_master_pubkey_file_name($scfg, $storeid); + + if (!unlink $pwfile) { + return if $! == ENOENT; + die "failed to delete master public key! $!\n"; + } + delete $scfg->{'master-pubkey'}; +} + +sub pbs_get_master_pubkey { + my ($scfg, $storeid) = @_; + + my $pwfile = pbs_master_pubkey_file_name($scfg, $storeid); + + return PVE::Tools::file_get_contents($pwfile); +} + +# Returns a file handle if there is a master key, or `undef` if there is not. Dies on error. +sub pbs_open_master_pubkey { + my ($scfg, $storeid) = @_; + + my $master_pubkey_file = pbs_master_pubkey_file_name($scfg, $storeid); + + my $keyfd; + if (!open($keyfd, '<', $master_pubkey_file)) { + return undef if $! == ENOENT; + die "failed to open master public key: $master_pubkey_file: $!\n"; + } + + return $keyfd; +} + sub print_volid { my ($storeid, $btype, $bid, $btime) = @_; @@ -188,7 +244,7 @@ my sub do_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; + my ($keyfd, $master_fd); if ($use_crypto) { if (defined($keyfd = pbs_open_encryption_key($scfg, $storeid))) { my $flags = fcntl($keyfd, F_GETFD, 0) @@ -196,6 +252,13 @@ my sub do_raw_client_cmd { 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); + if (defined($master_fd = pbs_open_master_pubkey($scfg, $storeid))) { + my $flags = fcntl($master_fd, F_GETFD, 0) + // die "failed to get file descriptor flags: $!\n"; + fcntl($master_fd, F_SETFD, $flags & ~FD_CLOEXEC) + or die "failed to remove FD_CLOEXEC from master public key file descriptor\n"; + push @$cmd, '--master-pubkey-fd='.fileno($master_fd); + } } else { push @$cmd, '--crypt-mode=none'; } @@ -394,6 +457,17 @@ sub on_add_hook { pbs_delete_encryption_key($scfg, $storeid); } + if (defined(my $master_key = delete $param{'master-pubkey'})) { + die "'master-pubkey' can only be used together with 'encryption-key'\n" + if !defined($scfg->{'encryption-key'}); + + my $decoded = decode_base64($master_key); + pbs_set_master_pubkey($scfg, $storeid, $decoded); + $scfg->{'master-pubkey'} = 1; + } else { + pbs_delete_master_pubkey($scfg, $storeid); + } + return $res; } @@ -427,6 +501,18 @@ sub on_update_hook { $scfg->{'encryption-key'} = $decoded_key->{fingerprint} || 1; } else { pbs_delete_encryption_key($scfg, $storeid); + delete $scfg->{'encryption-key'}; + } + } + + if (exists($param{'master-pubkey'})) { + if (defined(my $master_key = delete($param{'master-pubkey'}))) { + my $decoded = decode_base64($master_key); + + pbs_set_master_pubkey($scfg, $storeid, $decoded); + $scfg->{'master-pubkey'} = 1; + } else { + pbs_delete_master_pubkey($scfg, $storeid); } } @@ -438,6 +524,7 @@ sub on_delete_hook { pbs_delete_password($scfg, $storeid); pbs_delete_encryption_key($scfg, $storeid); + pbs_delete_master_pubkey($scfg, $storeid); return; } -- 2.20.1