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:
Luong Anh Tuan 2017-07-05 15:21:09 +07:00
parent 15c31b9023
commit 802c86ef04
8 changed files with 224 additions and 2 deletions

View File

@ -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``:

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 = (

View File

@ -0,0 +1,5 @@
---
features:
- |
Adds support to provision an instance in UEFI secure boot for
``irmc-pxe`` boot interface.