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:
parent
8427349985
commit
41628d3546
@ -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.
|
||||
#
|
||||
|
@ -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:
|
||||
|
12
releasenotes/notes/secure-boot-de663109ced9b266.yaml
Normal file
12
releasenotes/notes/secure-boot-de663109ced9b266.yaml
Normal 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.
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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})
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
12
sushy_tools/emulator/templates/secure_boot.json
Normal file
12
sushy_tools/emulator/templates/secure_boot.json
Normal 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."
|
||||
}
|
@ -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 }}
|
||||
},
|
||||
|
@ -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."""
|
||||
|
29
sushy_tools/tests/unit/emulator/domain-q35_uefi.xml
Normal file
29
sushy_tools/tests/unit/emulator/domain-q35_uefi.xml
Normal 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>
|
29
sushy_tools/tests/unit/emulator/domain-q35_uefi_secure.xml
Normal file
29
sushy_tools/tests/unit/emulator/domain-q35_uefi_secure.xml
Normal 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>
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user