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
This commit is contained in:
Jason Tarasovic
2020-12-15 06:43:46 -06:00
committed by GitHub
parent 20067be0d2
commit 1251189145
10 changed files with 333 additions and 39 deletions

View File

@@ -504,6 +504,7 @@ end
* `tpm_model` - The model of the TPM to which you wish to connect. * `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_type` - The type of TPM device to which you are connecting.
* `tpm_path` - The path to the TPM device on the host system. * `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 * `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 the device tree isn't added in-line to the kernel, it can be manually
specified here. specified here.
@@ -579,6 +580,7 @@ defined domain:
* `tpm_model` - Updated * `tpm_model` - Updated
* `tpm_type` - Updated * `tpm_type` - Updated
* `tpm_path` - Updated * `tpm_path` - Updated
* `tpm_version` - Updated
## Networks ## 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 system. This allows you to enable Trusted Boot Extensions, among other
features, on your guest VMs. features, on your guest VMs.
In general, you will only need to modify the `tpm_path` variable in your guest To passthrough a hardware TPM, you will generally only need to modify the
configuration. However, advanced usage, such as the application of a Software `tpm_path` variable in your guest configuration. However, advanced usage,
TPM, may require modifying the `tpm_model` and `tpm_type` variables. 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 The TPM options will only be used if you specify a TPM path or version.
any TPM options without specifying a path will result in those options being Declarations of any TPM options without specifying a path or version will
ignored. result in those options being ignored.
Here is an example of using the TPM options: Here is an example of using the TPM options:
@@ -1428,6 +1431,19 @@ Vagrant.configure("2") do |config|
end 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 ## Libvirt communication channels
For certain functionality to be available within a guest, a private For certain functionality to be available within a guest, a private

View File

@@ -81,6 +81,7 @@ module VagrantPlugins
@tpm_model = config.tpm_model @tpm_model = config.tpm_model
@tpm_type = config.tpm_type @tpm_type = config.tpm_type
@tpm_path = config.tpm_path @tpm_path = config.tpm_path
@tpm_version = config.tpm_version
# Boot order # Boot order
@boot_order = config.boot_order @boot_order = config.boot_order
@@ -254,7 +255,13 @@ module VagrantPlugins
env[:ui].info(" -- Video VRAM: #{@video_vram}") env[:ui].info(" -- Video VRAM: #{@video_vram}")
env[:ui].info(" -- Sound Type: #{@sound_type}") env[:ui].info(" -- Sound Type: #{@sound_type}")
env[:ui].info(" -- Keymap: #{@keymap}") 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| @boot_order.each do |device|
env[:ui].info(" -- Boot device: #{device}") env[:ui].info(" -- Boot device: #{device}")

View File

@@ -193,36 +193,31 @@ module VagrantPlugins
end end
# TPM # TPM
if config.tpm_path if [config.tpm_path, config.tpm_version].any?
raise Errors::FogCreateServerError, 'The TPM Path must be fully qualified' unless config.tpm_path[0].chr == '/' 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') # just build the tpm element every time
if tpm.nil? # check at the end if it is different
@logger.debug "tpm created from previously not defined" 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 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
end end

View File

@@ -117,6 +117,7 @@ module VagrantPlugins
attr_accessor :tpm_model attr_accessor :tpm_model
attr_accessor :tpm_type attr_accessor :tpm_type
attr_accessor :tpm_path attr_accessor :tpm_path
attr_accessor :tpm_version
# Sets the max number of NICs that can be created # Sets the max number of NICs that can be created
# Default set to 8. Don't change the default unless you know # Default set to 8. Don't change the default unless you know
@@ -245,6 +246,7 @@ module VagrantPlugins
@tpm_model = UNSET_VALUE @tpm_model = UNSET_VALUE
@tpm_type = UNSET_VALUE @tpm_type = UNSET_VALUE
@tpm_path = UNSET_VALUE @tpm_path = UNSET_VALUE
@tpm_version = UNSET_VALUE
@nic_adapter_count = UNSET_VALUE @nic_adapter_count = UNSET_VALUE
@@ -781,6 +783,7 @@ module VagrantPlugins
@tpm_model = 'tpm-tis' if @tpm_model == UNSET_VALUE @tpm_model = 'tpm-tis' if @tpm_model == UNSET_VALUE
@tpm_type = 'passthrough' if @tpm_type == UNSET_VALUE @tpm_type = 'passthrough' if @tpm_type == UNSET_VALUE
@tpm_path = nil if @tpm_path == 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 @nic_adapter_count = 8 if @nic_adapter_count == UNSET_VALUE
@emulator_path = nil if @emulator_path == UNSET_VALUE @emulator_path = nil if @emulator_path == UNSET_VALUE

