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_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

View File

@@ -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 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}")

View File

@@ -193,36 +193,31 @@ module VagrantPlugins
end
# TPM
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"
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
# 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
if backend.elements['device'].attributes['path'] != config.tpm_path
@logger.debug "tpm path updated from '#{backend.elements['device'].attributes['path']}' to '#{config.tpm_path}'"
unless "'#{newtpm}'".eql? "'#{oldtpm}'"
@logger.debug "tpm config changed"
descr_changed = true
backend.elements['device'].attributes['path'] = config.tpm_path
end
end
end

View File

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

View File

@@ -257,11 +257,13 @@
<% end %>
<% end -%>
<% if @tpm_path -%>
<% if @tpm_path || @tpm_version -%>
<%# TPM Device -%>
<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 %>'/>
<% end -%>
</backend>
</tpm>
<% end -%>

View File

@@ -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)

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
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

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>