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 # 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 = { SUSHY_EMULATOR_BOOT_LOADER_MAP = {
u'UEFI': { 'UEFI': {
u'x86_64': u'/usr/share/OVMF/OVMF_CODE.fd', 'x86_64': u'/usr/share/OVMF/OVMF_CODE.secboot.fd',
u'aarch64': u'/usr/share/AAVMF/AAVMF_CODE.fd' 'aarch64': u'/usr/share/AAVMF/AAVMF_CODE.fd'
}, },
u'Legacy': { 'Legacy': {
u'x86_64': None, 'x86_64': None,
u'aarch64': 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 # This map contains statically configured Redfish Chassis linked
# up with the Systems and Managers enclosed into this Chassis. # 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 could pull them as `edk2-ovmf` RPM. On Ubuntu, `apt-get install ovmf` should
do the job. do the job.
Then you need to create a VM by running `virt-install` with the `--boot uefi` Then you need to create a VM by running `virt-install` with the UEFI specific
option: `--boot` options:
Example: Example:
@ -163,7 +163,11 @@ Example:
virt-install \ virt-install \
--name vbmc-node \ --name vbmc-node \
--ram 1024 \ --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 \ --disk size=1 \
--vcpus 2 \ --vcpus 2 \
--os-type linux \ --os-type linux \
@ -174,16 +178,27 @@ Example:
rm $tmpfile rm $tmpfile
This will create a new `libvirt` domain with path to OVMF images properly 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 .. code-block:: xml
<loader readonly='yes' type='pflash'>/usr/share/edk2/ovmf/OVMF_CODE.fd</loader>
Because now we need to add this path to emulator's configuration matching <domain type="kvm">
VM architecture we are running. Make a copy of stock configuration file ...
and edit it accordingly: <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 .. code-block:: bash
@ -191,9 +206,11 @@ and edit it accordingly:
... ...
SUSHY_EMULATOR_BOOT_LOADER_MAP = { SUSHY_EMULATOR_BOOT_LOADER_MAP = {
'Uefi': { '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: 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 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', @app.route('/redfish/v1/Systems/<identity>/SimpleStorage',
methods=['GET']) methods=['GET'])
@api_utils.ensure_instance_access @api_utils.ensure_instance_access

View File

@ -124,6 +124,24 @@ class AbstractSystemsDriver(metaclass=abc.ABCMeta):
""" """
raise error.NotSupportedError('Not implemented') 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): def get_total_memory(self, identity):
"""Get computer system total memory """Get computer system total memory

View File

@ -143,6 +143,12 @@ class FakeDriver(AbstractSystemsDriver):
def set_boot_mode(self, identity, boot_mode): def set_boot_mode(self, identity, boot_mode):
self._update(identity, boot_mode=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): def get_boot_image(self, identity, device):
devinfo = self._get(identity).get('boot_image') or {} devinfo = self._get(identity).get('boot_image') or {}
return devinfo.get(device) or (None, False, False) return devinfo.get(device) or (None, False, False)

View File

@ -101,7 +101,7 @@ class LibvirtDriver(AbstractSystemsDriver):
BOOT_LOADER_MAP = { BOOT_LOADER_MAP = {
'UEFI': { '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' 'aarch64': '/usr/share/AAVMF/AAVMF_CODE.fd'
}, },
'Legacy': { '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 = { DEVICE_TYPE_MAP = {
constants.DEVICE_TYPE_CD: 'cdrom', constants.DEVICE_TYPE_CD: 'cdrom',
constants.DEVICE_TYPE_FLOPPY: 'floppy', constants.DEVICE_TYPE_FLOPPY: 'floppy',
@ -161,6 +164,12 @@ class LibvirtDriver(AbstractSystemsDriver):
'SUSHY_EMULATOR_BOOT_LOADER_MAP', cls.BOOT_LOADER_MAP) 'SUSHY_EMULATOR_BOOT_LOADER_MAP', cls.BOOT_LOADER_MAP)
cls.KNOWN_BOOT_LOADERS = set(y for x in cls.BOOT_LOADER_MAP.values() cls.KNOWN_BOOT_LOADERS = set(y for x in cls.BOOT_LOADER_MAP.values()
for y in x.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.SUSHY_EMULATOR_IGNORE_BOOT_DEVICE = \
cls._config.get('SUSHY_EMULATOR_IGNORE_BOOT_DEVICE', False) cls._config.get('SUSHY_EMULATOR_IGNORE_BOOT_DEVICE', False)
return cls return cls
@ -503,11 +512,34 @@ class LibvirtDriver(AbstractSystemsDriver):
:raises: `error.FishyError` if boot mode can't be set :raises: `error.FishyError` if boot mode can't be set
""" """
domain = self._get_domain(identity, readonly=True) 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)) 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: try:
loader_type = self.BOOT_MODE_MAP[boot_mode] loader_type = self.BOOT_MODE_MAP[boot_mode]
@ -529,7 +561,6 @@ class LibvirtDriver(AbstractSystemsDriver):
type_element = os_element.find('type') type_element = os_element.find('type')
if type_element is None: if type_element is None:
os_arch = None os_arch = None
else: else:
os_arch = type_element.get('arch') os_arch = type_element.get('arch')
@ -544,41 +575,83 @@ class LibvirtDriver(AbstractSystemsDriver):
boot_mode, os_arch) boot_mode, os_arch)
loader_path = None loader_path = None
loader_elements = os_element.findall('loader') # delete loader and nvram elements to rebuild from stratch
if len(loader_elements) > 1: for element in os_element.findall('loader'):
msg = ('Can\'t set boot mode because "loader" element must be ' os_element.remove(element)
'present exactly once in domain "%(identity)s" ' 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}) 'configuration' % {'identity': identity})
raise error.FishyError(msg) raise error.FishyError(msg)
if loader_elements: if not nvram:
loader_element = loader_elements[0] 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: def set_secure_boot(self, identity, secure):
msg = ('Unknown boot loader path "%(path)s" in domain ' """Set computer system secure boot state for UEFI boot mode.
'"%(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)
if loader_path: :param secure: boolean requesting the secure boot state
loader_element.set('type', loader_type)
loader_element.set('readonly', 'yes')
loader_element.text = loader_path
else: :raises: `FishyError` if the can't be set
# NOTE(etingof): path must be present or element must be absent """
os_element.remove(loader_element) if self.get_boot_mode(identity) == 'Legacy':
msg = 'Legacy boot mode does not support secure boot'
raise error.NotSupportedError(msg)
elif loader_path: domain = self._get_domain(identity, readonly=True)
loader_element = ET.SubElement(os_element, 'loader')
loader_element.set('type', loader_type) # XML schema: https://libvirt.org/formatdomain.html#elementsOSBIOS
loader_element.set('readonly', 'yes') tree = ET.fromstring(domain.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE))
loader_element.text = loader_path self._build_os_element(identity, tree, 'UEFI', secure)
with libvirt_open(self._uri) as conn: with libvirt_open(self._uri) as conn:
@ -586,7 +659,7 @@ class LibvirtDriver(AbstractSystemsDriver):
conn.defineXML(ET.tostring(tree).decode('utf-8')) conn.defineXML(ET.tostring(tree).decode('utf-8'))
except libvirt.libvirtError as e: 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, '"%(uri)s": %(error)s' % {'uri': self._uri,
'error': e}) 'error': e})

