diff --git a/docs/configuration.markdown b/docs/configuration.markdown
index 3d9ab9c..b1390dd 100644
--- a/docs/configuration.markdown
+++ b/docs/configuration.markdown
@@ -149,6 +149,14 @@ end
{:cpus => "2-3", :memory => "4096"}
]
```
+* `launchsecurity` - Configure Secure Encryption Virtualization for the guest, requires additional components to be configured to work, see [examples](./examples.html#secure-encryption-virtualization). For more information look at [libvirt documentation](https://libvirt.org/kbase/launch_security_sev.html).
+ ```
+ libvirt.launchsecurity :type => 'sev', :cbitpos => 47, :reducedPhysBits => 1, :policy => "0x0003"
+ ```
+* `memtune` - Configure the memtune settings for the guest, primarily exposed to facilitate enabling Secure Encryption Virtualization. Note that when configuring `hard_limit` that the value is in kB as opposed to `libvirt.memory` which is in Mb. Additionally it must be set to be higher than `libvirt.memory`, see [libvirt documentation](https://libvirt.org/kbase/launch_security_sev.html) for details on why.
+ ```
+ libvirt.memtune :type => "hard_limit", :value => 2500000 # Note here the value in kB (not in Mb)
+ ```
* `loader` - Sets path to custom UEFI loader.
* `kernel` - To launch the guest with a kernel residing on host filesystems.
Equivalent to qemu `-kernel`.
diff --git a/docs/examples.markdown b/docs/examples.markdown
index 00713cd..39731b4 100644
--- a/docs/examples.markdown
+++ b/docs/examples.markdown
@@ -477,6 +477,66 @@ Vagrant.configure("2") do |config|
end
```
+## Secure Encryption Virtualization (SEV)
+
+Secure Encryption Virtualization is supported by libvirt and by the vagrant-libvirt provider but comes with several requirements.
+
+This mode has only been tested with q35 types of machines, so you'll need an UEFI boot
+
+```ruby
+Vagrant.configure("2") do |config|
+ config.vm.provider :libvirt do |libvirt|
+ libvirt.loader = "/usr/share/OVMF/OVMF_CODE.fd"
+ libvirt.nvram = "/path/to/ovmf/OVMF_VARS.fd"
+ libvirt.machine_type = 'pc-q35-focal'
+ end
+end
+```
+
+Read the libvirt documentaiton to understand what OVMF is and how to use it.
+
+Next, you'll want to call the following methods:
+```ruby
+Vagrant.configure("2") do |config|
+ config.vm.provider :libvirt do |libvirt|
+ libvirt.launchsecurity :type => 'sev', :cbitpos => 47, :reducedPhysBits => 1, :policy => "0x0003"
+ libvirt.memtune :type => "hard_limit", :value => 2500000 # Note here the value in kB (not in Mb)
+ end
+end
+```
+
+Note that the value provided in the memtune `hard_limit` is in Kb by default. It should be higher than the
+one given in `libvirt.memory` (which is in Mb, by the way) by some amount (again, check out the [https://libvirt.org/kbase/launch_security_sev.html](documentation)) to understand why.
+
+It is also necessary to explicitly define the memballoon for it to accept the iommu flag.
+
+```ruby
+Vagrant.configure("2") do |config|
+ config.vm.provider :libvirt do |libvirt|
+ libvirt.memballoon_enabled = true
+ libvirt.memballoon_model = 'virtio'
+ libvirt.memballoon_pci_bus = '0x07'
+ libvirt.memballoon_pci_slot = '0x00'
+ end
+end
+```
+
+And finally, because the iommu flag has to be passed to the networks, you also need to set it explicitly:
+
+```ruby
+Vagrant.configure("2") do |config|
+ config.vm.provider :libvirt do |libvirt|
+ # Management network only (the NAT'ed network provided by Vagrant)
+ libvirt.management_network_driver_iommu = true
+ end
+ # Example in defining a bridge
+ config.vm.network :public_network, :dev => "br0", :bridge => "br0", :mode => "bridge", :type => "bridge", :driver_iommu => true # <== Note here the additional flag
+end
+```
+
+Don't forget that you'll need an UEFI base box.
+
+
## 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 f97f419..fc102f6 100644
--- a/lib/vagrant-libvirt/action/create_domain.rb
+++ b/lib/vagrant-libvirt/action/create_domain.rb
@@ -38,6 +38,7 @@ module VagrantPlugins
@features_hyperv = config.features_hyperv
@clock_offset = config.clock_offset
@clock_timers = config.clock_timers
+ @launchsecurity_data = config.launchsecurity_data
@shares = config.shares
@cpu_mode = config.cpu_mode
@cpu_model = config.cpu_model
@@ -52,6 +53,8 @@ module VagrantPlugins
@nested = config.nested
@memory_size = config.memory.to_i * 1024
@memory_backing = config.memory_backing
+ @memtunes = config.memtunes
+
@management_network_mac = config.management_network_mac
@domain_volume_cache = config.volume_cache || 'default'
@kernel = config.kernel
@@ -240,6 +243,10 @@ module VagrantPlugins
@memory_backing.each do |backing|
env[:ui].info(" -- Memory Backing: #{backing[:name]}: #{backing[:config].map { |k,v| "#{k}='#{v}'"}.join(' ')}")
end
+
+ @memtunes.each do |type, options|
+ env[:ui].info(" -- Memory Tuning: #{type}: #{options[:config].map { |k,v| "#{k}='#{v}'"}.join(' ')}, value: #{options[:value]}")
+ end
unless @shares.nil?
env[:ui].info(" -- Shares: #{@shares}")
end
@@ -311,6 +318,10 @@ module VagrantPlugins
env[:ui].info(" -- Disks: #{_disks_print(@disks)}")
end
+ if not @launchsecurity_data.nil?
+ env[:ui].info(" -- Launch security: #{@launchsecurity_data.map { |k, v| "#{k.to_s}=#{v}" }.join(", ")}")
+ end
+
@disks.each do |disk|
msg = " -- Disk(#{disk[:device]}): #{disk[:absolute_path]}"
msg += ' Shared' if disk[:shareable]
diff --git a/lib/vagrant-libvirt/action/create_network_interfaces.rb b/lib/vagrant-libvirt/action/create_network_interfaces.rb
index 3b98b24..c4c5ca2 100644
--- a/lib/vagrant-libvirt/action/create_network_interfaces.rb
+++ b/lib/vagrant-libvirt/action/create_network_interfaces.rb
@@ -76,6 +76,7 @@ module VagrantPlugins
@mac = iface_configuration.fetch(:mac, false)
@model_type = iface_configuration.fetch(:model_type, @nic_model_type)
@driver_name = iface_configuration.fetch(:driver_name, false)
+ @driver_iommu = iface_configuration.fetch(:driver_iommu, false )
@driver_queues = iface_configuration.fetch(:driver_queues, false)
@device_name = iface_configuration.fetch(:iface_name, false)
@mtu = iface_configuration.fetch(:mtu, nil)
@@ -84,12 +85,15 @@ module VagrantPlugins
template_name = 'interface'
@type = nil
@udp_tunnel = nil
+
+ @logger.debug("Interface configuration: #{iface_configuration}")
# Configuration for public interfaces which use the macvtap driver
if iface_configuration[:iface_type] == :public_network
@device = iface_configuration.fetch(:dev, 'eth0')
@mode = iface_configuration.fetch(:mode, 'bridge')
@type = iface_configuration.fetch(:type, 'direct')
@model_type = iface_configuration.fetch(:model_type, @nic_model_type)
+ @driver_iommu = iface_configuration.fetch(:driver_iommu, false )
@driver_name = iface_configuration.fetch(:driver_name, false)
@driver_queues = iface_configuration.fetch(:driver_queues, false)
@portgroup = iface_configuration.fetch(:portgroup, nil)
@@ -124,13 +128,14 @@ module VagrantPlugins
}
@tunnel_type = iface_configuration.fetch(:model_type, @nic_model_type)
@driver_name = iface_configuration.fetch(:driver_name, false)
+ @driver_iommu = iface_configuration.fetch(:driver_iommu, false )
@driver_queues = iface_configuration.fetch(:driver_queues, false)
template_name = 'tunnel_interface'
@logger.info("Setting up #{@type} tunnel interface using #{@tunnel_ip} port #{@tunnel_port}")
end
message = "Creating network interface eth#{@iface_number}"
- message += " connected to network #{@network_name}."
+ message += " connected to network #{@network_name} based on template #{template_name}."
if @mac
@mac = @mac.scan(/(\h{2})/).join(':')
message += " Using MAC address: #{@mac}"
@@ -141,7 +146,9 @@ module VagrantPlugins
# FIXME: all options for network driver should be hash from Vagrantfile
driver_options = {}
driver_options[:name] = @driver_name if @driver_name
+ driver_options[:iommu] = @driver_iommu ? "on" : "off"
driver_options[:queues] = @driver_queues if @driver_queues
+
@udp_tunnel ||= {}
xml = if template_name == 'interface' or
template_name == 'tunnel_interface'
@@ -259,12 +266,15 @@ module VagrantPlugins
xml.source(source_options) do
xml.local(udp_tunnel) if type == 'udp'
end
+
+ @logger.debug "Driver options: #{driver_options}"
+
xml.mac(address: mac) if mac
xml.target(dev: target_dev_name(device_name, type, iface_number))
xml.alias(name: "net#{iface_number}")
xml.model(type: model_type.to_s)
xml.mtu(size: Integer(mtu)) if mtu
- xml.driver(driver_options)
+ xml.driver(**driver_options)
xml.address(type: 'pci', bus: pci_bus, slot: pci_slot) if pci_bus and pci_slot
end
end.to_xml(
diff --git a/lib/vagrant-libvirt/action/start_domain.rb b/lib/vagrant-libvirt/action/start_domain.rb
index 29dea41..45155c3 100644
--- a/lib/vagrant-libvirt/action/start_domain.rb
+++ b/lib/vagrant-libvirt/action/start_domain.rb
@@ -194,6 +194,68 @@ module VagrantPlugins
end
end
+ # Launch security
+ launchSecurity = REXML::XPath.first(xml_descr, '/domain/launchSecurity')
+ unless config.launchsecurity_data.nil?
+ if launchSecurity.nil?
+ @logger.debug "Launch security has been added"
+ launchSecurity = REXML::Element.new('launchSecurity', REXML::XPath.first(xml_descr, '/domain'))
+ descr_changed = true
+ end
+
+ if launchSecurity.attributes['type'] != config.launchsecurity_data[:type]
+ launchSecurity.attributes['type'] = config.launchsecurity_data[:type]
+ descr_changed = true
+ end
+
+ [:cbitpos, :policy, :reducedPhysBits].each do |setting|
+ setting_value = config.launchsecurity_data[setting]
+ element = REXML::XPath.first(launchSecurity, setting.to_s)
+ if !setting_value.nil?
+ if element.nil?
+ element = launchSecurity.add_element(setting.to_s)
+ descr_changed = true
+ end
+
+ if element.text != setting_value
+ @logger.debug "launchSecurity #{setting.to_s} config changed"
+ element.text = setting_value
+ descr_changed = true
+ end
+ else
+ if !element.nil?
+ launchSecurity.delete_element(setting.to_s)
+ descr_changed = true
+ end
+ end
+ end
+
+ controllers = REXML::XPath.each( xml_descr, '/domain/devices/controller')
+ memballoon = REXML::XPath.each( xml_descr, '/domain/devices/memballoon')
+ [controllers, memballoon].lazy.flat_map(&:lazy).each do |controller|
+ driver_node = REXML::XPath.first(controller, 'driver')
+ driver_node = controller.add_element('driver') if driver_node.nil?
+ descr_changed = true if driver_node.attributes['iommu'] != 'on'
+ driver_node.attributes['iommu'] = 'on'
+ end
+ else
+ unless launchSecurity.nil?
+ @logger.debug "Launch security to be deleted"
+
+ descr_changed = true
+
+ launchSecurity.parent.delete_element(launchSecurity)
+ end
+
+ REXML::XPath.each( xml_descr, '/domain/devices/controller') do | controller |
+ driver_node = REXML::XPath.first(controller, 'driver')
+ if !driver_node.nil?
+ descr_changed = true if driver_node.attributes['iommu']
+ driver_node.attributes.delete('iommu')
+ end
+ end
+ end
+
# Graphics
graphics = REXML::XPath.first(xml_descr, '/domain/devices/graphics')
if config.graphics_type != 'none'
diff --git a/lib/vagrant-libvirt/config.rb b/lib/vagrant-libvirt/config.rb
index 94daf0c..c311828 100644
--- a/lib/vagrant-libvirt/config.rb
+++ b/lib/vagrant-libvirt/config.rb
@@ -67,6 +67,7 @@ module VagrantPlugins
attr_accessor :management_network_domain
attr_accessor :management_network_mtu
attr_accessor :management_network_keep
+ attr_accessor :management_network_driver_iommu
# System connection information
attr_accessor :system_uri
@@ -81,6 +82,7 @@ module VagrantPlugins
attr_accessor :memory
attr_accessor :nodeset
attr_accessor :memory_backing
+ attr_accessor :memtunes
attr_accessor :channel
attr_accessor :cpus
attr_accessor :cpuset
@@ -95,6 +97,7 @@ module VagrantPlugins
attr_accessor :features_hyperv
attr_accessor :clock_offset
attr_accessor :clock_timers
+ attr_accessor :launchsecurity_data
attr_accessor :numa_nodes
attr_accessor :loader
attr_accessor :nvram
@@ -243,6 +246,7 @@ module VagrantPlugins
@management_network_domain = UNSET_VALUE
@management_network_mtu = UNSET_VALUE
@management_network_keep = UNSET_VALUE
+ @management_network_driver_iommu = UNSET_VALUE
# System connection information
@system_uri = UNSET_VALUE
@@ -254,6 +258,7 @@ module VagrantPlugins
@memory = UNSET_VALUE
@nodeset = UNSET_VALUE
@memory_backing = UNSET_VALUE
+ @memtunes = {}
@cpus = UNSET_VALUE
@cpuset = UNSET_VALUE
@cpu_mode = UNSET_VALUE
@@ -267,6 +272,7 @@ module VagrantPlugins
@features_hyperv = UNSET_VALUE
@clock_offset = UNSET_VALUE
@clock_timers = []
+ @launchsecurity_data = UNSET_VALUE
@numa_nodes = UNSET_VALUE
@loader = UNSET_VALUE
@nvram = UNSET_VALUE
@@ -528,6 +534,37 @@ module VagrantPlugins
config: config)
end
+ def memtune(config={})
+ if config[:type].nil?
+ raise "Missing memtune type"
+ end
+
+ unless ['hard_limit', 'soft_limit', 'swap_hard_limit'].include? config[:type]
+ raise "Memtune type '#{config[:type]}' not allowed (hard_limit, soft_limit, swap_hard_limit are allowed)"
+ end
+
+ if config[:value].nil?
+ raise "Missing memtune value"
+ end
+
+ opts = config[:options] || {}
+ opts[:unit] = opts[:unit] || "KiB"
+
+ @memtunes[config[:type]] = { value: config[:value], config: opts }
+ end
+
+ def launchsecurity(options = {})
+ if options.fetch(:type) != 'sev'
+ raise "Launch security type only supports SEV. Explicitly set 'sev' as a type"
+ end
+
+ @launchsecurity_data = {}
+ @launchsecurity_data[:type] = options[:type]
+ @launchsecurity_data[:cbitpos] = options[:cbitpos] || 47
+ @launchsecurity_data[:reducedPhysBits] = options[:reducedPhysBits] || 1
+ @launchsecurity_data[:policy] = options[:policy] || "0x0003"
+ end
+
def input(options = {})
if options[:type].nil? || options[:bus].nil?
raise 'Input type AND bus must be specified'
@@ -916,6 +953,7 @@ module VagrantPlugins
@management_network_domain = nil if @management_network_domain == UNSET_VALUE
@management_network_mtu = nil if @management_network_mtu == UNSET_VALUE
@management_network_keep = false if @management_network_keep == UNSET_VALUE
+ @management_network_driver_iommu = false if @management_network_driver_iommu == UNSET_VALUE
# Domain specific settings.
@title = '' if @title == UNSET_VALUE
@@ -954,6 +992,7 @@ module VagrantPlugins
@features_hyperv = [] if @features_hyperv == UNSET_VALUE
@clock_offset = 'utc' if @clock_offset == UNSET_VALUE
@clock_timers = [] if @clock_timers == UNSET_VALUE
+ @launchsecurity_data = nil if @launchsecurity_data == UNSET_VALUE
@numa_nodes = @numa_nodes == UNSET_VALUE ? nil : _generate_numa
@loader = nil if @loader == UNSET_VALUE
@nvram = nil if @nvram == UNSET_VALUE
@@ -1224,6 +1263,8 @@ module VagrantPlugins
c += other.floppies
result.floppies = c
+ result.memtunes = memtunes.merge(other.memtunes)
+
result.disk_driver_opts = disk_driver_opts.merge(other.disk_driver_opts)
result.inputs = inputs != UNSET_VALUE ? inputs.dup + (other.inputs != UNSET_VALUE ? other.inputs : []) : other.inputs
diff --git a/lib/vagrant-libvirt/templates/domain.xml.erb b/lib/vagrant-libvirt/templates/domain.xml.erb
index 7064a10..53e48b3 100644
--- a/lib/vagrant-libvirt/templates/domain.xml.erb
+++ b/lib/vagrant-libvirt/templates/domain.xml.erb
@@ -45,6 +45,13 @@
<<%= backing[:name] %> <%= backing[:config].map { |k,v| "#{k}='#{v}'"}.join(' ') %>/>
<%- end -%>
+<%- end -%>
+<%- unless @memtunes.empty? -%>
+
+ <%- @memtunes.each do |name, options| -%>
+ <<%= name %> <%= options[:config].map { |k,v| "#{k}='#{v}'"}.join(' ') %>><%= options[:value] %><%= name %>>
+ <%- end -%>
+
<%- end%>
<%- if !@cpu_affinity.empty? || @shares -%>
@@ -229,7 +236,11 @@
<%- end -%>
<%- @inputs.each do |input| -%>
-
+
+ <%- unless @launchsecurity_data.nil? -%>
+
+ <%- end -%>
+
<%- end -%>
<%- if !@sound_type.nil? -%>
<%# Sound device-%>
@@ -265,6 +276,9 @@
<%- if @rng[:model] == "random"%>
/dev/random
+ <%- unless @launchsecurity_data.nil? -%>
+
+ <%- end -%>
<%- end -%>
<%-
@@ -338,18 +352,32 @@
<%- end -%>
<%- if not @usbctl_dev.empty? -%>
<%# USB Controller -%>
- />
+ >
+ <%- unless @launchsecurity_data.nil? -%>
+
+ <%- end -%>
+
<%- end -%>
<%- unless @memballoon_enabled.nil? -%>
<%- if @memballoon_enabled -%>
+ <%- unless @launchsecurity_data.nil? -%>
+
+ <%- end -%>
<%- else -%>
<%- end -%>
<%- end -%>
+<%- unless @launchsecurity_data.nil? -%>
+
+ <%= @launchsecurity_data[:cbitpos] %>
+ <%= @launchsecurity_data[:reducedPhysBits] %>
+ <%= @launchsecurity_data[:policy] %>
+
+<%- end -%>
<%- if not @qemu_args.empty? or not @qemu_env.empty? -%>
<%- @qemu_args.each do |arg| -%>
diff --git a/lib/vagrant-libvirt/templates/public_interface.xml.erb b/lib/vagrant-libvirt/templates/public_interface.xml.erb
index 83bc528..cf98a09 100644
--- a/lib/vagrant-libvirt/templates/public_interface.xml.erb
+++ b/lib/vagrant-libvirt/templates/public_interface.xml.erb
@@ -12,12 +12,15 @@
<% end %>
<% if @driver_name and @driver_queues %>
-
+ iommu="on" <% end %> name='<%=@driver_name%>' queues='<%=@driver_queues%>'/>
<% elsif @driver_queues %>
-
+ iommu="on" <% end %> queues='<%=@driver_queues%>'/>
<% elsif @driver_name %>
-
+ iommu="on" <% end %> name='<%=@driver_name%>'/>
+ <% elsif @driver_iommu %>
+
<% end %>
+
<% if @ovs %>
<% if @ovs_interfaceid %>
diff --git a/lib/vagrant-libvirt/util/network_util.rb b/lib/vagrant-libvirt/util/network_util.rb
index 2e7cc60..b29182e 100644
--- a/lib/vagrant-libvirt/util/network_util.rb
+++ b/lib/vagrant-libvirt/util/network_util.rb
@@ -33,6 +33,7 @@ module VagrantPlugins
management_network_domain = env[:machine].provider_config.management_network_domain
management_network_mtu = env[:machine].provider_config.management_network_mtu
management_network_keep = env[:machine].provider_config.management_network_keep
+ management_network_driver_iommu = env[:machine].provider_config.management_network_driver_iommu
logger.info "Using #{management_network_name} at #{management_network_address} as the management network #{management_network_mode} is the mode"
begin
@@ -74,7 +75,7 @@ module VagrantPlugins
}
end
-
+ management_network_options[:driver_iommu] = management_network_driver_iommu
unless management_network_mac.nil?
management_network_options[:mac] = management_network_mac
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 1bcf93b..998200f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -73,6 +73,10 @@ RSpec.configure do |config|
# don't run acceptance tests by default
config.filter_run_excluding :acceptance => true
+
+ config.expect_with :rspec do |c|
+ c.max_formatted_output_length = 2000 if c.respond_to?("max_formatted_output_length=")
+ end
end
begin
diff --git a/spec/unit/action/create_domain_spec.rb b/spec/unit/action/create_domain_spec.rb
index ef4389c..b2b6e1c 100644
--- a/spec/unit/action/create_domain_spec.rb
+++ b/spec/unit/action/create_domain_spec.rb
@@ -185,6 +185,38 @@ describe VagrantPlugins::ProviderLibvirt::Action::CreateDomain do
end
end
+ context 'launchSecurity' do
+ let(:vagrantfile_providerconfig) do
+ <<-EOF
+ libvirt.launchsecurity :type => 'sev', :cbitpos => 47, :reducedPhysBits => 1, :policy => "0x0003"
+ EOF
+ end
+
+ it 'should emit the settings to the ui' do
+ expect(ui).to receive(:info).with(/ -- Launch security: type=sev, cbitpos=47, reducedPhysBits=1, policy=0x0003/)
+ expect(servers).to receive(:create).and_return(machine)
+
+ expect(subject.call(env)).to be_nil
+ end
+ end
+
+ context 'memtunes' do
+ let(:vagrantfile_providerconfig) do
+ <<-EOF
+ libvirt.memtune :type => 'hard_limit', :value => 250000
+ libvirt.memtune :type => 'soft_limit', :value => 200000
+ EOF
+ end
+
+ it 'should emit the settings to the ui' do
+ expect(ui).to receive(:info).with(/ -- Memory Tuning: hard_limit: unit='KiB', value: 250000/)
+ expect(ui).to receive(:info).with(/ -- Memory Tuning: soft_limit: unit='KiB', value: 200000/)
+ expect(servers).to receive(:create).and_return(machine)
+
+ expect(subject.call(env)).to be_nil
+ end
+ end
+
context 'sysinfo' do
let(:domain_xml_file) { 'sysinfo.xml' }
let(:vagrantfile_providerconfig) do
diff --git a/spec/unit/action/create_domain_spec/additional_disks_domain.xml b/spec/unit/action/create_domain_spec/additional_disks_domain.xml
index 0f1c949..95c04a0 100644
--- a/spec/unit/action/create_domain_spec/additional_disks_domain.xml
+++ b/spec/unit/action/create_domain_spec/additional_disks_domain.xml
@@ -41,7 +41,8 @@
-
+
+