[pve-devel] [RFC qemu-server 2/2] fix #3075: add TPM v1.2 and v2.0 support via swtpm
alexandre derumier
aderumier at odiso.com
Fri Jul 16 16:47:14 CEST 2021
Hi, I have found old post where a nvram device (using a qcow2 or raw
file as backend) was used with tpm
https://libvir-list.redhat.narkive.com/eOrJPdYX/libvirt-how-libvirt-address-qemu-command-line-args
also dev doc from 2011 mention it
https://wiki.qemu.org/Features/TPM
(I don't have tested to verify)
Le jeudi 15 juillet 2021 à 16:23 +0200, Stefan Reiter a écrit :
> 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 at 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";
> + }
> };
> };
>
More information about the pve-devel
mailing list