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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.