Merge "libvirt: Launch instances with SEV-ES memory encryption"
This commit is contained in:
@@ -93,15 +93,14 @@ steps:
|
||||
|
||||
Since version 8.0.0, libvirt exposes maximum number of SEV guests
|
||||
which can run concurrently in its host, so the limit is automatically
|
||||
detected using this feature.
|
||||
detected using this feature. So it is not necessary to configure this option.
|
||||
|
||||
However in case an older version of libvirt is used, it is not possible for
|
||||
Nova to programmatically detect the correct value and Nova imposes no limit.
|
||||
So this configuration option serves as a stop-gap, allowing the cloud
|
||||
operator the option of providing this value manually.
|
||||
|
||||
This option also allows the cloud operator to set the limit lower than
|
||||
the actual hard limit.
|
||||
This option has been deprecated and will be removed in a future release.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -175,14 +174,11 @@ enable SEV for a flavor:
|
||||
$ openstack flavor set FLAVOR-NAME \
|
||||
--property hw:mem_encryption=true
|
||||
|
||||
These do not inherently cause a preference for SEV-capable hardware,
|
||||
but for now SEV is the only way of fulfilling the requirement for
|
||||
memory encryption. However in the future, support for other
|
||||
hardware-level guest memory encryption technology such as Intel MKTME
|
||||
may be added. If a guest specifically needs to be booted using SEV
|
||||
rather than any other memory encryption technology, it is possible to
|
||||
ensure this by setting the :nova:extra-spec:`trait{group}:HW_CPU_X86_AMD_SEV`
|
||||
extra spec or equivalent image metadata property to ``required``.
|
||||
It is also possible to use SEV-ES, instead of SEV, by setting
|
||||
the :nova:extra-spec:`hw:mem_encryption_model` extra spec to ``amd-sev-es``, or
|
||||
by using an image with the ``hw_mem_encryption_model`` property set to
|
||||
``amd-sev-es``. In case the extra spec and the property are unset or set to
|
||||
``amd-sev`` then SEV is used.
|
||||
|
||||
In all cases, SEV instances can only be booted from images which have
|
||||
the ``hw_firmware_type`` property set to ``uefi``, and only when the
|
||||
|
||||
@@ -407,6 +407,22 @@ feature_flag_validators = [
|
||||
'description': 'Whether to enable memory encryption',
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:mem_encryption_model',
|
||||
description=(
|
||||
'CPU feature used for memory encryption of the guest. '
|
||||
'This has no effect unless hw:mem_encryption (or equivalent '
|
||||
'image property) is set to True.'
|
||||
),
|
||||
value={
|
||||
'type': str,
|
||||
'description': 'A CPU feature used for memory encryption',
|
||||
'enum': [
|
||||
'amd-sev',
|
||||
'amd-sev-es',
|
||||
],
|
||||
},
|
||||
),
|
||||
base.ExtraSpecValidator(
|
||||
name='hw:pmem',
|
||||
description=(
|
||||
|
||||
+14
-6
@@ -184,7 +184,7 @@ class ResourceRequest(object):
|
||||
if disk:
|
||||
res_req._add_resource(orc.DISK_GB, disk)
|
||||
|
||||
res_req._translate_memory_encryption(request_spec.flavor, image)
|
||||
res_req._translate_mem_encryption_request(request_spec.flavor, image)
|
||||
|
||||
res_req._translate_vpmems_request(request_spec.flavor)
|
||||
|
||||
@@ -317,24 +317,32 @@ class ResourceRequest(object):
|
||||
LOG.debug("Requiring emulated TPM support via trait %s and %s.",
|
||||
version_trait, model_trait)
|
||||
|
||||
def _translate_memory_encryption(self, flavor, image):
|
||||
def _translate_mem_encryption_request(self, flavor, image):
|
||||
"""When the hw:mem_encryption extra spec or the hw_mem_encryption
|
||||
image property are requested, translate into a request for
|
||||
resources:MEM_ENCRYPTION_CONTEXT=1 which requires a slot on a
|
||||
host which can support encryption of the guest memory.
|
||||
host which can support encryption of the guest memory. Also require
|
||||
the specific trait for the requested memory encryption feature of CPU.
|
||||
"""
|
||||
# NOTE(aspiers): In theory this could raise FlavorImageConflict,
|
||||
# but we already check it in the API layer, so that should never
|
||||
# happen.
|
||||
if not hardware.get_mem_encryption_constraint(flavor, image):
|
||||
# happen
|
||||
mem_enc_config = hardware.get_mem_encryption_constraint(flavor, image)
|
||||
if not mem_enc_config:
|
||||
# No memory encryption required, so no further action required.
|
||||
return
|
||||
|
||||
self._add_resource(orc.MEM_ENCRYPTION_CONTEXT, 1)
|
||||
self._add_trait(os_traits.HW_CPU_X86_AMD_SEV, 'required')
|
||||
LOG.debug("Added %s=1 to requested resources",
|
||||
orc.MEM_ENCRYPTION_CONTEXT)
|
||||
|
||||
me_trait = os_traits.HW_CPU_X86_AMD_SEV
|
||||
if mem_enc_config.model == obj_fields.MemEncryptionModel.AMD_SEV_ES:
|
||||
me_trait = os_traits.HW_CPU_X86_AMD_SEV_ES
|
||||
self._add_trait(me_trait, 'required')
|
||||
LOG.debug("Requiring memory encryption model %s via trait %s",
|
||||
mem_enc_config.model, me_trait)
|
||||
|
||||
def _translate_vpmems_request(self, flavor):
|
||||
"""When the hw:pmem extra spec is present, require hosts which can
|
||||
provide enough vpmem resources.
|
||||
|
||||
@@ -253,8 +253,9 @@ class SevResphapeTests(base.ServersTestBase):
|
||||
hw_mem_enc_image['properties']['hw_mem_encryption'] = True
|
||||
self.glance.create(admin_context, hw_mem_enc_image)
|
||||
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._guest_configure_sev')
|
||||
def test_create_servers_with_amd_sev(self, mock_configure_sev):
|
||||
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver.'
|
||||
'_guest_configure_mem_encryption')
|
||||
def test_create_servers_with_amd_sev(self, mock_configure_me):
|
||||
self.hostname = self.start_compute(
|
||||
hostname='compute1',
|
||||
)
|
||||
|
||||
@@ -2327,16 +2327,22 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
|
||||
reqspec = self._get_request_spec(extra_specs, image)
|
||||
return utils.ResourceRequest.from_request_spec(reqspec)
|
||||
|
||||
def _get_expected_resource_request(self, mem_encryption_context):
|
||||
def _get_expected_resource_request(self, mem_encryption_model):
|
||||
expected_resources = {
|
||||
'VCPU': 1,
|
||||
'MEMORY_MB': 1024,
|
||||
'DISK_GB': 15,
|
||||
}
|
||||
required_traits = []
|
||||
if mem_encryption_context:
|
||||
if mem_encryption_model == 'amd-sev':
|
||||
expected_resources[orc.MEM_ENCRYPTION_CONTEXT] = 1
|
||||
required_traits = ['HW_CPU_X86_AMD_SEV']
|
||||
elif mem_encryption_model == 'amd-sev-es':
|
||||
expected_resources[orc.MEM_ENCRYPTION_CONTEXT] = 1
|
||||
required_traits = ['HW_CPU_X86_AMD_SEV_ES']
|
||||
elif mem_encryption_model is not None:
|
||||
self.fail('invalid mem_encryption_model: %s'
|
||||
% mem_encryption_model)
|
||||
|
||||
expected = FakeResourceRequest()
|
||||
expected._rg_by_id[None] = objects.RequestGroup(
|
||||
@@ -2349,7 +2355,7 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
|
||||
def _test_encrypted_memory_support_not_required(self, extra_specs,
|
||||
image=None):
|
||||
resreq = self._get_resource_request(extra_specs, image)
|
||||
expected = self._get_expected_resource_request(False)
|
||||
expected = self._get_expected_resource_request(None)
|
||||
|
||||
self.assertResourceRequestsEqual(expected, resreq)
|
||||
|
||||
@@ -2442,9 +2448,10 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
|
||||
|
||||
@mock.patch.object(utils, 'LOG')
|
||||
def _test_encrypted_memory_support_required(self, requesters, extra_specs,
|
||||
mock_log, image=None):
|
||||
mock_log, image=None,
|
||||
model='amd-sev'):
|
||||
resreq = self._get_resource_request(extra_specs, image)
|
||||
expected = self._get_expected_resource_request(True)
|
||||
expected = self._get_expected_resource_request(model)
|
||||
|
||||
self.assertResourceRequestsEqual(expected, resreq)
|
||||
mock_log.debug.assert_has_calls([
|
||||
@@ -2452,6 +2459,14 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
|
||||
orc.MEM_ENCRYPTION_CONTEXT)
|
||||
])
|
||||
|
||||
me_trait = 'HW_CPU_X86_AMD_SEV'
|
||||
if model == 'amd-sev-es':
|
||||
me_trait = 'HW_CPU_X86_AMD_SEV_ES'
|
||||
mock_log.debug.assert_has_calls([
|
||||
mock.call('Requiring memory encryption model %s via trait %s',
|
||||
model, me_trait)
|
||||
])
|
||||
|
||||
def test_encrypted_memory_support_extra_spec(self):
|
||||
for extra_spec in ('1', 'true', 'True'):
|
||||
self._test_encrypted_memory_support_required(
|
||||
@@ -2494,6 +2509,79 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
|
||||
hw_mem_encryption=image_prop))
|
||||
)
|
||||
|
||||
def test_encrypted_memory_model_extra_spec(self):
|
||||
for model in ('amd-sev', 'amd-sev-es'):
|
||||
self._test_encrypted_memory_support_required(
|
||||
'hw:mem_encryption extra spec',
|
||||
{'hw:mem_encryption': 'true',
|
||||
'hw:mem_encryption_model': model},
|
||||
image=objects.ImageMeta(
|
||||
id='005249be-3c2f-4351-9df7-29bb13c21b14',
|
||||
properties=objects.ImageMetaProps(
|
||||
hw_machine_type='q35',
|
||||
hw_firmware_type='uefi')),
|
||||
model=model
|
||||
)
|
||||
|
||||
def test_encrypted_memory_model_image_prop(self):
|
||||
for model in ('amd-sev', 'amd-sev-es'):
|
||||
self._test_encrypted_memory_support_required(
|
||||
'hw_mem_encryption image property',
|
||||
{},
|
||||
image=objects.ImageMeta(
|
||||
id='005249be-3c2f-4351-9df7-29bb13c21b14',
|
||||
name=self.image_name,
|
||||
properties=objects.ImageMetaProps(
|
||||
hw_machine_type='q35',
|
||||
hw_firmware_type='uefi',
|
||||
hw_mem_encryption='true',
|
||||
hw_mem_encryption_model=model)),
|
||||
model=model
|
||||
)
|
||||
|
||||
def test_encrypted_memory_model_both_required(self):
|
||||
for model in ('amd-sev', 'amd-sev-es'):
|
||||
self._test_encrypted_memory_support_required(
|
||||
'hw:mem_encryption extra spec and '
|
||||
'hw_mem_encryption image property',
|
||||
{'hw:mem_encryption': 'true',
|
||||
'hw:mem_encryption_model': model},
|
||||
image=objects.ImageMeta(
|
||||
id='005249be-3c2f-4351-9df7-29bb13c21b14',
|
||||
name=self.image_name,
|
||||
properties=objects.ImageMetaProps(
|
||||
hw_machine_type='q35',
|
||||
hw_firmware_type='uefi',
|
||||
hw_mem_encryption='true',
|
||||
hw_mem_encryption_model=model)),
|
||||
model=model
|
||||
)
|
||||
|
||||
def test_encrypted_memory_model_conflict_1(self):
|
||||
for f_model, i_model in (
|
||||
('amd-sev', 'amd-sev-es'),
|
||||
('amd-sev-es', 'amd-sev')
|
||||
):
|
||||
image = objects.ImageMeta(
|
||||
name=self.image_name,
|
||||
properties=objects.ImageMetaProps(
|
||||
hw_machine_type='q35',
|
||||
hw_firmware_type='uefi',
|
||||
hw_mem_encryption='true',
|
||||
hw_mem_encryption_model=i_model
|
||||
)
|
||||
)
|
||||
reqspec = self._get_request_spec(
|
||||
extra_specs={
|
||||
'hw:mem_encryption': 'true',
|
||||
'hw:mem_encryption_model': f_model,
|
||||
},
|
||||
image=image)
|
||||
self.assertRaises(
|
||||
exception.FlavorImageConflict,
|
||||
utils.ResourceRequest.from_request_spec, reqspec
|
||||
)
|
||||
|
||||
|
||||
class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
|
||||
"""These test cases assert what happens when the group policy is missing
|
||||
|
||||
@@ -2716,6 +2716,17 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest):
|
||||
|
||||
self.assertXmlEqual(launch_security_expected, xml)
|
||||
|
||||
obj.policy = 0x0035
|
||||
xml = obj.to_xml()
|
||||
launch_security_expected = """
|
||||
<launchSecurity type="sev">
|
||||
<policy>0x0035</policy>
|
||||
<cbitpos>47</cbitpos>
|
||||
<reducedPhysBits>1</reducedPhysBits>
|
||||
</launchSecurity>"""
|
||||
|
||||
self.assertXmlEqual(launch_security_expected, xml)
|
||||
|
||||
def test_config_lxc(self):
|
||||
obj = config.LibvirtConfigGuest()
|
||||
obj.virt_type = "lxc"
|
||||
|
||||
@@ -3787,9 +3787,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
None, None, flavor, image_meta,
|
||||
)
|
||||
|
||||
def _test_sev_enabled(self, expected=None, host_sev_enabled=False,
|
||||
enc_extra_spec=None, enc_image_prop=None,
|
||||
hw_machine_type=None, hw_firmware_type=None):
|
||||
def _test_get_mem_encryption_config(
|
||||
self, expected=None, host_sev_enabled=False, enc_extra_spec=None,
|
||||
enc_image_prop=None, hw_machine_type=None, hw_firmware_type=None):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_amd_sev = host_sev_enabled
|
||||
|
||||
@@ -3812,42 +3812,45 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
{'id': '150d530b-1c57-4367-b754-1f1b5237923d'},
|
||||
{}, image_props)
|
||||
|
||||
enabled = drvr._sev_enabled(flavor, image_meta)
|
||||
me_config = drvr._get_mem_encryption_config(flavor, image_meta)
|
||||
|
||||
if expected is None:
|
||||
self.fail("_test_sev_enabled called without an expected "
|
||||
"return value. Maybe you expected an exception?")
|
||||
self.assertIsNone(me_config)
|
||||
else:
|
||||
self.assertEqual(expected, me_config)
|
||||
|
||||
self.assertEqual(expected, enabled)
|
||||
def test_get_mem_encryption_config_no_host_support(self):
|
||||
self._test_get_mem_encryption_config()
|
||||
|
||||
def test_sev_enabled_no_host_support(self):
|
||||
self._test_sev_enabled(False)
|
||||
def test_get_mem_encryption_config_host_support_no_flavor_image(self):
|
||||
self._test_get_mem_encryption_config(host_sev_enabled=True)
|
||||
|
||||
def test_sev_enabled_host_support_no_flavor_image(self):
|
||||
self._test_sev_enabled(False, host_sev_enabled=True)
|
||||
def test_get_mem_encryption_config_no_host_support_flavor_requested(self):
|
||||
self._test_get_mem_encryption_config(enc_extra_spec=True)
|
||||
|
||||
def test_sev_enabled_no_host_support_flavor_requested(self):
|
||||
self._test_sev_enabled(False, enc_extra_spec=True)
|
||||
def test_get_mem_encryption_config_no_host_support_image_requested(self):
|
||||
self._test_get_mem_encryption_config(enc_image_prop=True)
|
||||
|
||||
def test_sev_enabled_no_host_support_image_requested(self):
|
||||
self._test_sev_enabled(False, enc_image_prop=True)
|
||||
def test_get_mem_encryption_config_host_support_flavor_requested(self):
|
||||
expected = hardware.MemEncryptionConfig(
|
||||
model=fields.MemEncryptionModel.AMD_SEV)
|
||||
self._test_get_mem_encryption_config(
|
||||
expected, host_sev_enabled=True, enc_extra_spec=True,
|
||||
hw_firmware_type='uefi', hw_machine_type='q35')
|
||||
|
||||
def test_sev_enabled_host_support_flavor_requested(self):
|
||||
self._test_sev_enabled(True, host_sev_enabled=True,
|
||||
enc_extra_spec=True, hw_firmware_type='uefi',
|
||||
hw_machine_type='q35')
|
||||
|
||||
def test_sev_enabled_host_support_image_requested(self):
|
||||
self._test_sev_enabled(True, host_sev_enabled=True,
|
||||
enc_image_prop=True, hw_firmware_type='uefi',
|
||||
hw_machine_type='q35')
|
||||
def test_get_mem_encryption_config_host_support_image_requested(self):
|
||||
expected = hardware.MemEncryptionConfig(
|
||||
model=fields.MemEncryptionModel.AMD_SEV)
|
||||
self._test_get_mem_encryption_config(
|
||||
expected, host_sev_enabled=True, enc_image_prop=True,
|
||||
hw_firmware_type='uefi', hw_machine_type='q35')
|
||||
|
||||
# The cases where the flavor and image requests contradict each other
|
||||
# are already covered by test_hardware.MemEncryptionConflictTestCase
|
||||
# so we don't need to test them in great detail here.
|
||||
def test_sev_enabled_host_extra_spec_image_conflict(self):
|
||||
def test_get_mem_encryption_config_host_extra_spec_image_conflict(self):
|
||||
exc = self.assertRaises(exception.FlavorImageConflict,
|
||||
self._test_sev_enabled,
|
||||
self._test_get_mem_encryption_config,
|
||||
host_sev_enabled=True, enc_extra_spec=False,
|
||||
enc_image_prop=True)
|
||||
self.assertEqual(
|
||||
@@ -3855,9 +3858,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
"to False, conflicting with image fake_image which has "
|
||||
"hw_mem_encryption property explicitly set to True", str(exc))
|
||||
|
||||
def test_sev_enabled_host_extra_spec_no_uefi(self):
|
||||
def test_get_mem_encryption_config_host_extra_spec_no_uefi(self):
|
||||
exc = self.assertRaises(exception.FlavorImageConflict,
|
||||
self._test_sev_enabled,
|
||||
self._test_get_mem_encryption_config,
|
||||
host_sev_enabled=True, enc_extra_spec=True)
|
||||
self.assertEqual(
|
||||
"Memory encryption requested by hw:mem_encryption extra spec in "
|
||||
@@ -3865,9 +3868,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
"'hw_firmware_type' property set to 'uefi' or volume-backed "
|
||||
"instance was requested", str(exc))
|
||||
|
||||
def test_sev_enabled_host_extra_spec_no_machine_type(self):
|
||||
def test_get_mem_encryption_config_host_extra_spec_no_machine_type(self):
|
||||
exc = self.assertRaises(exception.InvalidMachineType,
|
||||
self._test_sev_enabled,
|
||||
self._test_get_mem_encryption_config,
|
||||
host_sev_enabled=True, enc_extra_spec=True,
|
||||
hw_firmware_type='uefi')
|
||||
self.assertEqual(
|
||||
@@ -3875,9 +3878,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
"(150d530b-1c57-4367-b754-1f1b5237923d): q35 type is required "
|
||||
"for SEV to work", str(exc))
|
||||
|
||||
def test_sev_enabled_host_extra_spec_pc(self):
|
||||
def test_get_mem_encryption_config_host_extra_spec_pc(self):
|
||||
exc = self.assertRaises(exception.InvalidMachineType,
|
||||
self._test_sev_enabled,
|
||||
self._test_get_mem_encryption_config,
|
||||
host_sev_enabled=True, enc_extra_spec=True,
|
||||
hw_firmware_type='uefi', hw_machine_type='pc')
|
||||
self.assertEqual(
|
||||
@@ -3920,7 +3923,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
self.assertEqual(47, feature.cbitpos)
|
||||
self.assertEqual(1, feature.reduced_phys_bits)
|
||||
|
||||
def _setup_sev_guest(self, extra_image_properties=None):
|
||||
def _setup_sev_guest(self, extra_image_properties=None, model=None):
|
||||
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
|
||||
drvr._host._supports_uefi = True
|
||||
drvr._host._supports_amd_sev = True
|
||||
@@ -3933,6 +3936,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
extra_specs = {
|
||||
"hw:mem_encryption": True,
|
||||
}
|
||||
if model:
|
||||
extra_specs['hw:mem_encryption_model'] = model
|
||||
flavor = objects.Flavor(
|
||||
id=42, name='m1.small', memory_mb=6,
|
||||
vcpus=28, root_gb=496, ephemeral_gb=8128,
|
||||
@@ -3960,15 +3965,23 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
image_meta, disk_info,
|
||||
context=ctxt)
|
||||
|
||||
def test_get_guest_config_sev_no_feature(self):
|
||||
@ddt.data(None, 'amd-sev', 'amd-sev-es')
|
||||
def test_get_guest_config_sev_no_feature(self, sev_model):
|
||||
self.assertRaises(exception.MissingDomainCapabilityFeatureException,
|
||||
self._setup_sev_guest)
|
||||
self._setup_sev_guest, model=sev_model)
|
||||
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
{'sev_model': None, 'sev_policy': 0x0033},
|
||||
{'sev_model': 'amd-sev', 'sev_policy': 0x0033},
|
||||
{'sev_model': 'amd-sev-es', 'sev_policy': 0x0035}
|
||||
)
|
||||
@mock.patch.object(host.Host, 'get_domain_capabilities')
|
||||
@mock.patch.object(designer, 'set_driver_iommu_for_all_devices')
|
||||
def test_get_guest_config_sev(self, mock_designer, fake_domain_caps):
|
||||
def test_get_guest_config_sev(self, mock_designer, fake_domain_caps,
|
||||
sev_model, sev_policy):
|
||||
self._setup_fake_domain_caps(fake_domain_caps)
|
||||
cfg = self._setup_sev_guest()
|
||||
cfg = self._setup_sev_guest(model=sev_model)
|
||||
|
||||
# SEV-related tag should be set
|
||||
self.assertIsInstance(cfg.launch_security,
|
||||
@@ -3976,6 +3989,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
self.assertIsInstance(cfg.membacking,
|
||||
vconfig.LibvirtConfigGuestMemoryBacking)
|
||||
self.assertTrue(cfg.membacking.locked)
|
||||
self.assertEqual(sev_policy, cfg.launch_security.policy)
|
||||
|
||||
mock_designer.assert_called_once_with(cfg)
|
||||
|
||||
@@ -7359,14 +7373,20 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
self.assertEqual(cfg.devices[6].type, "unix")
|
||||
self.assertEqual(cfg.devices[6].target_name, "org.qemu.guest_agent.0")
|
||||
|
||||
@ddt.unpack
|
||||
@ddt.data(
|
||||
{'sev_model': None},
|
||||
{'sev_model': 'amd-sev'},
|
||||
{'sev_model': 'amd-sev-es'}
|
||||
)
|
||||
@mock.patch.object(host.Host, 'get_domain_capabilities')
|
||||
@mock.patch.object(designer, 'set_driver_iommu_for_all_devices')
|
||||
def test_get_guest_config_with_qga_through_image_meta_with_sev(
|
||||
self, mock_designer, fake_domain_caps,
|
||||
self, mock_designer, fake_domain_caps, sev_model
|
||||
):
|
||||
self._setup_fake_domain_caps(fake_domain_caps)
|
||||
extra_properties = {"hw_qemu_guest_agent": "yes"}
|
||||
cfg = self._setup_sev_guest(extra_properties)
|
||||
cfg = self._setup_sev_guest(extra_properties, model=sev_model)
|
||||
|
||||
self.assertIsInstance(cfg.devices[8],
|
||||
vconfig.LibvirtConfigGuestController)
|
||||
@@ -10149,8 +10169,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
|
||||
return fake_config
|
||||
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_sev_enabled',
|
||||
new=mock.Mock(return_value=False))
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver,
|
||||
'_get_mem_encryption_config',
|
||||
new=mock.Mock(return_value=None))
|
||||
@mock.patch.object(volume_drivers.LibvirtFakeVolumeDriver, 'get_config')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_set_cache_mode')
|
||||
def test_get_volume_config(self, mock_set_cache_mode, mock_get_config):
|
||||
@@ -10177,8 +10198,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
mock_set_cache_mode.assert_called_once_with(returned_config)
|
||||
self.assertEqual(generated_config.to_xml(), returned_config.to_xml())
|
||||
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_sev_enabled',
|
||||
new=mock.Mock(return_value=True))
|
||||
@mock.patch.object(
|
||||
libvirt_driver.LibvirtDriver, '_get_mem_encryption_config',
|
||||
new=mock.Mock(return_value=hardware.MemEncryptionConfig(
|
||||
model=fields.MemEncryptionModel.AMD_SEV)))
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_set_cache_mode',
|
||||
new=mock.Mock())
|
||||
@mock.patch.object(volume_drivers.LibvirtFakeVolumeDriver, 'get_config')
|
||||
@@ -13547,7 +13570,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
guest, 'get_xml_desc', return_value=initial_xml
|
||||
),
|
||||
mock.patch.object(
|
||||
drvr, '_sev_enabled', new=mock.Mock(return_value=False)
|
||||
drvr, '_get_mem_encryption_config',
|
||||
new=mock.Mock(return_value=None)
|
||||
)
|
||||
):
|
||||
config = libvirt_migrate.get_updated_guest_xml(
|
||||
@@ -13755,7 +13779,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
guest, 'get_xml_desc', return_value=initial_xml
|
||||
),
|
||||
mock.patch.object(
|
||||
drvr, '_sev_enabled', new=mock.Mock(return_value=False)
|
||||
drvr, '_get_mem_encryption_config',
|
||||
new=mock.Mock(return_value=None)
|
||||
)
|
||||
):
|
||||
config = libvirt_migrate.get_updated_guest_xml(
|
||||
@@ -13801,7 +13826,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
|
||||
guest, 'get_xml_desc', return_value=initial_xml
|
||||
),
|
||||
mock.patch.object(
|
||||
drvr, '_sev_enabled', new=mock.Mock(return_value=False)
|
||||
drvr, '_get_mem_encryption_config',
|
||||
new=mock.Mock(return_value=None)
|
||||
)
|
||||
):
|
||||
config = libvirt_migrate.get_updated_guest_xml(
|
||||
@@ -25775,7 +25801,8 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
|
||||
mock_set_metadata.assert_called_once_with(config_meta)
|
||||
|
||||
@mock.patch('nova.virt.libvirt.designer.set_driver_iommu_for_device')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_sev_enabled')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver,
|
||||
'_get_mem_encryption_config')
|
||||
@mock.patch.object(objects.Instance, 'get_network_info')
|
||||
@mock.patch.object(objects.Instance, 'save')
|
||||
@mock.patch.object(libvirt_driver.LibvirtDriver, '_build_device_metadata')
|
||||
@@ -25785,8 +25812,8 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
|
||||
def _test_attach_interface(self, power_state, expected_flags,
|
||||
mock_get_domain, mock_attach, mock_info,
|
||||
mock_build, mock_save, mock_get_network_info,
|
||||
mock_sev_enabled, mock_designer_set_iommu,
|
||||
sev_enabled=False):
|
||||
mock_me_config, mock_designer_set_iommu,
|
||||
me_config=None):
|
||||
instance = self._create_instance()
|
||||
network_info = _fake_network_info(self)
|
||||
domain = FakeVirtDomain(fake_xml="""
|
||||
@@ -25804,7 +25831,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
|
||||
</domain>""")
|
||||
mock_get_domain.return_value = domain
|
||||
mock_info.return_value = [power_state, 1, 2, 3, 4]
|
||||
mock_sev_enabled.return_value = sev_enabled
|
||||
mock_me_config.return_value = me_config
|
||||
|
||||
fake_image_meta = objects.ImageMeta.from_dict(
|
||||
{'id': instance.image_ref})
|
||||
@@ -25830,7 +25857,7 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
|
||||
mock_get_network_info.assert_called_once_with()
|
||||
mock_attach.assert_called_once_with(expected.to_xml(),
|
||||
flags=expected_flags)
|
||||
if sev_enabled:
|
||||
if me_config:
|
||||
mock_designer_set_iommu.assert_called_once_with(expected)
|
||||
|
||||
def test_attach_interface_with_running_instance(self):
|
||||
@@ -25844,7 +25871,9 @@ class LibvirtDriverTestCase(test.NoDBTestCase, TraitsComparisonMixin):
|
||||
power_state.RUNNING,
|
||||
(fakelibvirt.VIR_DOMAIN_AFFECT_CONFIG |
|
||||
fakelibvirt.VIR_DOMAIN_AFFECT_LIVE),
|
||||
sev_enabled=True)
|
||||
me_config=hardware.MemEncryptionConfig(
|
||||
model=fields.MemEncryptionModel.AMD_SEV
|
||||
))
|
||||
|
||||
def test_attach_interface_with_pause_instance(self):
|
||||
self._test_attach_interface(
|
||||
|
||||
@@ -5333,7 +5333,7 @@ class MemEncryptionNotRequiredTestCase(test.NoDBTestCase):
|
||||
if image_meta is None:
|
||||
image_meta = objects.ImageMeta(properties=objects.ImageMetaProps())
|
||||
|
||||
self.assertFalse(hw.get_mem_encryption_constraint(flavor, image_meta))
|
||||
self.assertIsNone(hw.get_mem_encryption_constraint(flavor, image_meta))
|
||||
|
||||
def test_requirement_disabled(self):
|
||||
self._test_encrypted_memory_support_not_required()
|
||||
@@ -5509,8 +5509,8 @@ class MemEncryptionRequestedWithInvalidMachineTypeTestCase(
|
||||
flavor = objects.Flavor(name=self.flavor_name,
|
||||
extra_specs=extra_specs)
|
||||
exc = self.assertRaises(self.expected_exception,
|
||||
hw.get_mem_encryption_constraint,
|
||||
flavor, image_meta)
|
||||
hw.get_mem_encryption_constraint,
|
||||
flavor, image_meta)
|
||||
self.assertEqual(self.expected_error % error_data, str(exc))
|
||||
|
||||
def test_flavor_requires_encrypted_memory_support_pc(self):
|
||||
@@ -5571,8 +5571,10 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase):
|
||||
'id': self.image_id,
|
||||
'name': self.image_name,
|
||||
'properties': image_props})
|
||||
self.assertTrue(hw.get_mem_encryption_constraint(flavor,
|
||||
image_meta))
|
||||
expected = hw.MemEncryptionConfig(
|
||||
model=fields.MemEncryptionModel.AMD_SEV)
|
||||
self.assertEqual(
|
||||
expected, hw.get_mem_encryption_constraint(flavor, image_meta))
|
||||
mock_log.debug.assert_has_calls([
|
||||
mock.call("Memory encryption requested by %s", requesters)
|
||||
])
|
||||
@@ -5643,8 +5645,11 @@ class MemEncryptionRequiredTestCase(test.NoDBTestCase):
|
||||
'size': 0,
|
||||
'status': 'active'})
|
||||
|
||||
self.assertTrue(hw.get_mem_encryption_constraint(flavor,
|
||||
image_meta))
|
||||
expected = hw.MemEncryptionConfig(
|
||||
model=fields.MemEncryptionModel.AMD_SEV)
|
||||
self.assertEqual(
|
||||
expected,
|
||||
hw.get_mem_encryption_constraint(flavor, image_meta))
|
||||
|
||||
requesters = "hw:mem_encryption extra spec in %s flavor and " \
|
||||
"hw_mem_encryption property of image %s" % \
|
||||
|
||||
+75
-15
@@ -48,6 +48,10 @@ class VTPMConfig(ty.NamedTuple):
|
||||
model: str
|
||||
|
||||
|
||||
class MemEncryptionConfig(ty.NamedTuple):
|
||||
model: str
|
||||
|
||||
|
||||
def get_vcpu_pin_set():
|
||||
"""Parse ``vcpu_pin_set`` config.
|
||||
|
||||
@@ -1157,10 +1161,9 @@ def get_mem_encryption_constraint(
|
||||
flavor: 'objects.Flavor',
|
||||
image_meta: 'objects.ImageMeta',
|
||||
machine_type: ty.Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Return a boolean indicating whether encryption of guest memory was
|
||||
requested, either via the hw:mem_encryption extra spec or the
|
||||
hw_mem_encryption image property (or both).
|
||||
) -> ty.Optional[MemEncryptionConfig]:
|
||||
"""Return memory encryption context requested either via flavor extra specs
|
||||
or image properties (or both).
|
||||
|
||||
Also watch out for contradictory requests between the flavor and
|
||||
image regarding memory encryption, and raise an exception where
|
||||
@@ -1188,8 +1191,7 @@ def get_mem_encryption_constraint(
|
||||
:param machine_type: a string representing the machine type (optional)
|
||||
:raises: nova.exception.FlavorImageConflict
|
||||
:raises: nova.exception.InvalidMachineType
|
||||
:returns: boolean indicating whether encryption of guest memory
|
||||
was requested
|
||||
:returns: A named tuple containing the memory encryption model, else None.
|
||||
"""
|
||||
|
||||
flavor_mem_enc_str, image_mem_enc = _get_flavor_image_meta(
|
||||
@@ -1203,7 +1205,7 @@ def get_mem_encryption_constraint(
|
||||
# boolean is handled automatically
|
||||
|
||||
if not flavor_mem_enc and not image_mem_enc:
|
||||
return False
|
||||
return None
|
||||
|
||||
_check_for_mem_encryption_requirement_conflicts(
|
||||
flavor_mem_enc_str, flavor_mem_enc, image_mem_enc, flavor, image_meta)
|
||||
@@ -1215,20 +1217,53 @@ def get_mem_encryption_constraint(
|
||||
# encryption enabled, image_meta has no id key. See bug #2041511.
|
||||
# So we check whether id exists. If there is no value, we set it
|
||||
# to a sentinel value we can detect later. i.e. '<no-id>'.
|
||||
requesters = []
|
||||
enc_requesters = []
|
||||
if flavor_mem_enc:
|
||||
requesters.append("hw:mem_encryption extra spec in %s flavor" %
|
||||
flavor.name)
|
||||
enc_requesters.append("hw:mem_encryption extra spec in %s flavor" %
|
||||
flavor.name)
|
||||
if image_mem_enc:
|
||||
image_id = (image_meta.id if 'id' in image_meta else '<no-id>')
|
||||
requesters.append("hw_mem_encryption property of image %s" %
|
||||
image_id)
|
||||
enc_requesters.append("hw_mem_encryption property of image %s" %
|
||||
image_id)
|
||||
|
||||
_check_mem_encryption_uses_uefi_image(requesters, image_meta)
|
||||
_check_mem_encryption_uses_uefi_image(enc_requesters, image_meta)
|
||||
_check_mem_encryption_machine_type(image_meta, machine_type)
|
||||
|
||||
LOG.debug("Memory encryption requested by %s", " and ".join(requesters))
|
||||
return True
|
||||
LOG.debug("Memory encryption requested by %s",
|
||||
" and ".join(enc_requesters))
|
||||
|
||||
flavor_mem_enc_model, image_mem_enc_model = _get_flavor_image_meta(
|
||||
'mem_encryption_model', flavor, image_meta)
|
||||
_check_for_mem_encryption_model_conflicts(
|
||||
flavor_mem_enc_model, image_mem_enc_model, flavor, image_meta)
|
||||
|
||||
mem_enc_model = None
|
||||
model_requesters = []
|
||||
if flavor_mem_enc_model:
|
||||
mem_enc_model = flavor_mem_enc_model
|
||||
model_requesters.append(
|
||||
"hw:mem_encryption_model extra spec in %s flavor" % flavor.name)
|
||||
if image_mem_enc_model:
|
||||
mem_enc_model = image_mem_enc_model
|
||||
image_id = (image_meta.id if 'id' in image_meta else '<no-id>')
|
||||
model_requesters.append(
|
||||
"hw_mem_encryption_model property of image %s" % image_id)
|
||||
|
||||
if not mem_enc_model:
|
||||
return MemEncryptionConfig(model=fields.MemEncryptionModel.AMD_SEV)
|
||||
|
||||
LOG.debug("Memory encryption model requested by %s",
|
||||
" and ".join(model_requesters))
|
||||
|
||||
if mem_enc_model not in fields.MemEncryptionModel.ALL:
|
||||
raise exception.Invalid(
|
||||
("Invalid memory encryption model %(model)r. "
|
||||
"Allowed values: %(valid)s.") %
|
||||
{'model': mem_enc_model,
|
||||
'valid': ', '.join(fields.MemEncryptionModel.ALL)}
|
||||
)
|
||||
|
||||
return MemEncryptionConfig(model=mem_enc_model)
|
||||
|
||||
|
||||
def _check_for_mem_encryption_requirement_conflicts(
|
||||
@@ -1255,6 +1290,31 @@ def _check_for_mem_encryption_requirement_conflicts(
|
||||
raise exception.FlavorImageConflict(emsg % data)
|
||||
|
||||
|
||||
def _check_for_mem_encryption_model_conflicts(
|
||||
flavor_mem_enc_model, image_mem_enc_model, flavor, image_meta):
|
||||
# Check for conflicts between explicit requirements regarding
|
||||
# memory encryption model.
|
||||
if (flavor_mem_enc_model is not None and
|
||||
image_mem_enc_model is not None and
|
||||
flavor_mem_enc_model != image_mem_enc_model):
|
||||
emsg = _(
|
||||
"Flavor %(flavor_name)s has hw:mem_encryption_model extra "
|
||||
"spec explicitly set to %(flavor_val)s, conflicting with "
|
||||
"image %(image_name)s which has hw_mem_encryption_model property "
|
||||
"explicitly set to %(image_val)s"
|
||||
)
|
||||
# image_meta.name is not set if image object represents root
|
||||
# Cinder volume.
|
||||
image_name = (image_meta.name if 'name' in image_meta else None)
|
||||
data = {
|
||||
'flavor_name': flavor.name,
|
||||
'flavor_val': flavor_mem_enc_model,
|
||||
'image_name': image_name,
|
||||
'image_val': image_mem_enc_model,
|
||||
}
|
||||
raise exception.FlavorImageConflict(emsg % data)
|
||||
|
||||
|
||||
def _check_mem_encryption_uses_uefi_image(requesters, image_meta):
|
||||
if image_meta.properties.get('hw_firmware_type') == 'uefi':
|
||||
return
|
||||
|
||||
@@ -3023,6 +3023,8 @@ class LibvirtConfigGuestSEVLaunchSecurity(LibvirtConfigObject):
|
||||
super(LibvirtConfigGuestSEVLaunchSecurity, self).__init__(
|
||||
root_name='launchSecurity', **kwargs)
|
||||
|
||||
# hardcoded default for SEV according to the spec
|
||||
self.policy = 0x0033
|
||||
self.cbitpos = None
|
||||
self.reduced_phys_bits = None
|
||||
|
||||
@@ -3031,7 +3033,7 @@ class LibvirtConfigGuestSEVLaunchSecurity(LibvirtConfigObject):
|
||||
|
||||
root.set('type', 'sev')
|
||||
policy = etree.Element('policy')
|
||||
policy.text = '0x0033' # hardcoded default according to the spec
|
||||
policy.text = '0x%04x' % self.policy
|
||||
root.append(policy)
|
||||
|
||||
cbitpos = etree.Element('cbitpos')
|
||||
|
||||
+17
-13
@@ -2133,7 +2133,8 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
vol_driver = self._get_volume_driver(connection_info)
|
||||
conf = vol_driver.get_config(connection_info, disk_info)
|
||||
|
||||
if self._sev_enabled(instance.flavor, instance.image_meta):
|
||||
if self._get_mem_encryption_config(
|
||||
instance.flavor, instance.image_meta):
|
||||
designer.set_driver_iommu_for_device(conf)
|
||||
|
||||
self._set_cache_mode(conf)
|
||||
@@ -3007,7 +3008,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
instance.flavor,
|
||||
CONF.libvirt.virt_type)
|
||||
|
||||
if self._sev_enabled(instance.flavor, image_meta):
|
||||
if self._get_mem_encryption_config(instance.flavor, image_meta):
|
||||
designer.set_driver_iommu_for_device(cfg)
|
||||
|
||||
try:
|
||||
@@ -6872,7 +6873,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
# of AMD SEV, any virtio device should use iommu driver, and
|
||||
# libvirt does not know about it. That is why the controller
|
||||
# should be created manually.
|
||||
if self._sev_enabled(flavor, image_meta):
|
||||
if self._get_mem_encryption_config(flavor, image_meta):
|
||||
self._add_virtio_serial_controller(guest, instance)
|
||||
|
||||
LOG.debug("Qemu guest agent is enabled through image "
|
||||
@@ -6935,7 +6936,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
membacking.sharedaccess = True
|
||||
membacking.allocateimmediate = True
|
||||
membacking.discard = True
|
||||
if self._sev_enabled(flavor, image_meta):
|
||||
if self._get_mem_encryption_config(flavor, image_meta):
|
||||
if not membacking:
|
||||
membacking = vconfig.LibvirtConfigGuestMemoryBacking()
|
||||
membacking.locked = True
|
||||
@@ -7526,7 +7527,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
self._get_guest_os_type()
|
||||
)
|
||||
|
||||
sev_enabled = self._sev_enabled(flavor, image_meta)
|
||||
me_config = self._get_mem_encryption_config(flavor, image_meta)
|
||||
|
||||
self._configure_guest_by_virt_type(guest, instance, image_meta, flavor)
|
||||
if CONF.libvirt.virt_type != 'lxc':
|
||||
@@ -7604,10 +7605,11 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
if mdevs:
|
||||
self._guest_add_mdevs(guest, mdevs)
|
||||
|
||||
if sev_enabled:
|
||||
if me_config:
|
||||
caps = self._host.get_capabilities()
|
||||
self._guest_configure_sev(guest, caps.host.cpu.arch,
|
||||
guest.os_mach_type)
|
||||
self._guest_configure_mem_encryption(guest, caps.host.cpu.arch,
|
||||
guest.os_mach_type,
|
||||
me_config.model)
|
||||
|
||||
if vpmems:
|
||||
self._guest_add_vpmems(guest, vpmems)
|
||||
@@ -7649,7 +7651,7 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
guest.max_memory_slots += 1
|
||||
guest.add_device(vpmem_config)
|
||||
|
||||
def _sev_enabled(self, flavor, image_meta):
|
||||
def _get_mem_encryption_config(self, flavor, image_meta):
|
||||
"""To enable AMD SEV, the following should be true:
|
||||
|
||||
a) the supports_amd_sev instance variable in the host is
|
||||
@@ -7671,13 +7673,13 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
are run while determining whether SEV is selected.
|
||||
"""
|
||||
if not self._host.supports_amd_sev:
|
||||
return False
|
||||
return None
|
||||
|
||||
mach_type = libvirt_utils.get_machine_type(image_meta)
|
||||
return hardware.get_mem_encryption_constraint(flavor, image_meta,
|
||||
mach_type)
|
||||
|
||||
def _guest_configure_sev(self, guest, arch, mach_type):
|
||||
def _guest_configure_mem_encryption(self, guest, arch, mach_type, model):
|
||||
sev = self._find_sev_feature(arch, mach_type)
|
||||
if sev is None:
|
||||
# In theory this should never happen because it should
|
||||
@@ -7694,12 +7696,14 @@ class LibvirtDriver(driver.ComputeDriver):
|
||||
feature='sev')
|
||||
|
||||
designer.set_driver_iommu_for_all_devices(guest)
|
||||
self._guest_add_launch_security(guest, sev)
|
||||
self._guest_add_sev_launch_security(guest, sev, model)
|
||||
|
||||
def _guest_add_launch_security(self, guest, sev):
|
||||
def _guest_add_sev_launch_security(self, guest, sev, model):
|
||||
launch_security = vconfig.LibvirtConfigGuestSEVLaunchSecurity()
|
||||
launch_security.cbitpos = sev.cbitpos
|
||||
launch_security.reduced_phys_bits = sev.reduced_phys_bits
|
||||
if model == fields.MemEncryptionModel.AMD_SEV_ES:
|
||||
launch_security.policy = 0x0035
|
||||
guest.launch_security = launch_security
|
||||
|
||||
def _find_sev_feature(self, arch, mach_type):
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The libvirt driver can now support requests for guest RAM to be encrypted
|
||||
using the AMD SEV-ES(Secure Encrypted Virtualization-Encrypted State),
|
||||
instead of AMD SEV.
|
||||
|
||||
Usage of AMD SEV-ES for memory encryption can be required either via
|
||||
a flavor which has the ``hw:mem_encryption_model`` extra spec set to
|
||||
``amd-sev-es``, or via an image which has the ``hw_mem_encryption_model``
|
||||
property set to ``amd-sev-es``. In case the extra spec and the property are
|
||||
unset or set to ``amd-sev``, then AMD SEV is used for memory encryption.
|
||||
Reference in New Issue
Block a user