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.rei...@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



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to