From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 46CC51FF09D for ; Sun, 01 Nov 2026 22:40:05 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id EB9972179A; Sun, 01 Nov 2026 22:38:54 +0100 (CET) From: Thomas Lamprecht To: pve-devel@lists.proxmox.com Subject: [PATCH manager 08/10] report: redact sensitive values from guest, storage and SDN configs Date: Sun, 1 Nov 2026 22:34:57 +0100 Message-ID: <20261101213835.353604-9-t.lamprecht@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261101213835.353604-1-t.lamprecht@proxmox.com> References: <20261101213835.353604-1-t.lamprecht@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793569121276 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.411 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) 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 Message-ID-Hash: T3VAJ3G3QJW2RO3TKKJZZLO5HYIJ2WNL X-Message-ID-Hash: T3VAJ3G3QJW2RO3TKKJZZLO5HYIJ2WNL X-MailFrom: t.lamprecht@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 VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The system report only requires Sys.Audit, but includes all guest configs and the SDN configs verbatim. So it contains cloud-init passwords, in plain text for Windows guests, and the API tokens of SDN IPAM and DNS integrations, while the config API masks the password for everyone. A report is often sent to support by someone who does not own these credentials, for example a hosting provider renting out VMs. Replace the values of all properties marked as sensitive in their schema, in every section including snapshots and pending changes. The key stays visible, and so does every other line of the file, including invalid ones. storage.cfg holds no secrets of the built-in plugins, which store them in separate files, but third-party plugins that keep credentials inline can now mark them and have them redacted too. Signed-off-by: Thomas Lamprecht --- NOTE: needs a libpve-common-perl with the raw config redaction helpers as a hard runtime dependency; with an older one the report shows errors instead of the guest, storage and SDN configs. The redaction only takes effect with qemu-server and libpve-network-perl versions that mark their secrets. PVE/Report.pm | 56 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/PVE/Report.pm b/PVE/Report.pm index e255d538f..7182c091d 100644 --- a/PVE/Report.pm +++ b/PVE/Report.pm @@ -4,11 +4,27 @@ use strict; use warnings; use PVE::IPRoute2; +use PVE::JSONSchema; +use PVE::LXC::Config; +use PVE::Network::SDN::Dns; +use PVE::Network::SDN::Ipams; +use PVE::QemuServer; +use PVE::Storage; use PVE::Tools; +my $redaction_placeholder = ''; + +my sub file2text { + my ($path, $redact) = @_; + + my $content = PVE::Tools::file_get_contents($path); + $content = $redact->($content, $path) if $redact; + return "\n# cat $path\n$content\n"; +} + # output the content of all the files of a directory my sub dir2text { - my ($target_dir, $regexp) = @_; + my ($target_dir, $regexp, $redact) = @_; print STDERR "dir2text '${target_dir}${regexp}'..."; my $text = "# output '${target_dir}${regexp}' file(s)\n"; @@ -18,13 +34,39 @@ my sub dir2text { sub { my ($file) = @_; return if $file eq '.' || $file eq '..'; - $text .= "\n# cat $target_dir$file\n"; - $text .= PVE::Tools::file_get_contents($target_dir . $file) . "\n"; + $text .= file2text($target_dir . $file, $redact); }, ); return $text; } +my sub redact_qemu_config { + my ($raw) = @_; + + my $schema = { type => 'object', properties => PVE::QemuServer::json_config_properties({}) }; + return PVE::JSONSchema::redact_raw_config($schema, $raw, $redaction_placeholder); +} + +my sub redact_lxc_config { + my ($raw) = @_; + + my $schema = { type => 'object', properties => PVE::LXC::Config->json_config_properties({}) }; + return PVE::JSONSchema::redact_raw_config($schema, $raw, $redaction_placeholder); +} + +my $section_configs = { + '/etc/pve/sdn/dns.cfg' => 'PVE::Network::SDN::Dns::Plugin', + '/etc/pve/sdn/ipams.cfg' => 'PVE::Network::SDN::Ipams::Plugin', + '/etc/pve/storage.cfg' => 'PVE::Storage::Plugin', +}; + +my sub redact_section_config { + my ($raw, $path) = @_; + + my $class = $section_configs->{$path} // return $raw; + return $class->redact_raw_config($raw, $redaction_placeholder); +} + # command -v is the posix equivalent of 'which' my sub cmd_exists { system("command -v '$_[0]' > /dev/null 2>&1") == 0 } @@ -59,7 +101,7 @@ my $init_report_cmds = sub { storage => { order => 30, cmds => [ - 'cat /etc/pve/storage.cfg', + sub { file2text('/etc/pve/storage.cfg', \&redact_section_config) }, 'pvesm status', 'cat /etc/fstab', 'findmnt --ascii', @@ -71,9 +113,9 @@ my $init_report_cmds = sub { order => 40, cmds => [ 'qm list', - sub { dir2text('/etc/pve/qemu-server/', '\d+\.conf') }, + sub { dir2text('/etc/pve/qemu-server/', '\d+\.conf', \&redact_qemu_config) }, 'pct list', - sub { dir2text('/etc/pve/lxc/', '\d+\.conf') }, + sub { dir2text('/etc/pve/lxc/', '\d+\.conf', \&redact_lxc_config) }, ], }, network => { @@ -85,7 +127,7 @@ my $init_report_cmds = sub { 'cat /etc/network/interfaces', sub { dir2text('/etc/network/interfaces.d/', '.*') }, 'cat /etc/pve/sdn/.running-config', - sub { dir2text('/etc/pve/sdn/', '.+\.cfg') }, + sub { dir2text('/etc/pve/sdn/', '.+\.cfg', \&redact_section_config) }, sub { dir2text('/etc/pve/sdn/', '.+\.json') }, ], }, -- 2.47.3