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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox