From 125118914557440c4587f3f8ee346640afa1288e Mon Sep 17 00:00:00 2001 From: Jason Tarasovic Date: Tue, 15 Dec 2020 06:43:46 -0600 Subject: [PATCH] Add ability to use emulated tpm (#1166) Qemu has supported tpm 2 and the ability to start swtpm. Additionally it expands the tests for the tpm configuration to ensure that only when the options cause a change to the domain XML will the domain be updated on a subsequent start. This change just allows passing through the necessary config. Vagrant.configure("2") do |config| config.vm.provider :libvirt do |libvirt| libvirt.tpm_model = "tpm-crb" libvirt.tpm_type = "emulator" libvirt.tpm_version = "2.0" end end closes #965 --- README.md | 28 ++++-- lib/vagrant-libvirt/action/create_domain.rb | 9 +- lib/vagrant-libvirt/action/start_domain.rb | 51 +++++----- lib/vagrant-libvirt/config.rb | 3 + lib/vagrant-libvirt/templates/domain.xml.erb | 6 +- spec/unit/action/start_domain_spec.rb | 98 ++++++++++++++++++- .../default_added_tpm_version.xml | 48 +++++++++ spec/unit/templates/domain_spec.rb | 24 +++++ spec/unit/templates/tpm/version_1.2.xml | 53 ++++++++++ spec/unit/templates/tpm/version_2.0.xml | 52 ++++++++++ 10 files changed, 333 insertions(+), 39 deletions(-) create mode 100644 spec/unit/action/start_domain_spec/default_added_tpm_version.xml create mode 100644 spec/unit/templates/tpm/version_1.2.xml create mode 100644 spec/unit/templates/tpm/version_2.0.xml diff --git a/README.md b/README.md index 52c55a8..af06302 100644 --- a/README.md +++ b/README.md @@ -504,6 +504,7 @@ end * `tpm_model` - The model of the TPM to which you wish to connect. * `tpm_type` - The type of TPM device to which you are connecting. * `tpm_path` - The path to the TPM device on the host system. +* `tpm_version` - The TPM version to use. * `dtb` - The device tree blob file, mostly used for non-x86 platforms. In case the device tree isn't added in-line to the kernel, it can be manually specified here. @@ -579,6 +580,7 @@ defined domain: * `tpm_model` - Updated * `tpm_type` - Updated * `tpm_path` - Updated +* `tpm_version` - Updated ## Networks @@ -1408,13 +1410,14 @@ Modern versions of Libvirt support connecting to TPM devices on the host system. This allows you to enable Trusted Boot Extensions, among other features, on your guest VMs. -In general, you will only need to modify the `tpm_path` variable in your guest -configuration. However, advanced usage, such as the application of a Software -TPM, may require modifying the `tpm_model` and `tpm_type` variables. +To passthrough a hardware TPM, you will generally only need to modify the +`tpm_path` variable in your guest configuration. However, advanced usage, +such as the application of a Software TPM, may require modifying the +`tpm_model`, `tpm_type` and `tpm_version` variables. -The TPM options will only be used if you specify a TPM path. Declarations of -any TPM options without specifying a path will result in those options being -ignored. +The TPM options will only be used if you specify a TPM path or version. +Declarations of any TPM options without specifying a path or version will +result in those options being ignored. Here is an example of using the TPM options: @@ -1428,6 +1431,19 @@ Vagrant.configure("2") do |config| end ``` +It's also possible for Libvirt to start an emulated TPM device on the host. +Requires `swtpm` and `swtpm-tools` + +```ruby +Vagrant.configure("2") do |config| + config.vm.provider :libvirt do |libvirt| + libvirt.tpm_model = "tpm-crb" + libvirt.tpm_type = "emulator" + libvirt.tpm_version = "2.0" + end +end +``` + ## Libvirt communication channels For certain functionality to be available within a guest, a private diff --git a/lib/vagrant-libvirt/action/create_domain.rb b/lib/vagrant-libvirt/action/create_domain.rb index 9ed0766..ddd45a5 100644 --- a/lib/vagrant-libvirt/action/create_domain.rb +++ b/lib/vagrant-libvirt/action/create_domain.rb @@ -81,6 +81,7 @@ module VagrantPlugins @tpm_model = config.tpm_model @tpm_type = config.tpm_type @tpm_path = config.tpm_path + @tpm_version = config.tpm_version # Boot order @boot_order = config.boot_order @@ -254,7 +255,13 @@ module VagrantPlugins env[:ui].info(" -- Video VRAM: #{@video_vram}") env[:ui].info(" -- Sound Type: #{@sound_type}") env[:ui].info(" -- Keymap: #{@keymap}") - env[:ui].info(" -- TPM Path: #{@tpm_path}") + env[:ui].info(" -- TPM Backend: #{@tpm_type}") + if @tpm_type == 'emulator' + env[:ui].info(" -- TPM Model: #{@tpm_model}") + env[:ui].info(" -- TPM Version: #{@tpm_version}") + else + env[:ui].info(" -- TPM Path: #{@tpm_path}") + end @boot_order.each do |device| env[:ui].info(" -- Boot device: #{device}") diff --git a/lib/vagrant-libvirt/action/start_domain.rb b/lib/vagrant-libvirt/action/start_domain.rb index 7fa6ab4..1058015 100644 --- a/lib/vagrant-libvirt/action/start_domain.rb +++ b/lib/vagrant-libvirt/action/start_domain.rb @@ -193,36 +193,31 @@ module VagrantPlugins end # TPM - if config.tpm_path - raise Errors::FogCreateServerError, 'The TPM Path must be fully qualified' unless config.tpm_path[0].chr == '/' + if [config.tpm_path, config.tpm_version].any? + if config.tpm_path + raise Errors::FogCreateServerError, 'The TPM Path must be fully qualified' unless config.tpm_path[0].chr == '/' + end - tpm = REXML::XPath.first(xml_descr, '/domain/devices/tpm') - if tpm.nil? - @logger.debug "tpm created from previously not defined" + # just build the tpm element every time + # check at the end if it is different + oldtpm = REXML::XPath.first(xml_descr, '/domain/devices/tpm') + REXML::XPath.first(xml_descr, '/domain/devices').delete_element("tpm") + newtpm = REXML::Element.new('tpm', REXML::XPath.first(xml_descr, '/domain/devices')) + + newtpm.attributes['model'] = config.tpm_model + backend = newtpm.add_element('backend') + backend.attributes['type'] = config.tpm_type + + case config.tpm_type + when 'emulator' + backend.attributes['version'] = config.tpm_version + when 'passthrough' + backend.add_element('device').attributes['path'] = config.tpm_path + end + + unless "'#{newtpm}'".eql? "'#{oldtpm}'" + @logger.debug "tpm config changed" descr_changed = true - tpm = REXML::Element.new('tpm', REXML::XPath.first(xml_descr, '/domain/devices')) - tpm.attributes['model'] = config.tpm_model - tpm_backend_type = tpm.add_element('backend') - tpm_backend_type.attributes['type'] = config.tpm_type - tpm_device_path = tpm_backend_type.add_element('device') - tpm_device_path.attributes['path'] = config.tpm_path - else - if tpm.attributes['model'] != config.tpm_model - @logger.debug "tpm model updated from '#{tpm.attributes['model']}' to '#{config.tpm_model}'" - descr_changed = true - tpm.attributes['model'] = config.tpm_model - end - backend = tpm.elements['backend'] - if backend.attributes['type'] != config.tpm_type - @logger.debug "tpm type updated from '#{backend.attributes['type']}' to '#{config.tpm_type}'" - descr_changed = true - backend.attributes['type'] = config.tpm_type - end - if backend.elements['device'].attributes['path'] != config.tpm_path - @logger.debug "tpm path updated from '#{backend.elements['device'].attributes['path']}' to '#{config.tpm_path}'" - descr_changed = true - backend.elements['device'].attributes['path'] = config.tpm_path - end end end diff --git a/lib/vagrant-libvirt/config.rb b/lib/vagrant-libvirt/config.rb index 4fbc7a8..175e88b 100644 --- a/lib/vagrant-libvirt/config.rb +++ b/lib/vagrant-libvirt/config.rb @@ -117,6 +117,7 @@ module VagrantPlugins attr_accessor :tpm_model attr_accessor :tpm_type attr_accessor :tpm_path + attr_accessor :tpm_version # Sets the max number of NICs that can be created # Default set to 8. Don't change the default unless you know @@ -245,6 +246,7 @@ module VagrantPlugins @tpm_model = UNSET_VALUE @tpm_type = UNSET_VALUE @tpm_path = UNSET_VALUE + @tpm_version = UNSET_VALUE @nic_adapter_count = UNSET_VALUE @@ -781,6 +783,7 @@ module VagrantPlugins @tpm_model = 'tpm-tis' if @tpm_model == UNSET_VALUE @tpm_type = 'passthrough' if @tpm_type == UNSET_VALUE @tpm_path = nil if @tpm_path == UNSET_VALUE + @tpm_version = nil if @tpm_version == UNSET_VALUE @nic_adapter_count = 8 if @nic_adapter_count == UNSET_VALUE @emulator_path = nil if @emulator_path == UNSET_VALUE diff --git a/lib/vagrant-libvirt/templates/domain.xml.erb b/lib/vagrant-libvirt/templates/domain.xml.erb index 824872c..d04336e 100644 --- a/lib/vagrant-libvirt/templates/domain.xml.erb +++ b/lib/vagrant-libvirt/templates/domain.xml.erb @@ -257,11 +257,13 @@ <% end %> <% end -%> - <% if @tpm_path -%> + <% if @tpm_path || @tpm_version -%> <%# TPM Device -%> - + version='<%= @tpm_version %>'<% end %>> + <% if @tpm_path -%> + <% end -%> <% end -%> diff --git a/spec/unit/action/start_domain_spec.rb b/spec/unit/action/start_domain_spec.rb index 31cc635..1c0743c 100644 --- a/spec/unit/action/start_domain_spec.rb +++ b/spec/unit/action/start_domain_spec.rb @@ -48,8 +48,19 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do expect(subject.call(env)).to be_nil end + end - context 'tpm_path added' do + context 'tpm' do + let(:test_file) { 'default.xml' } + + before do + allow(libvirt_domain).to receive(:xml_desc).and_return(domain_xml) + + allow(libvirt_domain).to receive(:max_memory).and_return(512*1024) + allow(libvirt_domain).to receive(:num_vcpus).and_return(1) + end + + context 'passthrough tpm added' do let(:updated_test_file) { 'default_added_tpm_path.xml' } let(:vagrantfile_providerconfig) do <<-EOF @@ -61,7 +72,90 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do it 'should modify the domain tpm_path' do expect(libvirt_domain).to receive(:undefine) - expect(logger).to receive(:debug).with('tpm created from previously not defined') + expect(logger).to receive(:debug).with('tpm config changed') + expect(servers).to receive(:create).with(xml: updated_domain_xml) + expect(libvirt_domain).to receive(:autostart=) + expect(domain).to receive(:start) + + expect(subject.call(env)).to be_nil + end + end + + context 'emulated tpm added' do + let(:updated_test_file) { 'default_added_tpm_version.xml' } + let(:vagrantfile_providerconfig) do + <<-EOF + libvirt.tpm_type = 'emulator' + libvirt.tpm_model = 'tpm-crb' + libvirt.tpm_version = '2.0' + EOF + end + + it 'should modify the domain tpm_path' do + expect(libvirt_domain).to receive(:undefine) + expect(logger).to receive(:debug).with('tpm config changed') + expect(servers).to receive(:create).with(xml: updated_domain_xml) + expect(libvirt_domain).to receive(:autostart=) + expect(domain).to receive(:start) + + expect(subject.call(env)).to be_nil + end + end + + context 'same passthrough tpm config' do + let(:test_file) { 'default_added_tpm_path.xml' } + let(:updated_test_file) { 'default_added_tpm_path.xml' } + let(:vagrantfile_providerconfig) do + <<-EOF + libvirt.tpm_path = '/dev/tpm0' + libvirt.tpm_type = 'passthrough' + libvirt.tpm_model = 'tpm-tis' + EOF + end + + it 'should execute without changing' do + expect(logger).to_not receive(:debug) + expect(libvirt_domain).to receive(:autostart=) + expect(domain).to receive(:start) + + expect(subject.call(env)).to be_nil + end + end + + context 'same emulated tpm config' do + let(:test_file) { 'default_added_tpm_version.xml' } + let(:updated_test_file) { 'default_added_tpm_version.xml' } + let(:vagrantfile_providerconfig) do + <<-EOF + libvirt.tpm_type = 'emulator' + libvirt.tpm_model = 'tpm-crb' + libvirt.tpm_version = '2.0' + EOF + end + + it 'should execute without changing' do + expect(logger).to_not receive(:debug) + expect(libvirt_domain).to receive(:autostart=) + expect(domain).to receive(:start) + + expect(subject.call(env)).to be_nil + end + end + + context 'change from passthrough to emulated' do + let(:test_file) { 'default_added_tpm_path.xml' } + let(:updated_test_file) { 'default_added_tpm_version.xml' } + let(:vagrantfile_providerconfig) do + <<-EOF + libvirt.tpm_type = 'emulator' + libvirt.tpm_model = 'tpm-crb' + libvirt.tpm_version = '2.0' + EOF + end + + it 'should modify the domain' do + expect(libvirt_domain).to receive(:undefine) + expect(logger).to receive(:debug).with('tpm config changed') expect(servers).to receive(:create).with(xml: updated_domain_xml) expect(libvirt_domain).to receive(:autostart=) expect(domain).to receive(:start) diff --git a/spec/unit/action/start_domain_spec/default_added_tpm_version.xml b/spec/unit/action/start_domain_spec/default_added_tpm_version.xml new file mode 100644 index 0000000..beaba37 --- /dev/null +++ b/spec/unit/action/start_domain_spec/default_added_tpm_version.xml @@ -0,0 +1,48 @@ + + + + <description/> + <uuid/> + <memory/> + <vcpu>1</vcpu> + + + <cpu mode='host-model'> + <model fallback='allow'/> + </cpu> + + + <os> + <type>hvm</type> + <kernel/> + <initrd/> + <cmdline/> + </os> + <features> + <acpi/> + <apic/> + <pae/> + </features> + <clock offset='utc'/> + <devices> + + + <serial type='pty'> + <target port='0'/> + </serial> + <console type='pty'> + <target port='0'/> + </console> + + + <input bus='ps2' type='mouse'/> + + <graphics autoport='yes' keymap='en-us' listen='127.0.0.1' port='-1' type='vnc'/> + <video> + <model heads='1' type='cirrus' vram='9216'/> + </video> + + + <tpm model='tpm-crb'><backend type='emulator' version='2.0'/></tpm></devices> + +</domain> diff --git a/spec/unit/templates/domain_spec.rb b/spec/unit/templates/domain_spec.rb index c800e05..056dd3b 100644 --- a/spec/unit/templates/domain_spec.rb +++ b/spec/unit/templates/domain_spec.rb @@ -105,4 +105,28 @@ describe 'templates/domain' do expect(domain.to_xml('domain')).to eq xml_expected end end + + context 'when tpm 2.0 device is specified' do + before do + domain.tpm_version = '2.0' + domain.tpm_type = 'emulator' + domain.tpm_model = 'tpm-crb' + end + let(:test_file) { 'tpm/version_2.0.xml' } + it 'renders template' do + domain.finalize! + expect(domain.to_xml('domain')).to eq xml_expected + end + end + + context 'when tpm 1.2 device is implicitly used' do + before do + domain.tpm_path = '/dev/tpm0' + end + let(:test_file) { 'tpm/version_1.2.xml' } + it 'renders template' do + domain.finalize! + expect(domain.to_xml('domain')).to eq xml_expected + end + end end diff --git a/spec/unit/templates/tpm/version_1.2.xml b/spec/unit/templates/tpm/version_1.2.xml new file mode 100644 index 0000000..c33f9d0 --- /dev/null +++ b/spec/unit/templates/tpm/version_1.2.xml @@ -0,0 +1,53 @@ +<domain type='' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'> + <name></name> + <title> + + + + 1 + + + + + + + + + hvm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/unit/templates/tpm/version_2.0.xml b/spec/unit/templates/tpm/version_2.0.xml new file mode 100644 index 0000000..fc92aef --- /dev/null +++ b/spec/unit/templates/tpm/version_2.0.xml @@ -0,0 +1,52 @@ + + + + + + + 1 + + + + + + + + + hvm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +