summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/deploy/install-guide.rst15
-rw-r--r--ironic/drivers/modules/deploy_utils.py18
-rw-r--r--ironic/drivers/modules/ilo/deploy.py90
-rw-r--r--ironic/tests/drivers/ilo/test_deploy.py137
-rw-r--r--ironic/tests/drivers/test_deploy_utils.py12
5 files changed, 272 insertions, 0 deletions
diff --git a/doc/source/deploy/install-guide.rst b/doc/source/deploy/install-guide.rst
index 44f8ba28f..ebfa63670 100644
--- a/doc/source/deploy/install-guide.rst
+++ b/doc/source/deploy/install-guide.rst
@@ -675,6 +675,21 @@ steps on the Ironic conductor node to configure PXE UEFI environment.
ironic node-update <node-uuid> add properties/capabilities='boot_mode:uefi'
+#. For deploying signed images, update the Ironic node with ``secure_boot``
+ capability in node's properties.
+ field::
+
+ ironic node-update <node-uuid> add properties/capabilities='secure_boot:true'
+
+#. Ensure the public key of the signed image is loaded into baremetal to deploy
+ signed images.
+ For HP Proliant Gen9 servers, one can enroll public key using iLO System
+ Utilities UI. Please refer to section ``Accessing Secure Boot options`` in
+ HP UEFI System Utilities User Guide http://www.hp.com/ctg/Manual/c04398276.pdf.
+ Also, one can refer to white paper on Secure Boot on Linux for HP Proliant
+ Servers at http://h20195.www2.hp.com/V2/getpdf.aspx/4AA5-4496ENW.pdf for
+ more details.
+
#. Make sure that bare metal node is configured to boot in UEFI boot mode and
boot device is set to network/pxe.
diff --git a/ironic/drivers/modules/deploy_utils.py b/ironic/drivers/modules/deploy_utils.py
index f9f2361d8..66c5db454 100644
--- a/ironic/drivers/modules/deploy_utils.py
+++ b/ironic/drivers/modules/deploy_utils.py
@@ -1013,3 +1013,21 @@ def parse_root_device_hints(node):
hints.append("%s=%s" % (key, value))
return ','.join(hints)
+
+
+def is_secure_boot_requested(node):
+ """Returns True if secure_boot is requested for deploy.
+
+ This method checks node property for secure_boot and returns True
+ if it is requested.
+
+ :param node: a single Node.
+ :raises: InvalidParameterValue if the capabilities string is not a
+ dictionary or is malformed.
+ :returns: True if secure_boot is requested.
+ """
+
+ capabilities = parse_instance_info_capabilities(node)
+ sec_boot = capabilities.get('secure_boot', 'false').lower()
+
+ return sec_boot == 'true'
diff --git a/ironic/drivers/modules/ilo/deploy.py b/ironic/drivers/modules/ilo/deploy.py
index 843f15b33..09a99d19b 100644
--- a/ironic/drivers/modules/ilo/deploy.py
+++ b/ironic/drivers/modules/ilo/deploy.py
@@ -274,6 +274,96 @@ def _prepare_agent_vmedia_boot(task):
_reboot_into(task, deploy_iso, deploy_ramdisk_opts)
+def _disable_secure_boot(task):
+ """Disables secure boot on node, if secure boot is enabled on node.
+
+ This method checks if secure boot is enabled on node. If enabled, it
+ disables same and returns True.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :returns: It returns True, if secure boot was successfully disabled on
+ the node.
+ It returns False, if secure boot on node is in disabled state
+ or if secure boot feature is not supported by the node.
+ :raises: IloOperationError, if some operation on iLO failed.
+ """
+ cur_sec_state = False
+ try:
+ cur_sec_state = ilo_common.get_secure_boot_mode(task)
+ except exception.IloOperationNotSupported:
+ LOG.debug('Secure boot mode is not supported for node %s',
+ task.node.uuid)
+ return False
+
+ if cur_sec_state:
+ LOG.debug('Disabling secure boot for node %s', task.node.uuid)
+ ilo_common.set_secure_boot_mode(task, False)
+ return True
+ return False
+
+
+def _prepare_node_for_deploy(task):
+ """Common preparatory steps for all iLO drivers.
+
+ This method performs common preparatory steps required for all drivers.
+ 1. Power off node
+ 2. Disables secure boot, if it is in enabled state.
+ 3. Updates boot_mode capability to 'uefi' if secure boot is requested.
+ 4. Changes boot mode of the node if secure boot is disabled currently.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :raises: IloOperationError, if some operation on iLO failed.
+ """
+ manager_utils.node_power_action(task, states.POWER_OFF)
+
+ # Boot mode can be changed only if secure boot is in disabled state.
+ # secure boot and boot mode cannot be changed together.
+ change_boot_mode = True
+
+ # Disable secure boot on the node if it is in enabled state.
+ if _disable_secure_boot(task):
+ change_boot_mode = False
+
+ # Set boot_mode capability to uefi for secure boot
+ if deploy_utils.is_secure_boot_requested(task.node):
+ LOG.debug('Secure boot deploy requested for node %s', task.node.uuid)
+ _enable_uefi_capability(task)
+
+ if change_boot_mode:
+ ilo_common.update_boot_mode(task)
+
+
+def _update_secure_boot_mode(task, mode):
+ """Changes secure boot mode for next boot on the node.
+
+ This method changes secure boot mode on the node for next boot. It changes
+ the secure boot mode setting on node only if the deploy has requested for
+ the secure boot.
+ During deploy, this method is used to enable secure boot on the node by
+ passing 'mode' as 'True'.
+ During teardown, this method is used to disable secure boot on the node by
+ passing 'mode' as 'False'.
+
+ :param task: a TaskManager instance containing the node to act on.
+ :param mode: Boolean value requesting the next state for secure boot
+ :raises: IloOperationNotSupported, if operation is not supported on iLO
+ :raises: IloOperationError, if some operation on iLO failed.
+ """
+ if deploy_utils.is_secure_boot_requested(task.node):
+ ilo_common.set_secure_boot_mode(task, mode)
+ LOG.info(_LI('Changed secure boot to %(mode)s for node %(node)s'),
+ {'mode': mode, 'node': task.node.uuid})
+
+
+def _enable_uefi_capability(task):
+ """Adds capability boot_mode='uefi' into Node property.
+
+ :param task: a TaskManager instance containing the node to act on.
+ """
+ driver_utils.rm_node_capability(task, 'boot_mode')
+ driver_utils.add_node_capability(task, 'boot_mode', 'uefi')
+
+
class IloVirtualMediaIscsiDeploy(base.DeployInterface):
def get_properties(self):
diff --git a/ironic/tests/drivers/ilo/test_deploy.py b/ironic/tests/drivers/ilo/test_deploy.py
index 5916e84a3..0f9f8ae58 100644
--- a/ironic/tests/drivers/ilo/test_deploy.py
+++ b/ironic/tests/drivers/ilo/test_deploy.py
@@ -268,6 +268,143 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase):
'deploy-iso-uuid',
deploy_opts)
+ @mock.patch.object(deploy_utils, 'is_secure_boot_requested')
+ @mock.patch.object(ilo_common, 'set_secure_boot_mode')
+ def test__update_secure_boot_passed_true(self,
+ func_set_secure_boot_mode,
+ func_is_secure_boot_requested):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_is_secure_boot_requested.return_value = True
+ ilo_deploy._update_secure_boot_mode(task, True)
+ func_set_secure_boot_mode.assert_called_once_with(task, True)
+
+ @mock.patch.object(deploy_utils, 'is_secure_boot_requested')
+ @mock.patch.object(ilo_common, 'set_secure_boot_mode')
+ def test__update_secure_boot_passed_False(self,
+ func_set_secure_boot_mode,
+ func_is_secure_boot_requested):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_is_secure_boot_requested.return_value = False
+ ilo_deploy._update_secure_boot_mode(task, False)
+ self.assertFalse(func_set_secure_boot_mode.called)
+
+ @mock.patch.object(driver_utils, 'add_node_capability')
+ @mock.patch.object(driver_utils, 'rm_node_capability')
+ def test__enable_uefi_capability(self, func_rm_node_capability,
+ func_add_node_capability):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ ilo_deploy._enable_uefi_capability(task)
+ func_rm_node_capability.assert_called_once_with(task,
+ 'boot_mode')
+ func_add_node_capability.assert_called_once_with(task,
+ 'boot_mode',
+ 'uefi')
+
+ @mock.patch.object(ilo_common, 'set_secure_boot_mode')
+ @mock.patch.object(ilo_common, 'get_secure_boot_mode')
+ def test__disable_secure_boot_false(self,
+ func_get_secure_boot_mode,
+ func_set_secure_boot_mode):
+ func_get_secure_boot_mode.return_value = False
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = ilo_deploy._disable_secure_boot(task)
+ func_get_secure_boot_mode.assert_called_once_with(task)
+ self.assertFalse(func_set_secure_boot_mode.called)
+ self.assertFalse(returned_state)
+
+ @mock.patch.object(ilo_common, 'set_secure_boot_mode')
+ @mock.patch.object(ilo_common, 'get_secure_boot_mode')
+ def test__disable_secure_boot_true(self,
+ func_get_secure_boot_mode,
+ func_set_secure_boot_mode):
+ func_get_secure_boot_mode.return_value = True
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ returned_state = ilo_deploy._disable_secure_boot(task)
+ func_get_secure_boot_mode.assert_called_once_with(task)
+ func_set_secure_boot_mode.assert_called_once_with(task, False)
+ self.assertTrue(returned_state)
+
+ @mock.patch.object(ilo_deploy, 'exception')
+ @mock.patch.object(ilo_common, 'get_secure_boot_mode')
+ def test__disable_secure_boot_exception(self,
+ func_get_secure_boot_mode,
+ exception_mock):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ exception_mock.IloOperationNotSupported = Exception
+ func_get_secure_boot_mode.side_effect = Exception
+ returned_state = ilo_deploy._disable_secure_boot(task)
+ func_get_secure_boot_mode.assert_called_once_with(task)
+ self.assertFalse(returned_state)
+
+ @mock.patch.object(ilo_common, 'update_boot_mode')
+ @mock.patch.object(deploy_utils, 'is_secure_boot_requested')
+ @mock.patch.object(ilo_deploy, '_disable_secure_boot')
+ @mock.patch.object(manager_utils, 'node_power_action')
+ def test__prepare_node_for_deploy(self,
+ func_node_power_action,
+ func_disable_secure_boot,
+ func_is_secure_boot_requested,
+ func_update_boot_mode):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_disable_secure_boot.return_value = False
+ func_is_secure_boot_requested.return_value = False
+ ilo_deploy._prepare_node_for_deploy(task)
+ func_node_power_action.assert_called_once_with(task,
+ states.POWER_OFF)
+ func_disable_secure_boot.assert_called_once_with(task)
+ func_is_secure_boot_requested.assert_called_once_with(task.node)
+ func_update_boot_mode.assert_called_once_with(task)
+
+ @mock.patch.object(ilo_common, 'update_boot_mode')
+ @mock.patch.object(deploy_utils, 'is_secure_boot_requested')
+ @mock.patch.object(ilo_deploy, '_disable_secure_boot')
+ @mock.patch.object(manager_utils, 'node_power_action')
+ def test__prepare_node_for_deploy_sec_boot_on(self,
+ func_node_power_action,
+ func_disable_secure_boot,
+ func_is_secure_boot_req,
+ func_update_boot_mode):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_disable_secure_boot.return_value = True
+ func_is_secure_boot_req.return_value = False
+ ilo_deploy._prepare_node_for_deploy(task)
+ func_node_power_action.assert_called_once_with(task,
+ states.POWER_OFF)
+ func_disable_secure_boot.assert_called_once_with(task)
+ func_is_secure_boot_req.assert_called_once_with(task.node)
+ self.assertFalse(func_update_boot_mode.called)
+
+ @mock.patch.object(ilo_common, 'update_boot_mode')
+ @mock.patch.object(ilo_deploy, '_enable_uefi_capability')
+ @mock.patch.object(deploy_utils, 'is_secure_boot_requested')
+ @mock.patch.object(ilo_deploy, '_disable_secure_boot')
+ @mock.patch.object(manager_utils, 'node_power_action')
+ def test__prepare_node_for_deploy_sec_boot_req(self,
+ func_node_power_action,
+ func_disable_secure_boot,
+ func_is_secure_boot_req,
+ func_enable_uefi_cap,
+ func_update_boot_mode):
+ with task_manager.acquire(self.context, self.node.uuid,
+ shared=False) as task:
+ func_disable_secure_boot.return_value = True
+ func_is_secure_boot_req.return_value = True
+ ilo_deploy._prepare_node_for_deploy(task)
+ func_node_power_action.assert_called_once_with(task,
+ states.POWER_OFF)
+ func_disable_secure_boot.assert_called_once_with(task)
+ func_is_secure_boot_req.assert_called_once_with(task.node)
+ func_enable_uefi_cap.assert_called_once_with(task)
+ self.assertFalse(func_update_boot_mode.called)
+
class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase):
diff --git a/ironic/tests/drivers/test_deploy_utils.py b/ironic/tests/drivers/test_deploy_utils.py
index bdbec9319..dfbcc5532 100644
--- a/ironic/tests/drivers/test_deploy_utils.py
+++ b/ironic/tests/drivers/test_deploy_utils.py
@@ -1452,6 +1452,18 @@ class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase):
self.assertRaises(exception.InvalidParameterValue,
utils.parse_instance_info_capabilities, self.node)
+ def test_is_secure_boot_requested_true(self):
+ self.node.instance_info = {'capabilities': {"secure_boot": "true"}}
+ self.assertTrue(utils.is_secure_boot_requested(self.node))
+
+ def test_is_secure_boot_requested_false(self):
+ self.node.instance_info = {'capabilities': {"secure_boot": "false"}}
+ self.assertFalse(utils.is_secure_boot_requested(self.node))
+
+ def test_is_secure_boot_requested_invalid(self):
+ self.node.instance_info = {'capabilities': {"secure_boot": "invalid"}}
+ self.assertFalse(utils.is_secure_boot_requested(self.node))
+
class TrySetBootDeviceTestCase(db_base.DbTestCase):