From: Thomas Lamprecht <t.lamprecht@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH common 04/10] section config: add redaction of sensitive values in raw configs
Date: Sun, 1 Nov 2026 22:34:53 +0100 [thread overview]
Message-ID: <20261101213835.353604-5-t.lamprecht@proxmox.com> (raw)
In-Reply-To: <20261101213835.353604-1-t.lamprecht@proxmox.com>
Section config files like the SDN IPAM and DNS configs hold API tokens
and are included verbatim in the system report, so they need the same
raw-text redaction as key/value config files.
Sections are delimited as in parse_config, so each value is checked
against the schema of the section the parser assigns it to. Sections of
a type without a registered plugin are still checked against the
properties that are not isolated to a plugin.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
---
src/PVE/SectionConfig.pm | 48 +++++++++++++++++++
.../section_config_property_isolation_test.pl | 43 +++++++++++++++++
test/section_config_test.pl | 40 ++++++++++++++++
3 files changed, 131 insertions(+)
diff --git a/src/PVE/SectionConfig.pm b/src/PVE/SectionConfig.pm
index 1febf76..04a79de 100644
--- a/src/PVE/SectionConfig.pm
+++ b/src/PVE/SectionConfig.pm
@@ -1807,6 +1807,54 @@ sub write_config {
return $out;
}
+=head3 $base->redact_raw_config(...)
+
+=head3 $base->redact_raw_config($raw, $placeholder)
+
+ $redacted = PVE::Example::BasePlugin->redact_raw_config($raw, $placeholder)
+ $redacted = $class->redact_raw_config($raw, $placeholder)
+
+Returns the contents of a C<L<PVE::SectionConfig>> file with the value of every
+property marked as C<sensitive> replaced by C<$placeholder>, including sensitive
+keys of property strings.
+
+Works on the raw text, so lines that
+C<L<< parse_config()|/$base->parse_config(...) >>> would skip as invalid are
+kept. As in C<L<< parse_config()|/$base->parse_config(...) >>>, a section ends
+at the next empty line. Outside of sections of a known
+I<L<< type|/$plugin->type() >>>, only properties that are not isolated to a
+plugin are recognized.
+
+=cut
+
+sub redact_raw_config($class, $raw, $placeholder) {
+ my ($type, $in_section) = ('', 0);
+ my @lines = split(/\n/, $raw, -1);
+ for my $line (@lines) {
+ next if $line =~ m/^\s*#/;
+
+ if ($line eq '') {
+ ($type, $in_section) = ('', 0);
+ next;
+ }
+
+ if (!$in_section) {
+ my ($header_type) = $class->parse_section_header($line);
+ if (defined($header_type)) {
+ ($type, $in_section) = ($header_type, 1);
+ next;
+ }
+ }
+
+ if (my ($prefix, $key, $value) = $line =~ m/^(\s+(\S+)\s+)(.*)$/) {
+ my $schema = $class->get_property_schema($type, $key) or next;
+ $line =
+ $prefix . PVE::JSONSchema::redact_sensitive_value($schema, $value, $placeholder);
+ }
+ }
+ return join("\n", @lines);
+}
+
sub assert_if_modified {
my ($cfg, $digest) = @_;
diff --git a/test/section_config_property_isolation_test.pl b/test/section_config_property_isolation_test.pl
index 11b3142..a487952 100755
--- a/test/section_config_property_isolation_test.pl
+++ b/test/section_config_property_isolation_test.pl
@@ -85,6 +85,7 @@ sub properties {
description => 'Another field',
type => 'string',
optional => 1,
+ sensitive => 1,
},
arrayfield => {
description => "Array Field with property string",
@@ -395,6 +396,7 @@ my sub make_schema($is_update) {
description => 'Another field',
type => 'string',
optional => 1,
+ sensitive => 1,
},
arrayfield => {
description => "Array Field with property string",
@@ -481,6 +483,47 @@ ok(
"property lookup does not add a property list",
);
+my $redact_in = <<"EOF";
+one: a
+ another secret
+
+two: b
+ another visible
+
+unknown: c
+ another isolated
+
+two: d
+one: d-ignored
+ another visible
+
+one: e
+two: e-ignored
+ another secret
+ another
+EOF
+my $redact_out = <<"EOF";
+one: a
+ another <redacted>
+
+two: b
+ another visible
+
+unknown: c
+ another isolated
+
+two: d
+one: d-ignored
+ another visible
+
+one: e
+two: e-ignored
+ another <redacted>
+ another
+EOF
+is(Conf->redact_raw_config($redact_in, '<redacted>'), $redact_out,
+ "property-isolation redact test");
+
done_testing();
1;
diff --git a/test/section_config_test.pl b/test/section_config_test.pl
index 2cdff98..3e52834 100755
--- a/test/section_config_test.pl
+++ b/test/section_config_test.pl
@@ -75,6 +75,7 @@ sub properties {
another => {
description => 'Another field',
type => 'string',
+ sensitive => 1,
},
};
}
@@ -115,6 +116,7 @@ sub properties {
subfield1 => {
type => 'string',
description => 'first subfield',
+ sensitive => 1,
},
subfield2 => {
type => 'integer',
@@ -300,6 +302,7 @@ my $expected_create_schema = {
'subfield1' => {
'description' => 'first subfield',
'type' => 'string',
+ 'sensitive' => 1,
},
},
'type' => 'string',
@@ -311,6 +314,7 @@ my $expected_create_schema = {
'description' => 'Another field',
'optional' => 1,
'type' => 'string',
+ 'sensitive' => 1,
},
},
};
@@ -369,6 +373,7 @@ my $expected_update_schema = {
subfield1 => {
description => 'first subfield',
type => 'string',
+ sensitive => 1,
},
},
},
@@ -379,11 +384,46 @@ my $expected_update_schema = {
description => 'Another field',
optional => 1,
type => 'string',
+ sensitive => 1,
},
},
};
is_deeply($update_schema, $expected_update_schema, "update schema test");
+my $redact_in = <<"EOF";
+one: t1
+ common foo
+ another secret1
+
+# another: comment
+two: t2
+ field2 5
+ another secret2
+invalid line
+ arrayfield subfield2=2,subfield1=secret3
+
+bad: unknown
+ another secret4
+ unknownfield visible
+EOF
+my $redact_out = <<"EOF";
+one: t1
+ common foo
+ another <redacted>
+
+# another: comment
+two: t2
+ field2 5
+ another <redacted>
+invalid line
+ arrayfield subfield2=2,subfield1=<redacted>
+
+bad: unknown
+ another <redacted>
+ unknownfield visible
+EOF
+is(Conf->redact_raw_config($redact_in, '<redacted>'), $redact_out, "redact raw config");
+
done_testing();
1;
--
2.47.3
next prev parent reply other threads:[~2026-11-01 21:39 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-01 21:34 [PATCH common,qemu-server,network,manager 0/10] mark secrets in schemas and redact them from the system report Thomas Lamprecht
2026-11-01 21:34 ` [PATCH common 01/10] section config: do not add isolated property lists on lookups Thomas Lamprecht
2026-11-01 21:34 ` [PATCH common 02/10] schema: allow marking properties as sensitive Thomas Lamprecht
2026-11-01 21:34 ` [PATCH common 03/10] schema: add helpers to redact sensitive values Thomas Lamprecht
2026-11-01 21:34 ` Thomas Lamprecht [this message]
2026-11-01 21:34 ` [PATCH qemu-server 05/10] cloud-init: mark the password as sensitive Thomas Lamprecht
2026-11-01 21:34 ` [PATCH qemu-server 06/10] config: hide sensitive values of unparsable lines in warnings Thomas Lamprecht
2026-11-01 21:34 ` [PATCH network 07/10] sdn: ipam, dns: mark API tokens as sensitive Thomas Lamprecht
2026-11-01 21:34 ` [PATCH manager 08/10] report: redact sensitive values from guest, storage and SDN configs Thomas Lamprecht
2026-11-01 21:34 ` [PATCH manager 09/10] report: redact credentials in URLs, options and environment variables Thomas Lamprecht
2026-11-01 21:34 ` [PATCH manager 10/10] pvereport: man page: document which values get redacted Thomas Lamprecht
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=20261101213835.353604-5-t.lamprecht@proxmox.com \
--to=t.lamprecht@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox