From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 0F61E1FF13B for ; Wed, 03 Jun 2026 20:05:02 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 1E3961566B; Wed, 3 Jun 2026 20:05:01 +0200 (CEST) From: Stoiko Ivanov To: pmg-devel@lists.proxmox.com Subject: [PATCH pve-common 02/15] pbs-client: add support for master public key Date: Wed, 3 Jun 2026 20:03:04 +0200 Message-ID: <20260603180445.98770-3-s.ivanov@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260603180445.98770-1-s.ivanov@proxmox.com> References: <20260603180445.98770-1-s.ivanov@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1780509859365 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.087 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: SKESOTQWJOWORUEBWITFSJ7ZEN7PO64X X-Message-ID-Hash: SKESOTQWJOWORUEBWITFSJ7ZEN7PO64X X-MailFrom: s.ivanov@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Mail Gateway development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: adapted from PVE::Storage::PBSPlugin originally introduced there with c56f7a7 ("pbs: allow setting up a master key") Signed-off-by: Stoiko Ivanov --- src/PVE/PBSClient.pm | 59 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/PVE/PBSClient.pm b/src/PVE/PBSClient.pm index d8dd3f0..34ce843 100644 --- a/src/PVE/PBSClient.pm +++ b/src/PVE/PBSClient.pm @@ -132,6 +132,51 @@ my sub open_encryption_key { return $keyfd; } +sub master_pubkey_file_name { + my ($self) = @_; + + return "$self->{secret_dir}/$self->{storeid}.master.pem"; +} + +sub set_master_pubkey { + my ($self, $key) = @_; + + my $master_pubkey_file = $self->master_pubkey_file_name(); + mkdir($self->{secret_dir}); + + PVE::Tools::file_set_contents($master_pubkey_file, "$key\n", 0600); +} + +sub delete_master_pubkey { + my ($self) = @_; + + my $master_pubkey_file = $self->master_pubkey_file_name(); + + if (!unlink($master_pubkey_file)) { + return if $! == ENOENT; + die "failed to delete the master public key! $!\n"; + } +} + +# Returns a file handle if there is a master key, or `undef` if there is not. Dies on error. +sub open_master_pubkey { + my ($self) = @_; + + my $master_pubkey_file = $self->master_pubkey_file_name(); + + my $keyfd; + if (!open($keyfd, '<', $master_pubkey_file)) { + if ($! == ENOENT) { + die "master public key configured but no key file found!\n" + if $self->{'master-pubkey'}; + return undef; + } + die "failed to open master public key: $master_pubkey_file: $!\n"; + } + + return $keyfd; +} + my $USE_CRYPT_PARAMS = { 'proxmox-backup-client' => { backup => 1, @@ -144,11 +189,16 @@ my $USE_CRYPT_PARAMS = { }, }; +my $USE_MASTER_KEY = { + backup => 1, +}; + my sub do_raw_client_cmd { my ($self, $client_cmd, $param, %opts) = @_; my $client_bin = delete($opts{binary}) || 'proxmox-backup-client'; my $use_crypto = $USE_CRYPT_PARAMS->{$client_bin}->{$client_cmd} // 0; + my $use_master = $USE_MASTER_KEY->{$client_cmd}; my $client_exe = "/usr/bin/$client_bin"; die "executable not found '$client_exe'! $client_bin not installed?\n" if !-x $client_exe; @@ -165,7 +215,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 = open_encryption_key($self))) { my $flags = fcntl($keyfd, F_GETFD, 0) @@ -173,6 +223,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 ($use_master && defined($master_fd = $self->open_master_pubkey())) { + 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'); } -- 2.47.3