Add SecureBoot support to the emulator, all drivers

This change adds a basic SecureBoot endpoint to the emulator which
returns the actual secure boot enabled status.

The fake and libvirt drivers supports setting the secure boot.

OVMF paths have aligned on /usr/share/OVMF/ since this is supported by
Red Hat and Debian family distros. Default and example firmware loader
is now OVMF_CODE.secboot.fd since this exists on all distros and is
known to work with both secure boot enabled and disabled on some
distros.

Change-Id: Icb85710d81c656da6853d67f5e1c6a8609abc1cf
This commit is contained in:
Steve Baker 2022-05-13 14:45:28 +12:00 committed by Julia Kreger
parent 8427349985
commit 41628d3546
17 changed files with 500 additions and 106 deletions

View File

@ -32,18 +32,23 @@ SUSHY_EMULATOR_IGNORE_BOOT_DEVICE = False
# The map of firmware loaders dependant on the boot mode and
# system architecture
# system architecture. Ideally the x86_64 loader will be capable
# of secure boot or not based on the chosen nvram.
SUSHY_EMULATOR_BOOT_LOADER_MAP = {
u'UEFI': {
u'x86_64': u'/usr/share/OVMF/OVMF_CODE.fd',
u'aarch64': u'/usr/share/AAVMF/AAVMF_CODE.fd'
'UEFI': {
'x86_64': u'/usr/share/OVMF/OVMF_CODE.secboot.fd',
'aarch64': u'/usr/share/AAVMF/AAVMF_CODE.fd'
},
u'Legacy': {
u'x86_64': None,
u'aarch64': None
'Legacy': {
'x86_64': None,
'aarch64': None
}
}
# nvram templates to use on x86_64 to enable or disable secure boot
SUSHY_EMULATOR_SECURE_BOOT_ENABLED_NVRAM = '/usr/share/OVMF/OVMF_VARS.secboot.fd'
SUSHY_EMULATOR_SECURE_BOOT_DISABLED_NVRAM = '/usr/share/OVMF/OVMF_VARS.fd'
# This map contains statically configured Redfish Chassis linked
# up with the Systems and Managers enclosed into this Chassis.
#

View File

@ -152,8 +152,8 @@ On the host you need to have OVMF firmware binaries installed. Fedora users
could pull them as `edk2-ovmf` RPM. On Ubuntu, `apt-get install ovmf` should
do the job.
Then you need to create a VM by running `virt-install` with the `--boot uefi`
option:
Then you need to create a VM by running `virt-install` with the UEFI specific
`--boot` options:
Example:
@ -163,7 +163,11 @@ Example:
virt-install \
--name vbmc-node \
--ram 1024 \
--boot uefi \
--boot loader.readonly=yes \
--boot loader.type=pflash \
--boot loader.secure=no \
--boot loader=/usr/share/OVMF/OVMF_CODE.secboot.fd \
--boot nvram.template=/usr/share/OVMF/OVMF_VARS.fd \
--disk size=1 \
--vcpus 2 \
--os-type linux \
@ -174,16 +178,27 @@ Example:
rm $tmpfile
This will create a new `libvirt` domain with path to OVMF images properly
configured. Let's take a note on the path to the blob:
configured. Let's take a note on the path to the blob by running `virsh dumpxml vbmc-node`:
.. code-block:: bash
Example:
$ virsh dumpxml vbmc-node | grep loader
<loader readonly='yes' type='pflash'>/usr/share/edk2/ovmf/OVMF_CODE.fd</loader>
.. code-block:: xml
Because now we need to add this path to emulator's configuration matching
VM architecture we are running. Make a copy of stock configuration file
and edit it accordingly:
<domain type="kvm">
...
<os>
<type arch="x86_64" machine="q35">hvm</type>
<loader readonly="yes" type="pflash" secure="no">/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd</loader>
<nvram template="/usr/share/edk2/ovmf/OVMF_VARS.fd"/>
<boot dev="hd"/>
</os>
...
</domain>
Because now we need to add this path to emulator's configuration matching VM
architecture we are running. It is also possible to make Redfish calls to enable
or disable Secure Boot by specifying which nvram template to load in each case.
Make a copy of stock configuration file and edit it accordingly:
.. code-block:: bash
@ -191,9 +206,11 @@ and edit it accordingly:
...
SUSHY_EMULATOR_BOOT_LOADER_MAP = {
'Uefi': {
'x86_64': '/usr/share/edk2/ovmf/OVMF_CODE.fd',
'x86_64': '/usr/share/OVMF/OVMF_CODE.secboot.fd',
...
}
SUSHY_EMULATOR_SECURE_BOOT_ENABLED_NVRAM = '/usr/share/OVMF/OVMF_VARS.secboot.fd'
SUSHY_EMULATOR_SECURE_BOOT_DISABLED_NVRAM = '/usr/share/OVMF/OVMF_VARS.fd'
...
Now you can run `sushy-emulator` with the updated configuration file:

View File

@ -0,0 +1,12 @@
---
features:
- |
It is now possible to enable and disable UEFI Secure Boot mode via Redfish
requests for the libvirt driver. This is possible by configuring domains to
use a secure boot capable firmware loader, and setting configuration values
`SUSHY_EMULATOR_SECURE_BOOT_ENABLED_NVRAM` and
`SUSHY_EMULATOR_SECURE_BOOT_DISABLED_NVRAM` to nvram template paths which
enable or disable secure boot.
The fake driver supports getting and setting secure boot, the nova driver
only supports getting.

View File

@ -558,6 +558,33 @@ def system_reset_bios(identity):
return '', 204
@app.route('/redfish/v1/Systems/<identity>/SecureBoot',
methods=['GET', 'PATCH'])
@api_utils.ensure_instance_access
@api_utils.returns_json
def secure_boot(identity):
if flask.request.method == 'GET':
secure = app.systems.get_secure_boot(identity)
app.logger.debug('Serving secure boot for system "%s"', identity)
return flask.render_template(
'secure_boot.json',
identity=identity,
secure_boot_enable=secure,
secure_boot_current_boot=secure and 'Enabled' or 'Disabled')
elif flask.request.method == 'PATCH':
secure = flask.request.json.get('SecureBootEnable')
app.systems.set_secure_boot(identity, secure)
app.logger.info('System "%s" secure boot updated to "%s"',
identity, secure)
return '', 204
@app.route('/redfish/v1/Systems/<identity>/SimpleStorage',
methods=['GET'])
@api_utils.ensure_instance_access

View File

@ -124,6 +124,24 @@ class AbstractSystemsDriver(metaclass=abc.ABCMeta):
"""
raise error.NotSupportedError('Not implemented')
def get_secure_boot(self, identity):
"""Get computer system secure boot state for UEFI boot mode.
:returns: boolean of the current secure boot state
:raises: `FishyError` if the state can't be fetched
"""
raise error.NotSupportedError('Not implemented')
def set_secure_boot(self, identity, secure):
"""Set computer system secure boot state for UEFI boot mode.
:param secure: boolean requesting the secure boot state
:raises: `FishyError` if the can't be set
"""
raise error.NotSupportedError('Not implemented')
def get_total_memory(self, identity):
"""Get computer system total memory

View File

@ -143,6 +143,12 @@ class FakeDriver(AbstractSystemsDriver):
def set_boot_mode(self, identity, boot_mode):
self._update(identity, boot_mode=boot_mode)
def get_secure_boot(self, identity):
return self._get(identity).get('secure_boot', False)
def set_secure_boot(self, identity, secure):
self._update(identity, secure_boot=secure)
def get_boot_image(self, identity, device):
devinfo = self._get(identity).get('boot_image') or {}
return devinfo.get(device) or (None, False, False)

View File

@ -101,7 +101,7 @@ class LibvirtDriver(AbstractSystemsDriver):
BOOT_LOADER_MAP = {
'UEFI': {
'x86_64': '/usr/share/OVMF/OVMF_CODE.fd',
'x86_64': '/usr/share/OVMF/OVMF_CODE.secboot.fd',
'aarch64': '/usr/share/AAVMF/AAVMF_CODE.fd'
},
'Legacy': {
@ -111,6 +111,9 @@ class LibvirtDriver(AbstractSystemsDriver):
}
SECURE_BOOT_ENABLED_NVRAM = '/usr/share/OVMF/OVMF_VARS.secboot.fd'
SECURE_BOOT_DISABLED_NVRAM = '/usr/share/OVMF/OVMF_VARS.fd'
DEVICE_TYPE_MAP = {
constants.DEVICE_TYPE_CD: 'cdrom',
constants.DEVICE_TYPE_FLOPPY: 'floppy',
@ -161,6 +164,12 @@ class LibvirtDriver(AbstractSystemsDriver):
'SUSHY_EMULATOR_BOOT_LOADER_MAP', cls.BOOT_LOADER_MAP)
cls.KNOWN_BOOT_LOADERS = set(y for x in cls.BOOT_LOADER_MAP.values()
for y in x.values())
cls.SECURE_BOOT_ENABLED_NVRAM = cls._config.get(
'SUSHY_EMULATOR_SECURE_BOOT_ENABLED_NVRAM',
cls.SECURE_BOOT_ENABLED_NVRAM)
cls.SECURE_BOOT_DISABLED_NVRAM = cls._config.get(
'SUSHY_EMULATOR_SECURE_BOOT_DISABLED_NVRAM',
cls.SECURE_BOOT_DISABLED_NVRAM)
cls.SUSHY_EMULATOR_IGNORE_BOOT_DEVICE = \
cls._config.get('SUSHY_EMULATOR_IGNORE_BOOT_DEVICE', False)
return cls
@ -503,11 +512,34 @@ class LibvirtDriver(AbstractSystemsDriver):
:raises: `error.FishyError` if boot mode can't be set
"""
domain = self._get_domain(identity, readonly=True)
# XML schema: https://libvirt.org/formatdomain.html#elementsOSBIOS
# XML schema:
# https://libvirt.org/formatdomain.html#operating-system-booting
tree = ET.fromstring(self.get_xml_desc(domain))
self._build_os_element(identity, tree, boot_mode)
with libvirt_open(self._uri) as conn:
try:
conn.defineXML(ET.tostring(tree).decode('utf-8'))
except libvirt.libvirtError as e:
msg = ('Error changing boot mode at libvirt URI '
'"%(uri)s": %(error)s' % {'uri': self._uri,
'error': e})
raise error.FishyError(msg)
def _build_os_element(self, identity, tree, boot_mode, secure=None):
"""Set the boot mode and secure boot on the os element
This also converts from the previous manual layout to the automatic
approach.
:raises: `error.FishyError` if boot mode can't be set
"""
try:
loader_type = self.BOOT_MODE_MAP[boot_mode]
@ -529,7 +561,6 @@ class LibvirtDriver(AbstractSystemsDriver):
type_element = os_element.find('type')
if type_element is None:
os_arch = None
else:
os_arch = type_element.get('arch')
@ -544,41 +575,83 @@ class LibvirtDriver(AbstractSystemsDriver):
boot_mode, os_arch)
loader_path = None
loader_elements = os_element.findall('loader')
if len(loader_elements) > 1:
msg = ('Can\'t set boot mode because "loader" element must be '
'present exactly once in domain "%(identity)s" '
# delete loader and nvram elements to rebuild from stratch
for element in os_element.findall('loader'):
os_element.remove(element)
for element in os_element.findall('nvram'):
os_element.remove(element)
loader_element = ET.SubElement(os_element, 'loader')
loader_element.set('type', loader_type)
if loader_path:
loader_element.text = loader_path
loader_element.set('readonly', 'yes')
if boot_mode == 'UEFI':
nvram_element = ET.SubElement(os_element, 'nvram')
if secure:
nvram_suffix = '.secboot.fd'
loader_element.set('secure', 'yes')
nvram_element.set('template', self.SECURE_BOOT_ENABLED_NVRAM)
else:
nvram_suffix = '.fd'
loader_element.set('secure', 'no')
nvram_element.set('template', self.SECURE_BOOT_DISABLED_NVRAM)
# force a different nvram path for secure vs not. This will ensure
# it gets regenerated from the template when secure boot mode
# changes
nvram_path = "/var/lib/libvirt/nvram-%s%s" % (identity,
nvram_suffix)
nvram_element.text = nvram_path
def get_secure_boot(self, identity):
"""Get computer system secure boot state for UEFI boot mode.
:returns: boolean of the current secure boot state
:raises: `FishyError` if the state can't be fetched
"""
if self.get_boot_mode(identity) == 'Legacy':
msg = 'Legacy boot mode does not support secure boot'
raise error.NotSupportedError(msg)
domain = self._get_domain(identity, readonly=True)
# XML schema:
# https://libvirt.org/formatdomain.html#operating-system-booting
tree = ET.fromstring(domain.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE))
os_element = tree.find('os')
nvram = os_element.findall('nvram')
if len(nvram) > 1:
msg = ('Can\'t get secure boot state because "nvram" element '
'must be present exactly once in domain "%(identity)s" '
'configuration' % {'identity': identity})
raise error.FishyError(msg)
if loader_elements:
loader_element = loader_elements[0]
if not nvram:
return False
nvram_template = nvram[0].get('template')
return nvram_template == self.SECURE_BOOT_ENABLED_NVRAM
if loader_element.text not in self.KNOWN_BOOT_LOADERS:
msg = ('Unknown boot loader path "%(path)s" in domain '
'"%(identity)s" configuration encountered while '
'setting boot mode "%(mode)s", system architecture '
'"%(arch)s". Consider adding this loader path to '
'emulator config.' % {'identity': identity,
'mode': boot_mode,
'arch': os_arch,
'path': loader_element.text})
raise error.FishyError(msg)
def set_secure_boot(self, identity, secure):
"""Set computer system secure boot state for UEFI boot mode.
if loader_path:
loader_element.set('type', loader_type)
loader_element.set('readonly', 'yes')
loader_element.text = loader_path
:param secure: boolean requesting the secure boot state
else:
# NOTE(etingof): path must be present or element must be absent
os_element.remove(loader_element)
:raises: `FishyError` if the can't be set
"""
if self.get_boot_mode(identity) == 'Legacy':
msg = 'Legacy boot mode does not support secure boot'
raise error.NotSupportedError(msg)
elif loader_path:
loader_element = ET.SubElement(os_element, 'loader')
loader_element.set('type', loader_type)
loader_element.set('readonly', 'yes')
loader_element.text = loader_path
domain = self._get_domain(identity, readonly=True)
# XML schema: https://libvirt.org/formatdomain.html#elementsOSBIOS
tree = ET.fromstring(domain.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE))
self._build_os_element(identity, tree, 'UEFI', secure)
with libvirt_open(self._uri) as conn:
@ -586,7 +659,7 @@ class LibvirtDriver(AbstractSystemsDriver):
conn.defineXML(ET.tostring(tree).decode('utf-8'))
except libvirt.libvirtError as e:
msg = ('Error changing boot mode at libvirt URI '
msg = ('Error changing secure boot at libvirt URI '
'"%(uri)s": %(error)s' % {'uri': self._uri,
'error': e})

View File

@ -273,6 +273,34 @@ class OpenStackDriver(AbstractSystemsDriver):
raise error.NotSupportedError(msg)
def get_secure_boot(self, identity):
"""Get computer system secure boot state for UEFI boot mode.
:returns: boolean of the current secure boot state
:raises: `FishyError` if the state can't be fetched
"""
if self.get_boot_mode(identity) == 'Legacy':
msg = 'Legacy boot mode does not support secure boot'
raise error.NotSupportedError(msg)
instance = self._get_instance(identity)
image = self._get_image_info(instance.image['id'])
return getattr(image, 'os_secure_boot', None) == 'required'
def set_secure_boot(self, identity, secure):
"""Set computer system secure boot state for UEFI boot mode.
:param secure: boolean requesting the secure boot state
:raises: `FishyError` if the can't be set
"""
msg = ('The cloud driver %(driver)s does not support changing secure '
'boot mode through Redfish' % {'driver': self.driver})
raise error.NotSupportedError(msg)
def get_total_memory(self, identity):
"""Get computer system total memory

View File

@ -0,0 +1,12 @@
{
"@odata.type": "#SecureBoot.v1_1_0.SecureBoot",
"Id": "SecureBoot",
"Name": "UEFI Secure Boot",
"Actions": {},
"SecureBootEnable": {{ 'true' if secure_boot_enable else 'false' }},
"SecureBootCurrentBoot": {{ secure_boot_current_boot|string|tojson }},
"SecureBootMode": "DeployedMode",
"@odata.id": {{ "/redfish/v1/Systems/%s/SecureBoot"|format(identity)|tojson }},
"@odata.context": "/redfish/v1/$metadata#SecureBoot.SecureBoot",
"@Redfish.Copyright": "Copyright 2014-2017 Distributed Management Task Force, Inc. (DMTF). For the full DMTF copyright policy, see http://www.dmtf.org/about/policies/copyright."
}

View File

@ -67,6 +67,9 @@
"EthernetInterfaces": {
"@odata.id": {{ "/redfish/v1/Systems/%s/EthernetInterfaces"|format(identity)|tojson }}
},
"SecureBoot": {
"@odata.id": {{ "/redfish/v1/Systems/%s/SecureBoot"|format(identity)|tojson }}
},
"SimpleStorage": {
"@odata.id": {{ "/redfish/v1/Systems/%s/SimpleStorage"|format(identity)|tojson }}
},

View File

@ -29,6 +29,9 @@ class AliasAccessError(FishyError):
class NotSupportedError(FishyError):
"""Feature not supported by resource driver"""
def __init__(self, msg='Unsupported'):
super().__init__(msg)
class NotFound(FishyError):
"""Entity not found."""

View File

@ -0,0 +1,29 @@
<domain type='qemu'>
<name>QEmu-fedora-i686</name>
<uuid>c7a5fdbd-cdaf-9455-926a-d65c16db1809</uuid>
<memory>219200</memory>
<currentMemory>219200</currentMemory>
<vcpu>2</vcpu>
<os>
<type arch="x86_64" machine="q35">hvm</type>
<loader readonly="yes" type="pflash">/usr/share/OVMF/OVMF_CODE.fd</loader>
<nvram template="/usr/share/OVMF/OVMF_VARS.fd"/>
<boot dev="disk"/>
</os>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='cdrom'>
<source file='/home/user/boot.iso'/>
<target dev='hdc'/>
<readonly/>
</disk>
<disk type='file' device='disk'>
<source file='/home/user/fedora.img'/>
<target dev='hda'/>
</disk>
<interface type='network'>
<source network='default'/>
</interface>
<graphics type='vnc' port='-1'/>
</devices>
</domain>

View File

@ -0,0 +1,29 @@
<domain type='qemu'>
<name>QEmu-fedora-i686</name>
<uuid>c7a5fdbd-cdaf-9455-926a-d65c16db1809</uuid>
<memory>219200</memory>
<currentMemory>219200</currentMemory>
<vcpu>2</vcpu>
<os>
<type arch="x86_64" machine="q35">hvm</type>
<loader secure="yes" readonly="yes" type="pflash">/usr/share/OVMF/OVMF_CODE.secboot.fd</loader>
<nvram template="/usr/share/OVMF/OVMF_VARS.secboot.fd"/>
<boot dev="disk"/>
</os>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='cdrom'>
<source file='/home/user/boot.iso'/>
<target dev='hdc'/>
<readonly/>
</disk>
<disk type='file' device='disk'>
<source file='/home/user/fedora.img'/>
<target dev='hda'/>
</disk>
<interface type='network'>
<source network='default'/>
</interface>
<graphics type='vnc' port='-1'/>
</devices>
</domain>

View File

@ -92,3 +92,8 @@ class FakeDriverTestCase(base.BaseTestCase):
self.test_driver.get_boot_image(UUID, 'Cd'))
self.assertEqual((None, False, False),
self.test_driver.get_boot_image(UUID, 'Hdd'))
def test_secure_boot(self):
self.assertFalse(self.test_driver.get_secure_boot(UUID))
self.test_driver.set_secure_boot(UUID, True)
self.assertTrue(self.test_driver.get_secure_boot(UUID))

View File

@ -328,7 +328,7 @@ class LibvirtDriverTestCase(base.BaseTestCase):
self.assertIn(expected, conn_mock.defineXML.call_args[0][0])
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_boot_mode(self, libvirt_mock):
def test_get_boot_mode_legacy(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f:
data = f.read()
@ -340,6 +340,20 @@ class LibvirtDriverTestCase(base.BaseTestCase):
self.assertEqual('Legacy', boot_mode)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_boot_mode_uefi(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/domain-q35_uefi.xml',
'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
boot_mode = self.test_driver.get_boot_mode(self.uuid)
self.assertEqual('UEFI', boot_mode)
@mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_boot_mode(self, libvirt_mock, libvirt_rw_mock):
@ -359,8 +373,9 @@ class LibvirtDriverTestCase(base.BaseTestCase):
@mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_boot_mode_known_loader(self, libvirt_mock, libvirt_rw_mock):
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f:
def test_set_boot_mode_legacy(self, libvirt_mock, libvirt_rw_mock):
with open('sushy_tools/tests/unit/emulator/domain-q35_uefi.xml',
'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
@ -369,37 +384,15 @@ class LibvirtDriverTestCase(base.BaseTestCase):
with mock.patch.object(
self.test_driver, 'get_power_state', return_value='Off'):
self.test_driver.set_boot_mode(self.uuid, 'UEFI')
self.test_driver.set_boot_mode(self.uuid, 'Legacy')
conn_mock = libvirt_rw_mock.return_value
xml_document = conn_mock.defineXML.call_args[0][0]
tree = ET.fromstring(xml_document)
os_element = tree.find('os')
loader_element = os_element.find('loader')
self.assertEqual(
'pflash', loader_element.get('type'))
self.assertEqual(
'/usr/share/OVMF/OVMF_CODE.fd',
loader_element.text)
@mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_boot_mode_unknown_loader_path(
self, libvirt_mock, libvirt_rw_mock):
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
with mock.patch.dict(
self.test_driver.KNOWN_BOOT_LOADERS, {}, clear=True):
with mock.patch.object(
self.test_driver, 'get_power_state', return_value='Off'):
self.assertRaises(
error.FishyError, self.test_driver.set_boot_mode,
self.uuid, 'Uefi')
self.assertIsNone(loader_element.text)
self.assertNotIn('readonly', loader_element.attrib)
@mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True)
@ -467,34 +460,6 @@ class LibvirtDriverTestCase(base.BaseTestCase):
error.FishyError, self.test_driver.set_boot_mode,
self.uuid, 'Uefi')
@mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_boot_mode_no_type(self, libvirt_mock, libvirt_rw_mock):
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f:
data = f.read()
tree = ET.fromstring(data)
os_element = tree.find('os')
type_element = os_element.find('type')
os_element.remove(type_element)
data = ET.tostring(tree)
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
with mock.patch.object(
self.test_driver, 'get_power_state', return_value='Off'):
self.test_driver.set_boot_mode(self.uuid, 'UEFI')
conn_mock = libvirt_rw_mock.return_value
xml_document = conn_mock.defineXML.call_args[0][0]
tree = ET.fromstring(xml_document)
os_element = tree.find('os')
# NOTE(etingof): should enforce default loader
self.assertIsNone(os_element.find('loader'))
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_boot_image(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f:
@ -1117,3 +1082,106 @@ class LibvirtDriverTestCase(base.BaseTestCase):
self.test_driver.find_or_create_storage_volume(vol_data)
pool_mock.createXML.assert_called_once_with(mock.ANY)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_secure_boot_off(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/domain-q35_uefi.xml',
'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
self.assertFalse(self.test_driver.get_secure_boot(self.uuid))
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_secure_boot_on(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/domain-q35_uefi_secure.xml',
'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
self.assertTrue(self.test_driver.get_secure_boot(self.uuid))
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_secure_boot_not_uefi(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/domain-q35.xml', 'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
self.assertRaises(error.NotSupportedError,
self.test_driver.get_secure_boot, self.uuid)
@mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_secure_boot_on(self, libvirt_mock, libvirt_rw_mock):
with open('sushy_tools/tests/unit/emulator/domain-q35_uefi.xml',
'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
self.test_driver.set_secure_boot(self.uuid, True)
conn_mock = libvirt_rw_mock.return_value
xml_document = conn_mock.defineXML.call_args[0][0]
tree = ET.fromstring(xml_document)
os_element = tree.find('os')
loader_element = os_element.find('loader')
nvram_element = os_element.find('nvram')
self.assertEqual('yes', loader_element.get('secure'))
self.assertEqual('/usr/share/OVMF/OVMF_CODE.secboot.fd',
loader_element.text)
self.assertEqual('/usr/share/OVMF/OVMF_VARS.secboot.fd',
nvram_element.get('template'))
self.assertEqual('/var/lib/libvirt/nvram-%s.secboot.fd' %
self.uuid, nvram_element.text)
@mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_secure_boot_off(self, libvirt_mock, libvirt_rw_mock):
with open('sushy_tools/tests/unit/emulator/domain-q35_uefi_secure.xml',
'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
self.test_driver.set_secure_boot(self.uuid, False)
conn_mock = libvirt_rw_mock.return_value
xml_document = conn_mock.defineXML.call_args[0][0]
tree = ET.fromstring(xml_document)
os_element = tree.find('os')
loader_element = os_element.find('loader')
nvram_element = os_element.find('nvram')
self.assertEqual('no', loader_element.get('secure'))
self.assertEqual('/usr/share/OVMF/OVMF_CODE.secboot.fd',
loader_element.text)
self.assertEqual('/usr/share/OVMF/OVMF_VARS.fd',
nvram_element.get('template'))
self.assertEqual('/var/lib/libvirt/nvram-%s.fd' %
self.uuid, nvram_element.text)
@mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_secure_boot_not_uefi(self, libvirt_mock, libvirt_rw_mock):
with open('sushy_tools/tests/unit/emulator/domain-q35.xml', 'r') as f:
data = f.read()
conn_mock = libvirt_mock.return_value
domain_mock = conn_mock.lookupByUUID.return_value
domain_mock.XMLDesc.return_value = data
self.assertRaises(error.NotSupportedError,
self.test_driver.set_secure_boot, self.uuid, True)

View File

@ -262,3 +262,28 @@ class NovaDriverTestCase(base.BaseTestCase):
self.assertRaises(
error.FishyError,
self.test_driver.get_simple_storage_collection, self.uuid)
def test_get_secure_boot_off(self):
server = mock.Mock(id=self.uuid, image=dict(id=self.uuid))
self.nova_mock.return_value.get_server.return_value = server
image = mock.Mock()
self.nova_mock.return_value.image.find_image.return_value = image
self.assertFalse(self.test_driver.get_secure_boot(self.uuid))
def test_get_secure_boot_on(self):
server = mock.Mock(id=self.uuid, image=dict(id=self.uuid))
self.nova_mock.return_value.get_server.return_value = server
image = mock.Mock(os_secure_boot='required')
self.nova_mock.return_value.image.find_image.return_value = image
self.assertTrue(self.test_driver.get_secure_boot(self.uuid))
def test_set_secure_boot(self):
self.assertRaises(
error.NotSupportedError, self.test_driver.set_secure_boot,
self.uuid, True)

View File

@ -428,6 +428,40 @@ class EthernetInterfacesTestCase(EmulatorTestCase):
self.assertEqual(404, response.status_code)
@patch_resource('systems')
class SecureBootTestCase(EmulatorTestCase):
def test_secure_boot_get(self, systems_mock):
systems_mock = systems_mock.return_value
systems_mock.get_secure_boot.return_value = True
response = self.app.get('redfish/v1/Systems/%s/SecureBoot' % self.uuid)
self.assertEqual(200, response.status_code)
self.assertEqual('UEFI Secure Boot',
response.json['Name'])
self.assertTrue(response.json['SecureBootEnable'])
systems_mock.get_secure_boot.return_value = False
response = self.app.get('redfish/v1/Systems/%s/SecureBoot' % self.uuid)
self.assertFalse(response.json['SecureBootEnable'])
def test_secure_boot_patch_on(self, systems_mock):
systems_mock = systems_mock.return_value
data = {'SecureBootEnable': True}
response = self.app.patch('redfish/v1/Systems/%s/SecureBoot'
% self.uuid, json=data)
self.assertEqual(204, response.status_code)
systems_mock.set_secure_boot.assert_called_once_with(self.uuid, True)
def test_secure_boot_patch_off(self, systems_mock):
systems_mock = systems_mock.return_value
data = {'SecureBootEnable': False}
response = self.app.patch('redfish/v1/Systems/%s/SecureBoot'
% self.uuid, json=data)
self.assertEqual(204, response.status_code)
systems_mock.set_secure_boot.assert_called_once_with(self.uuid, False)
@patch_resource('systems')
class StorageTestCase(EmulatorTestCase):