Secure boot support for irmc-pxe driver
This patch adds secure boot support for irmc-pxe boot interface as follows: - Implement secure boot support for irmc-pxe boot interface - Update version of python-scciclient supporting secure boot - Update irmc-pxe driver documentation Change-Id: Ie82ff07421d23b5c0d26e2d2fbde33fc9f8e3c42 Partial-Bug: #1694649
This commit is contained in:
parent
15c31b9023
commit
802c86ef04
@ -22,7 +22,7 @@ Prerequisites
|
|||||||
* Install `python-scciclient <https://pypi.python.org/pypi/python-scciclient>`_
|
* Install `python-scciclient <https://pypi.python.org/pypi/python-scciclient>`_
|
||||||
and `pysnmp <https://pypi.python.org/pypi/pysnmp>`_ packages::
|
and `pysnmp <https://pypi.python.org/pypi/pysnmp>`_ packages::
|
||||||
|
|
||||||
$ pip install "python-scciclient>=0.4.0" pysnmp
|
$ pip install "python-scciclient>=0.5.0" pysnmp
|
||||||
|
|
||||||
Drivers
|
Drivers
|
||||||
=======
|
=======
|
||||||
@ -55,6 +55,8 @@ Node configuration
|
|||||||
irmc_username.
|
irmc_username.
|
||||||
- ``properties/capabilities`` property to be ``boot_mode:uefi`` if
|
- ``properties/capabilities`` property to be ``boot_mode:uefi`` if
|
||||||
UEFI boot is required.
|
UEFI boot is required.
|
||||||
|
- ``properties/capabilities`` property to be ``boot_mode:uefi,secure_boot:true`` if
|
||||||
|
UEFI Secure Boot is required.
|
||||||
|
|
||||||
* All of nodes are configured by setting the following configuration
|
* All of nodes are configured by setting the following configuration
|
||||||
options in ``[irmc]`` section of ``/etc/ironic/ironic.conf``:
|
options in ``[irmc]`` section of ``/etc/ironic/ironic.conf``:
|
||||||
|
@ -8,7 +8,7 @@ proliantutils>=2.2.1
|
|||||||
pysnmp
|
pysnmp
|
||||||
python-ironic-inspector-client>=1.5.0
|
python-ironic-inspector-client>=1.5.0
|
||||||
python-oneviewclient<3.0.0,>=2.5.2
|
python-oneviewclient<3.0.0,>=2.5.2
|
||||||
python-scciclient>=0.4.0
|
python-scciclient>=0.5.0
|
||||||
UcsSdk==0.8.2.2
|
UcsSdk==0.8.2.2
|
||||||
python-dracclient>=1.3.0
|
python-dracclient>=1.3.0
|
||||||
|
|
||||||
|
@ -692,3 +692,39 @@ class IRMCPXEBoot(pxe.PXEBoot):
|
|||||||
irmc_management.backup_bios_config(task)
|
irmc_management.backup_bios_config(task)
|
||||||
|
|
||||||
super(IRMCPXEBoot, self).prepare_ramdisk(task, ramdisk_params)
|
super(IRMCPXEBoot, self).prepare_ramdisk(task, ramdisk_params)
|
||||||
|
|
||||||
|
@METRICS.timer('IRMCPXEBoot.prepare_instance')
|
||||||
|
def prepare_instance(self, task):
|
||||||
|
"""Prepares the boot of instance.
|
||||||
|
|
||||||
|
This method prepares the boot of the instance after reading
|
||||||
|
relevant information from the node's instance_info. In case of netboot,
|
||||||
|
it updates the dhcp entries and switches the PXE config. In case of
|
||||||
|
localboot, it cleans up the PXE config.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:returns: None
|
||||||
|
:raises: IRMCOperationError, if some operation on iRMC failed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
super(IRMCPXEBoot, self).prepare_instance(task)
|
||||||
|
node = task.node
|
||||||
|
if deploy_utils.is_secure_boot_requested(node):
|
||||||
|
irmc_common.set_secure_boot_mode(node, enable=True)
|
||||||
|
|
||||||
|
@METRICS.timer('IRMCPXEBoot.clean_up_instance')
|
||||||
|
def clean_up_instance(self, task):
|
||||||
|
"""Cleans up the boot of instance.
|
||||||
|
|
||||||
|
This method cleans up the environment that was setup for booting
|
||||||
|
the instance. It unlinks the instance kernel/ramdisk in node's
|
||||||
|
directory in tftproot and removes the PXE config.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:raises: IRMCOperationError, if some operation on iRMC failed.
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
if deploy_utils.is_secure_boot_requested(node):
|
||||||
|
irmc_common.set_secure_boot_mode(node, enable=False)
|
||||||
|
super(IRMCPXEBoot, self).clean_up_instance(task)
|
||||||
|
@ -17,6 +17,7 @@ Common functionalities shared between different iRMC modules.
|
|||||||
"""
|
"""
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
@ -24,7 +25,9 @@ from ironic.common.i18n import _
|
|||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
|
|
||||||
scci = importutils.try_import('scciclient.irmc.scci')
|
scci = importutils.try_import('scciclient.irmc.scci')
|
||||||
|
elcm = importutils.try_import('scciclient.irmc.elcm')
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
REQUIRED_PROPERTIES = {
|
REQUIRED_PROPERTIES = {
|
||||||
'irmc_address': _("IP address or hostname of the iRMC. Required."),
|
'irmc_address': _("IP address or hostname of the iRMC. Required."),
|
||||||
'irmc_username': _("Username for the iRMC with administrator privileges. "
|
'irmc_username': _("Username for the iRMC with administrator privileges. "
|
||||||
@ -195,3 +198,26 @@ def get_irmc_report(node):
|
|||||||
port=driver_info['irmc_port'],
|
port=driver_info['irmc_port'],
|
||||||
auth_method=driver_info['irmc_auth_method'],
|
auth_method=driver_info['irmc_auth_method'],
|
||||||
client_timeout=driver_info['irmc_client_timeout'])
|
client_timeout=driver_info['irmc_client_timeout'])
|
||||||
|
|
||||||
|
|
||||||
|
def set_secure_boot_mode(node, enable):
|
||||||
|
"""Enable or disable UEFI Secure Boot
|
||||||
|
|
||||||
|
Enable or disable UEFI Secure Boot
|
||||||
|
|
||||||
|
:param node: An ironic node object.
|
||||||
|
:param enable: Boolean value. True if the secure boot to be
|
||||||
|
enabled.
|
||||||
|
:raises: IRMCOperationError if the operation fails.
|
||||||
|
"""
|
||||||
|
driver_info = parse_driver_info(node)
|
||||||
|
|
||||||
|
try:
|
||||||
|
elcm.set_secure_boot_mode(driver_info, enable)
|
||||||
|
LOG.info("Set secure boot to %(flag)s for node %(node)s",
|
||||||
|
{'flag': enable, 'node': node.uuid})
|
||||||
|
except scci.SCCIError as irmc_exception:
|
||||||
|
LOG.error("Failed to set secure boot to %(flag)s for node %(node)s",
|
||||||
|
{'flag': enable, 'node': node.uuid})
|
||||||
|
raise exception.IRMCOperationError(operation=_("set_secure_boot_mode"),
|
||||||
|
error=irmc_exception)
|
||||||
|
@ -1097,3 +1097,121 @@ class IRMCPXEBootTestCase(db_base.DbTestCase):
|
|||||||
self.assertFalse(mock_backup_bios.called)
|
self.assertFalse(mock_backup_bios.called)
|
||||||
mock_parent_prepare.assert_called_once_with(
|
mock_parent_prepare.assert_called_once_with(
|
||||||
task.driver.boot, task, {})
|
task.driver.boot, task, {})
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
def test_prepare_instance_with_secure_boot(self, mock_prepare_instance,
|
||||||
|
mock_set_secure_boot_mode):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.target_provision_state = states.ACTIVE
|
||||||
|
self.node.instance_info = {
|
||||||
|
'capabilities': {
|
||||||
|
"secure_boot": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
mock_set_secure_boot_mode.assert_called_once_with(task.node,
|
||||||
|
enable=True)
|
||||||
|
mock_prepare_instance.assert_called_once_with(
|
||||||
|
task.driver.boot, task)
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
def test_prepare_instance_with_secure_boot_false(
|
||||||
|
self, mock_prepare_instance, mock_set_secure_boot_mode):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.target_provision_state = states.ACTIVE
|
||||||
|
self.node.instance_info = {
|
||||||
|
'capabilities': {
|
||||||
|
"secure_boot": "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
self.assertFalse(mock_set_secure_boot_mode.called)
|
||||||
|
mock_prepare_instance.assert_called_once_with(
|
||||||
|
task.driver.boot, task)
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(pxe.PXEBoot, 'prepare_instance', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
def test_prepare_instance_without_secure_boot(self, mock_prepare_instance,
|
||||||
|
mock_set_secure_boot_mode):
|
||||||
|
self.node.provision_state = states.DEPLOYING
|
||||||
|
self.node.target_provision_state = states.ACTIVE
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
self.assertFalse(mock_set_secure_boot_mode.called)
|
||||||
|
mock_prepare_instance.assert_called_once_with(
|
||||||
|
task.driver.boot, task)
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(pxe.PXEBoot, 'clean_up_instance', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
def test_clean_up_instance_with_secure_boot(self, mock_clean_up_instance,
|
||||||
|
mock_set_secure_boot_mode):
|
||||||
|
self.node.provision_state = states.CLEANING
|
||||||
|
self.node.target_provision_state = states.AVAILABLE
|
||||||
|
self.node.instance_info = {
|
||||||
|
'capabilities': {
|
||||||
|
"secure_boot": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.boot.clean_up_instance(task)
|
||||||
|
mock_set_secure_boot_mode.assert_called_once_with(task.node,
|
||||||
|
enable=False)
|
||||||
|
mock_clean_up_instance.assert_called_once_with(
|
||||||
|
task.driver.boot, task)
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(pxe.PXEBoot, 'clean_up_instance', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
def test_clean_up_instance_secure_boot_false(self, mock_clean_up_instance,
|
||||||
|
mock_set_secure_boot_mode):
|
||||||
|
self.node.provision_state = states.CLEANING
|
||||||
|
self.node.target_provision_state = states.AVAILABLE
|
||||||
|
self.node.instance_info = {
|
||||||
|
'capabilities': {
|
||||||
|
"secure_boot": "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.boot.clean_up_instance(task)
|
||||||
|
self.assertFalse(mock_set_secure_boot_mode.called)
|
||||||
|
mock_clean_up_instance.assert_called_once_with(
|
||||||
|
task.driver.boot, task)
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_common, 'set_secure_boot_mode', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(pxe.PXEBoot, 'clean_up_instance', spec_set=True,
|
||||||
|
autospec=True)
|
||||||
|
def test_clean_up_instance_without_secure_boot(
|
||||||
|
self, mock_clean_up_instance, mock_set_secure_boot_mode):
|
||||||
|
self.node.provision_state = states.CLEANING
|
||||||
|
self.node.target_provision_state = states.AVAILABLE
|
||||||
|
self.node.save()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.boot.clean_up_instance(task)
|
||||||
|
self.assertFalse(mock_set_secure_boot_mode.called)
|
||||||
|
mock_clean_up_instance.assert_called_once_with(
|
||||||
|
task.driver.boot, task)
|
||||||
|
@ -210,3 +210,37 @@ class IRMCCommonMethodsTestCase(db_base.DbTestCase):
|
|||||||
def test_out_range_sensor_method(self):
|
def test_out_range_sensor_method(self):
|
||||||
self.assertRaises(ValueError, cfg.CONF.set_override,
|
self.assertRaises(ValueError, cfg.CONF.set_override,
|
||||||
'sensor_method', 'fake', 'irmc')
|
'sensor_method', 'fake', 'irmc')
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_common, 'elcm',
|
||||||
|
spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC)
|
||||||
|
def test_set_secure_boot_mode_enable(self, mock_elcm):
|
||||||
|
mock_elcm.set_secure_boot_mode.return_value = 'set_secure_boot_mode'
|
||||||
|
info = irmc_common.parse_driver_info(self.node)
|
||||||
|
irmc_common.set_secure_boot_mode(self.node, True)
|
||||||
|
mock_elcm.set_secure_boot_mode.assert_called_once_with(
|
||||||
|
info, True)
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_common, 'elcm',
|
||||||
|
spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC)
|
||||||
|
def test_set_secure_boot_mode_disable(self, mock_elcm):
|
||||||
|
mock_elcm.set_secure_boot_mode.return_value = 'set_secure_boot_mode'
|
||||||
|
info = irmc_common.parse_driver_info(self.node)
|
||||||
|
irmc_common.set_secure_boot_mode(self.node, False)
|
||||||
|
mock_elcm.set_secure_boot_mode.assert_called_once_with(
|
||||||
|
info, False)
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_common, 'elcm',
|
||||||
|
spec_set=mock_specs.SCCICLIENT_IRMC_ELCM_SPEC)
|
||||||
|
@mock.patch.object(irmc_common, 'scci',
|
||||||
|
spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
|
||||||
|
def test_set_secure_boot_mode_fail(self, mock_scci, mock_elcm):
|
||||||
|
irmc_common.scci.SCCIError = Exception
|
||||||
|
mock_elcm.set_secure_boot_mode.side_effect = Exception
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
self.assertRaises(exception.IRMCOperationError,
|
||||||
|
irmc_common.set_secure_boot_mode,
|
||||||
|
task.node, True)
|
||||||
|
info = irmc_common.parse_driver_info(task.node)
|
||||||
|
mock_elcm.set_secure_boot_mode.assert_called_once_with(
|
||||||
|
info, True)
|
||||||
|
@ -105,6 +105,7 @@ SCCICLIENT_IRMC_SCCI_SPEC = (
|
|||||||
SCCICLIENT_IRMC_ELCM_SPEC = (
|
SCCICLIENT_IRMC_ELCM_SPEC = (
|
||||||
'backup_bios_config',
|
'backup_bios_config',
|
||||||
'restore_bios_config',
|
'restore_bios_config',
|
||||||
|
'set_secure_boot_mode',
|
||||||
)
|
)
|
||||||
|
|
||||||
ONEVIEWCLIENT_SPEC = (
|
ONEVIEWCLIENT_SPEC = (
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds support to provision an instance in UEFI secure boot for
|
||||||
|
``irmc-pxe`` boot interface.
|
Loading…
Reference in New Issue
Block a user