View File

@ -273,6 +273,34 @@ class OpenStackDriver(AbstractSystemsDriver):
raise error.NotSupportedError(msg) 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): def get_total_memory(self, identity):
"""Get computer system total memory """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": { "EthernetInterfaces": {
"@odata.id": {{ "/redfish/v1/Systems/%s/EthernetInterfaces"|format(identity)|tojson }} "@odata.id": {{ "/redfish/v1/Systems/%s/EthernetInterfaces"|format(identity)|tojson }}
}, },
"SecureBoot": {
"@odata.id": {{ "/redfish/v1/Systems/%s/SecureBoot"|format(identity)|tojson }}
},
"SimpleStorage": { "SimpleStorage": {
"@odata.id": {{ "/redfish/v1/Systems/%s/SimpleStorage"|format(identity)|tojson }} "@odata.id": {{ "/redfish/v1/Systems/%s/SimpleStorage"|format(identity)|tojson }}
}, },

View File

@ -29,6 +29,9 @@ class AliasAccessError(FishyError):
class NotSupportedError(FishyError): class NotSupportedError(FishyError):
"""Feature not supported by resource driver""" """Feature not supported by resource driver"""
def __init__(self, msg='Unsupported'):
super().__init__(msg)
class NotFound(FishyError): class NotFound(FishyError):
"""Entity not found.""" """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.test_driver.get_boot_image(UUID, 'Cd'))
self.assertEqual((None, False, False), self.assertEqual((None, False, False),
self.test_driver.get_boot_image(UUID, 'Hdd')) 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]) self.assertIn(expected, conn_mock.defineXML.call_args[0][0])
@mock.patch('libvirt.openReadOnly', autospec=True) @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: with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f:
data = f.read() data = f.read()
@ -340,6 +340,20 @@ class LibvirtDriverTestCase(base.BaseTestCase):
self.assertEqual('Legacy', boot_mode) 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.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True) @mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_boot_mode(self, libvirt_mock, libvirt_rw_mock): 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.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True) @mock.patch('libvirt.openReadOnly', autospec=True)
def test_set_boot_mode_known_loader(self, libvirt_mock, libvirt_rw_mock): def test_set_boot_mode_legacy(self, libvirt_mock, libvirt_rw_mock):
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f: with open('sushy_tools/tests/unit/emulator/domain-q35_uefi.xml',
'r') as f:
data = f.read() data = f.read()
conn_mock = libvirt_mock.return_value conn_mock = libvirt_mock.return_value
@ -369,37 +384,15 @@ class LibvirtDriverTestCase(base.BaseTestCase):
with mock.patch.object( with mock.patch.object(
self.test_driver, 'get_power_state', return_value='Off'): 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 conn_mock = libvirt_rw_mock.return_value
xml_document = conn_mock.defineXML.call_args[0][0] xml_document = conn_mock.defineXML.call_args[0][0]
tree = ET.fromstring(xml_document) tree = ET.fromstring(xml_document)
os_element = tree.find('os') os_element = tree.find('os')
loader_element = os_element.find('loader') loader_element = os_element.find('loader')
self.assertEqual( self.assertIsNone(loader_element.text)
'pflash', loader_element.get('type')) self.assertNotIn('readonly', loader_element.attrib)
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')
@mock.patch('libvirt.open', autospec=True) @mock.patch('libvirt.open', autospec=True)
@mock.patch('libvirt.openReadOnly', autospec=True) @mock.patch('libvirt.openReadOnly', autospec=True)
@ -467,34 +460,6 @@ class LibvirtDriverTestCase(base.BaseTestCase):
error.FishyError, self.test_driver.set_boot_mode, error.FishyError, self.test_driver.set_boot_mode,
self.uuid, 'Uefi') 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) @mock.patch('libvirt.openReadOnly', autospec=True)
def test_get_boot_image(self, libvirt_mock): def test_get_boot_image(self, libvirt_mock):
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f: 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) self.test_driver.find_or_create_storage_volume(vol_data)
pool_mock.createXML.assert_called_once_with(mock.ANY) 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( self.assertRaises(
error.FishyError, error.FishyError,
self.test_driver.get_simple_storage_collection, self.uuid) 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) 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') @patch_resource('systems')
class StorageTestCase(EmulatorTestCase): class StorageTestCase(EmulatorTestCase):