[pve-devel] [RFC PATCH 2/2] config: add systemd credentials support

Maximiliano Sandoval m.sandoval at proxmox.com
Tue Sep 24 16:35:02 CEST 2024


Allows to pass systemd credentials to a VM. See [1] for a description of
systemd credentials. This can be potentially used to provision a VM as
per [2]. Values can be passed either as plain text (which might be
base64 encrypted) or by reading the contents of a snippet.

A VM configuration file which, for example, contains:

    systemd.foo: value=bar
    systemd.ssh.authorized_keys.root: snippet=local:snippets/id_ed25519.pub
    systemd.encoded-foo: value=YmFya=,base64=1

will have the following arguments added to its kvm command:

    -smbios 'type=11,value=io.systemd.credential.binary:ssh.authorized_keys.root=c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSUZWZkFTYnVHdGdoWXBQQTBUS0w4N3I2dWRYNm5CbEM2L2hLWVZaTTdENzYgZm9vQGJhcgo=' \
    -smbios 'type=11,value=io.systemd.credential:foo=bar' \
    -smbios 'type=11,value=io.systemd.credential.binary:encoded-foo=YmFy'

On the guest these credentials can be read via:

    dmidecode -t 11

In the example above, the SSH key will be added to
/root/.ssh/authorized_keys provided the file did not exist, see [3].

[1] https://systemd.io/CREDENTIALS/
[2] https://www.freedesktop.org/software/systemd/man/latest/systemd.system-credentials.html
[3] https://www.freedesktop.org/software/systemd/man/latest/systemd.system-credentials.html#ssh.authorized_keys.root

Suggested-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
Signed-off-by: Maximiliano Sandoval <m.sandoval at proxmox.com>
---
 PVE/QemuServer.pm | 77 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 1566cf91..3ec21064 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -149,6 +149,26 @@ my $watchdog_fmt = {
 };
 PVE::JSONSchema::register_format('pve-qm-watchdog', $watchdog_fmt);
 
+our $systemd_value_fmt = {
+    value => {
+	description => 'The credential value. base64=1 should be specified if the value is base64 encoded.',
+	type => 'string',
+	optional => 1,
+    },
+    snippet => {
+	type => 'string',
+	description => "Specify a snippet containing the credential's value",
+	format => 'pve-volume-id',
+	optional => 1,
+    },
+    base64 => {
+	description => 'Whether the value is base64 encoded.',
+	type => 'boolean',
+	optional => 1,
+	default => 0,
+    },
+};
+
 my $agent_fmt = {
     enabled => {
 	description => "Enable/disable communication with a QEMU Guest Agent (QGA) running in the VM.",
@@ -2039,6 +2059,16 @@ sub parse_guest_agent {
     return $res;
 }
 
+sub parse_systemd_credential {
+    my ($value) = @_;
+
+    return {} if !$value;
+
+    my $res = eval { parse_property_string($systemd_value_fmt, $value) };
+    warn $@ if $@;
+    return $res;
+}
+
 sub get_qga_key {
     my ($conf, $key) = @_;
     return undef if !defined($conf->{agent});
@@ -2390,6 +2420,12 @@ sub parse_vm_config {
 		$conf->{$key} = $value;
 		next;
 	    }
+	    if ($key =~ /^systemd\.([a-z][a-z_\.-]*)$/) {
+		# ignore validation of systemd credentials
+		$conf->{'systemd-credentials'}->{$1} = $value;
+		next;
+	    }
+
 	    eval { $value = check_type($key, $value); };
 	    if ($@) {
 		$handle_error->("vm $vmid - unable to parse value of '$key' - $@");
@@ -3514,6 +3550,27 @@ my sub get_vga_properties {
     return ($vga, $qxlnum);
 }
 
+sub smbios_11_cred_arg {
+    my ($key, $value, $is_encoded) = @_;
+
+    if ($is_encoded) {
+	return ('-smbios', "type=11,value=io.systemd.credential.binary:$key=$value");
+    } else {
+	return ('-smbios', "type=11,value=io.systemd.credential:$key=$value");
+    }
+}
+
+sub read_systemd_custom_file {
+    my ($storage_conf, $path) = @_;
+
+    my ($vtype, undef) = PVE::Storage::parse_volname($storage_conf, $path);
+
+    die "$path is not in the snippets directory\n" if $vtype ne 'snippets';
+
+    my $full_path = PVE::Storage::abs_filesystem_path($storage_conf, $path, 1);
+    return PVE::Tools::file_get_contents($full_path, 1 * 1024 * 1024);
+}
+
 sub config_to_command {
     my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu,
         $live_restore_backing) = @_;
@@ -4142,6 +4199,26 @@ sub config_to_command {
 	push @$cmd, '-snapshot';
     }
 
+    # set systemd-credentials
+    my $storage_conf;
+    my $systemd_credentials = $conf->{'systemd-credentials'} || {};
+    foreach my $key (keys %$systemd_credentials) {
+	my $opts = parse_systemd_credential($systemd_credentials->{$key});
+	my $is_encoded = $opts->{'base64'} ? 1 : 0;
+	my $value;
+
+	if (my $v = $opts->{'value'}) {
+	    $value = $v;
+	} elsif (my $snippet = $opts->{'snippet'}) {
+	    $storage_conf = PVE::Storage::config() if !defined($storage_conf);
+	    my $contents = read_systemd_custom_file($storage_conf, $snippet);
+	    $value = encode_base64($contents, '');
+	    $is_encoded = 1;
+	}
+
+	push @$cmd, smbios_11_cred_arg($key, $value, $is_encoded) if $value;
+    }
+
     # add custom args
     if ($conf->{args}) {
 	my $aa = PVE::Tools::split_args($conf->{args});
-- 
2.39.5





More information about the pve-devel mailing list