View File

@@ -257,11 +257,13 @@
<% end %> <% end %>
<% end -%> <% end -%>
<% if @tpm_path -%> <% if @tpm_path || @tpm_version -%>
<%# TPM Device -%> <%# TPM Device -%>
<tpm model='<%= @tpm_model %>'> <tpm model='<%= @tpm_model %>'>
<backend type='<%= @tpm_type %>'> <backend type='<%= @tpm_type %>'<% if @tpm_version %> version='<%= @tpm_version %>'<% end %>>
<% if @tpm_path -%>
<device path='<%= @tpm_path %>'/> <device path='<%= @tpm_path %>'/>
<% end -%>
</backend> </backend>
</tpm> </tpm>
<% end -%> <% end -%>

View File

@@ -48,8 +48,19 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do
expect(subject.call(env)).to be_nil expect(subject.call(env)).to be_nil
end 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(:updated_test_file) { 'default_added_tpm_path.xml' }
let(:vagrantfile_providerconfig) do let(:vagrantfile_providerconfig) do
<<-EOF <<-EOF
@@ -61,7 +72,90 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do
it 'should modify the domain tpm_path' do it 'should modify the domain tpm_path' do
expect(libvirt_domain).to receive(:undefine) 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(servers).to receive(:create).with(xml: updated_domain_xml)
expect(libvirt_domain).to receive(:autostart=) expect(libvirt_domain).to receive(:autostart=)
expect(domain).to receive(:start) expect(domain).to receive(:start)

View File

@@ -0,0 +1,48 @@
<domain xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0' type=''>
<name/>
<title/>
<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>

View File

@@ -105,4 +105,28 @@ describe 'templates/domain' do
expect(domain.to_xml('domain')).to eq xml_expected expect(domain.to_xml('domain')).to eq xml_expected
end end
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 end

View File

@@ -0,0 +1,53 @@
<domain type='' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<name></name>
<title></title>
<description></description>
<uuid></uuid>
<memory></memory>
<vcpu>1</vcpu>
<cpu mode='host-model'>
<model fallback='allow'></model>
</cpu>
<os>
<type>hvm</type>
<kernel></kernel>
<initrd></initrd>
<cmdline></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 type='mouse' bus='ps2'/>
<graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1' keymap='en-us' />
<video>
<model type='cirrus' vram='9216' heads='1'/>
</video>
<tpm model='tpm-tis'>
<backend type='passthrough'>
<device path='/dev/tpm0'/>
</backend>
</tpm>
</devices>
</domain>

View File

@@ -0,0 +1,52 @@
<domain type='' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<name></name>
<title></title>
<description></description>
<uuid></uuid>
<memory></memory>
<vcpu>1</vcpu>
<cpu mode='host-model'>
<model fallback='allow'></model>
</cpu>
<os>
<type>hvm</type>
<kernel></kernel>
<initrd></initrd>
<cmdline></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 type='mouse' bus='ps2'/>
<graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1' keymap='en-us' />
<video>
<model type='cirrus' vram='9216' heads='1'/>
</video>
<tpm model='tpm-crb'>
<backend type='emulator' version='2.0'>
</backend>
</tpm>
</devices>
</domain>