From: Stefan Reiter <s.reiter@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [RFC qemu-server 2/2] fix #3075: add TPM v1.2 and v2.0 support via swtpm
Date: Thu, 15 Jul 2021 16:23:19 +0200 [thread overview]
Message-ID: <20210715142319.1457131-3-s.reiter@proxmox.com> (raw)
In-Reply-To: <20210715142319.1457131-1-s.reiter@proxmox.com>
Starts an instance of swtpm per VM in it's systemd scope, it will
terminate by itself if the VM exits, or be terminated manually if
startup fails.
Before first use, a TPM state is created via swtpm_setup. The state
lives in "/etc/pve/priv/tpm/<vmid>-<version>/".
TPM state is cleared if the 'tpm' config option is removed or the
version changed, effectively clearing any stored keys/data.
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
---
PVE/QemuServer.pm | 134 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 133 insertions(+), 1 deletion(-)
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index b0fe257..76a25ae 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -686,6 +686,12 @@ EODESCR
description => "Configure a VirtIO-based Random Number Generator.",
optional => 1,
},
+ tpm => {
+ optional => 1,
+ type => 'string',
+ enum => [ qw(v1.2 v2.0) ],
+ description => "Configure an emulated Trusted Platform Module.",
+ },
};
my $cicustom_fmt = {
@@ -2945,6 +2951,116 @@ sub audio_devs {
return $devs;
}
+sub get_tpm_paths {
+ my ($vmid, $version) = @_;
+ return {
+ state => "/etc/pve/priv/tpm/$vmid-$version/",
+ socket => "/var/run/qemu-server/$vmid.swtpm",
+ pid => "/var/run/qemu-server/$vmid.swtpm.pid",
+ filename => $version eq "v1.2" ? "tpm-00.permall" : "tpm2-00.permall",
+ };
+}
+
+sub print_tpm_device {
+ my ($vmid, $version) = @_;
+ my $paths = get_tpm_paths($vmid, $version);
+
+ my $devs = [];
+
+ push @$devs, "-chardev", "socket,id=tpmchar,path=$paths->{socket}";
+ push @$devs, "-tpmdev", "emulator,id=tpmdev,chardev=tpmchar";
+ push @$devs, "-device", "tpm-tis,tpmdev=tpmdev";
+
+ return $devs;
+}
+
+sub start_swtpm {
+ my ($vmid, $version, $migration) = @_;
+ my $paths = get_tpm_paths($vmid, $version);
+
+ if ($migration) {
+ # we will get migration state from remote, so remove any pre-existing
+ clear_tpm_states($vmid);
+ File::Path::make_path($paths->{state});
+ } else {
+ # run swtpm_setup to create a new TPM state if it doesn't exist yet
+ if (! -f "$paths->{state}/$paths->{filename}") {
+ print "Creating new TPM state\n";
+
+ # swtpm_setup does not like /etc/pve/priv, so create in tempdir
+ my $tmppath = "/tmp/tpm-$vmid-$$";
+ File::Path::make_path($tmppath, mode => 0600);
+ my $setup_cmd = [
+ "swtpm_setup",
+ "--tpmstate",
+ "$tmppath",
+ "--createek",
+ "--create-ek-cert",
+ "--create-platform-cert",
+ "--lock-nvram",
+ "--config",
+ "/etc/swtpm_setup.conf", # do not use XDG configs
+ "--runas",
+ "0", # force creation as root, error if not possible
+ ];
+
+ push @$setup_cmd, "--tpm2" if $version eq 'v2.0';
+ # TPM 2.0 supports ECC crypto, use if possible
+ push @$setup_cmd, "--ecc" if $version eq 'v2.0';
+
+ # produces a lot of verbose output, only show on error
+ my $tpmout = "";
+ run_command($setup_cmd, outfunc => sub {
+ $tpmout .= $1 . "\n";
+ });
+
+ File::Path::make_path($paths->{state});
+ my $res = File::Copy::move("$tmppath/$paths->{filename}",
+ "$paths->{state}/$paths->{filename}");
+ File::Path::rmtree($tmppath);
+ if (!$res) {
+ my $err = $!;
+ File::Path::rmtree($tmppath);
+ print "swtpm_setup reported:\n$tpmout";
+ die "couldn't move TPM state into '$paths->{state}' - $err\n";
+ }
+ }
+ }
+
+ my $emulator_cmd = [
+ "swtpm",
+ "socket",
+ "--tpmstate",
+ "dir=$paths->{state},mode=0600",
+ "--ctrl",
+ "type=unixio,path=$paths->{socket},mode=0600",
+ "--pid",
+ "file=$paths->{pid}",
+ "--terminate", # terminate on QEMU disconnect
+ "--daemon",
+ ];
+ push @$emulator_cmd, "--tpm2" if $version eq 'v2.0';
+ run_command($emulator_cmd);
+
+ # return untainted PID of swtpm daemon so it can be killed on error
+ file_read_firstline($paths->{pid}) =~ m/(\d+)/;
+ return $1;
+}
+
+# clear any TPM states other than the ones relevant for $version
+sub clear_tpm_states {
+ my ($vmid, $keep_version) = @_;
+
+ my $clear = sub {
+ my ($v) = @_;
+ my $paths = get_tpm_paths($vmid, $v);
+ rmtree $paths->{state};
+ };
+
+ &$clear("v1.2") if !$keep_version || $keep_version ne "v1.2";
+ &$clear("v2.0") if !$keep_version || $keep_version ne "v2.0";
+}
+
sub vga_conf_has_spice {
my ($vga) = @_;
@@ -3446,6 +3562,11 @@ sub config_to_command {
push @$devices, @$audio_devs;
}
+ if (my $tpmver = $conf->{tpm}) {
+ my $tpmdev = print_tpm_device($vmid, $tpmver);
+ push @$devices, @$tpmdev;
+ }
+
my $sockets = 1;
$sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
$sockets = $conf->{sockets} if $conf->{sockets};
@@ -4829,6 +4950,8 @@ sub vmconfig_apply_pending {
}
}
+ PVE::QemuServer::clear_tpm_states($vmid, $conf->{tpm});
+
# write all changes at once to avoid unnecessary i/o
PVE::QemuConfig->write_config($vmid, $conf);
}
@@ -5329,8 +5452,17 @@ sub vm_start_nolock {
PVE::Tools::run_fork sub {
PVE::Systemd::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties);
+ my $tpmpid;
+ if (my $tpmver = $conf->{tpm}) {
+ # start the TPM emulator so QEMU can connect on start
+ $tpmpid = start_swtpm($vmid, $tpmver, $migratedfrom);
+ }
+
my $exitcode = run_command($cmd, %run_params);
- die "QEMU exited with code $exitcode\n" if $exitcode;
+ if ($exitcode) {
+ kill 'TERM', $tpmpid if $tpmpid;
+ die "QEMU exited with code $exitcode\n";
+ }
};
};
--
2.30.2
next prev parent reply other threads:[~2021-07-15 14:24 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-07-15 14:23 [pve-devel] [RFC 0/2] Initial TPM support for VMs Stefan Reiter
2021-07-15 14:23 ` [pve-devel] [RFC edk2-firmware 1/2] enable TPM and TPM2 support Stefan Reiter
2021-07-15 14:23 ` Stefan Reiter [this message]
2021-07-16 14:47 ` [pve-devel] [RFC qemu-server 2/2] fix #3075: add TPM v1.2 and v2.0 support via swtpm alexandre derumier
2021-08-09 18:17 ` Nick Chevsky
2021-08-10 7:48 ` Stefan Reiter
2021-07-16 9:48 ` [pve-devel] [RFC 0/2] Initial TPM support for VMs 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=20210715142319.1457131-3-s.reiter@proxmox.com \
--to=s.reiter@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.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal