diff --git a/doc/source/admin/drivers/ilo.rst b/doc/source/admin/drivers/ilo.rst index 74153cc9ce..40bb06735d 100644 --- a/doc/source/admin/drivers/ilo.rst +++ b/doc/source/admin/drivers/ilo.rst @@ -56,6 +56,7 @@ The hardware type ``ilo`` supports following HPE server features: * `Update Minimum Password Length security parameter as manual clean step`_ * `Update Authentication Failure Logging security parameter as manual clean step`_ * `Activating iLO Advanced license as manual clean step`_ +* `Removing CA certificates from iLO as manual clean step`_ * `Firmware based UEFI iSCSI boot from volume support`_ * `Certificate based validation in iLO`_ * `Rescue mode support`_ @@ -702,6 +703,10 @@ Supported **Manual** Cleaning Operations delivered with a flexible-quantity kit or after completing an Activation Key Agreement (AKA), then the driver can still be used for executing this cleaning step. + ``clear_ca_certificates``: + Removes the CA certificates from iLO. See + `Removing CA certificates from iLO as manual clean step`_ for user + guidance on usage. ``apply_configuration``: Applies given BIOS settings on the node. See `BIOS configuration support`_. This step is part of the ``bios`` interface. @@ -1477,6 +1482,37 @@ The different attributes of ``activate_license`` clean step are as follows: "``args``", "Keyword-argument entry (<name>: <value>) being passed to clean step" "``args.ilo_license_key``", "iLO Advanced license key to activate enterprise features. This is mandatory." +Removing CA certificates from iLO as manual clean step +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +iLO driver can remove the invalidated CA certificates as a manual step. +Any manual cleaning step can only be initiated when a node is in the +``manageable`` state. Once the manual cleaning is finished, the node will be +put in the ``manageable`` state again. User can follow steps from +:ref:`manual_cleaning` to initiate manual cleaning operation on a node. + +An example of a manual clean step with ``clear_ca_certificates`` as the only clean +step could be:: + + "clean_steps": [{ + "interface": "management", + "step": "clear_ca_certificates", + "args": { + "certificate_files" : ["/path/to/certsA", "/path/to/certsB"] + } + }] + +The different attributes of ``clear_ca_certificates`` clean step are as follows: + +.. csv-table:: + :header: "Attribute", "Description" + :widths: 30, 120 + + "``interface``", "Interface of clean step, here ``management``" + "``step``", "Name of clean step, here ``clear_ca_certificates``" + "``args``", "Keyword-argument entry (<name>: <value>) being passed to clean step" + "``args.certificate_files``", "List of CA certificates which are to be removed. " + "This is mandatory." + Initiating firmware update as manual clean step ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ iLO driver can invoke secure firmware update as a manual cleaning step. Any diff --git a/ironic/drivers/modules/ilo/boot.py b/ironic/drivers/modules/ilo/boot.py index bcfb7bb967..60e0c3a25e 100644 --- a/ironic/drivers/modules/ilo/boot.py +++ b/ironic/drivers/modules/ilo/boot.py @@ -1022,11 +1022,16 @@ class IloUefiHttpsBoot(base.BootInterface): iso_ref = image_utils.prepare_deploy_iso(task, ramdisk_params, mode, d_info) + # NOTE(vmud213): Do not call if it is in the middle of + # clear_ca_certificates clean step as the TLS pending settings will + # be overridden + if not node.driver_internal_info.get('clear_ca_certs_flag'): + ilo_common.add_certificates(task) + LOG.debug("Set 'UEFIHTTP' as one time boot option on the node " "%(node)s to boot from URL %(iso_ref)s.", {'node': node.uuid, 'iso_ref': iso_ref}) - ilo_common.add_certificates(task) ilo_common.setup_uefi_https(task, iso_ref) @METRICS.timer('IloUefiHttpsBoot.clean_up_ramdisk') diff --git a/ironic/drivers/modules/ilo/common.py b/ironic/drivers/modules/ilo/common.py index 69161499b6..a69a4e3ec0 100644 --- a/ironic/drivers/modules/ilo/common.py +++ b/ironic/drivers/modules/ilo/common.py @@ -1068,10 +1068,13 @@ def clear_certificates(task, cert_file_list=None): node = task.node operation = (_("Clearing certificates from node %(node)s.") % {'node': node.uuid}) + # NOTE(vmud213): Exclude the certificates used to boot deploy images + exclude_cfl = _get_certificate_file_list(None) try: ilo_object = get_ilo_object(node) - ilo_object.remove_tls_certificate(cert_file_list) + ilo_object.remove_tls_certificate( + cert_file_list=cert_file_list, excl_cert_file_list=exclude_cfl) except ilo_error.IloCommandNotSupportedInBiosError as ilo_exception: raise exception.IloOperationNotSupported(operation=operation, error=ilo_exception) diff --git a/ironic/drivers/modules/ilo/management.py b/ironic/drivers/modules/ilo/management.py index 17516b4b82..e78db25ef5 100644 --- a/ironic/drivers/modules/ilo/management.py +++ b/ironic/drivers/modules/ilo/management.py @@ -186,6 +186,20 @@ _FIRMWARE_UPDATE_SUM_ARGSINFO = { } } +_CLEAR_CA_CERTS_ARGSINFO = { + 'certificate_files': { + 'description': ( + "The list of files containing the certificates to be cleared. " + "If empty list is specified, all the certificates on the ilo " + "will be cleared, except the certificates in the file " + "configured with configuration parameter 'webserver_verify_ca' " + "are spared as they are required for booting the deploy image " + "for some boot interfaces." + ), + 'required': True + } +} + def _execute_ilo_step(node, step, *args, **kwargs): """Executes a particular deploy or clean step. @@ -1128,3 +1142,51 @@ class Ilo5Management(IloManagement): {'node': task.node.uuid, 'message': ilo_exception}) manager_utils.cleaning_error_handler(task, log_msg, errmsg=ilo_exception) + + @base.clean_step(priority=0, argsinfo=_CLEAR_CA_CERTS_ARGSINFO) + def clear_ca_certificates(self, task, certificate_files): + """Clears the certificates provided in the list of files to iLO. + + :param task: a task from TaskManager. + :param certificate_files: a list of cerificate files. + :raises: NodeCleaningFailure, on failure to execute of clean step. + :raises: InstanceDeployFailure, on failure to execute of deploy step. + """ + node = task.node + driver_internal_info = node.driver_internal_info + + if driver_internal_info.get('clear_ca_certs_flag'): + # NOTE(vmud213): Clear the flag and do nothing as this flow + # is part of the reboot required by the clean step that is + # already executed. + driver_internal_info.pop('clear_ca_certs_flag', None) + node.driver_internal_info = driver_internal_info + node.save() + return + + try: + ilo_common.clear_certificates(task, certificate_files) + except (exception.IloOperationNotSupported, + exception.IloOperationError) as ir_exception: + msg = (_("Step 'clear_ca_certificates' failed on node %(node)s " + "with error: %(err)s") % + {'node': node.uuid, 'err': ir_exception}) + if node.clean_step: + raise exception.NodeCleaningFailure(msg) + raise exception.InstanceDeployFailure(msg) + + driver_internal_info['clear_ca_certs_flag'] = True + node.driver_internal_info = driver_internal_info + node.save() + + deploy_opts = deploy_utils.build_agent_options(task.node) + task.driver.boot.prepare_ramdisk(task, deploy_opts) + manager_utils.node_power_action(task, states.REBOOT) + + # set_async_step_flags calls node.save() + deploy_utils.set_async_step_flags( + node, + reboot=True, + skip_current_step=False) + + return deploy_utils.get_async_step_return_state(task.node) diff --git a/ironic/tests/unit/drivers/modules/ilo/test_boot.py b/ironic/tests/unit/drivers/modules/ilo/test_boot.py index 2fae7b9c34..128f603c56 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_boot.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_boot.py @@ -1812,7 +1812,11 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): task, ramdisk_params, mode, d_info) setup_uefi_https_mock.assert_called_once_with(task, 'recreated-iso') - add_mock.assert_called_once_with(task) + if task.node.driver_internal_info.get("clear_ca_certs_flag", + False): + add_mock.assert_not_called() + else: + add_mock.assert_called_once_with(task) def test_prepare_ramdisk_rescue_glance_image(self): self._test_prepare_ramdisk( @@ -1832,6 +1836,21 @@ class IloUefiHttpsBootTestCase(db_base.DbTestCase): self.node.instance_info['boot_iso']) def test_prepare_ramdisk_glance_image(self): + driver_internal_info = self.node.driver_internal_info + driver_internal_info['clear_ca_certs_flag'] = False + self.node.driver_internal_info = driver_internal_info + self.node.save() + self._test_prepare_ramdisk( + ilo_boot_iso='swift:abcdef', + image_source='6b2f0c0c-79e8-4db6-842e-43c9764204af') + self.node.refresh() + self.assertNotIn('boot_iso', self.node.instance_info) + + def test_prepare_ramdisk_middle_of_clean_step(self): + driver_internal_info = self.node.driver_internal_info + driver_internal_info['clear_ca_certs_flag'] = True + self.node.driver_internal_info = driver_internal_info + self.node.save() self._test_prepare_ramdisk( ilo_boot_iso='swift:abcdef', image_source='6b2f0c0c-79e8-4db6-842e-43c9764204af') diff --git a/ironic/tests/unit/drivers/modules/ilo/test_common.py b/ironic/tests/unit/drivers/modules/ilo/test_common.py index f1d9cd241e..1d4bfd8b29 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_common.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_common.py @@ -1370,34 +1370,50 @@ class IloCommonMethodsTestCase(BaseIloTest): get_cl_mock.assert_called_once_with(c_l) ilo_mock_object.add_tls_certificate.assert_called_once_with(c_l) + @mock.patch.object(ilo_common, '_get_certificate_file_list', + spec_set=True, autospec=True) @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, autospec=True) - def test_clear_certificates(self, get_ilo_object_mock): + def test_clear_certificates(self, get_ilo_object_mock, + get_certs_mock): ilo_mock_object = get_ilo_object_mock.return_value c_l = ['/file/path/a', '/file/path/b'] + get_certs_mock.return_value = ['/file/path/x'] with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: ilo_common.clear_certificates(task, c_l) - ilo_mock_object.remove_tls_certificate.assert_called_once_with(c_l) + ilo_mock_object.remove_tls_certificate.assert_called_once_with( + cert_file_list=c_l, excl_cert_file_list=['/file/path/x']) + get_certs_mock.assert_called_once_with(None) + @mock.patch.object(ilo_common, '_get_certificate_file_list', + spec_set=True, autospec=True) @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, autospec=True) - def test_clear_certificates_default(self, get_ilo_object_mock): + def test_clear_certificates_default(self, get_ilo_object_mock, + get_certs_mock): ilo_mock_object = get_ilo_object_mock.return_value + get_certs_mock.return_value = ['/file/path/x'] with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: ilo_common.clear_certificates(task) - ilo_mock_object.remove_tls_certificate.assert_called_once_with(None) + ilo_mock_object.remove_tls_certificate.assert_called_once_with( + cert_file_list=None, excl_cert_file_list=['/file/path/x']) + get_certs_mock.assert_called_once_with(None) + @mock.patch.object(ilo_common, '_get_certificate_file_list', + spec_set=True, autospec=True) @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, autospec=True) - def test_clear_certificates_raises_ilo_error(self, get_ilo_object_mock): + def test_clear_certificates_raises_ilo_error(self, get_ilo_object_mock, + get_certs_mock): ilo_mock_object = get_ilo_object_mock.return_value c_l = ['/file/path/a', '/file/path/b'] + get_certs_mock.return_value = ['/file/path/x'] exc = ilo_error.IloError('error') ilo_mock_object.remove_tls_certificate.side_effect = exc @@ -1407,7 +1423,9 @@ class IloCommonMethodsTestCase(BaseIloTest): ilo_common.clear_certificates, task, c_l) - ilo_mock_object.remove_tls_certificate.assert_called_once_with(c_l) + ilo_mock_object.remove_tls_certificate.assert_called_once_with( + cert_file_list=c_l, excl_cert_file_list=['/file/path/x']) + get_certs_mock.assert_called_once_with(None) @mock.patch.object(ilo_common, 'get_ilo_object', spec_set=True, autospec=True) diff --git a/ironic/tests/unit/drivers/modules/ilo/test_management.py b/ironic/tests/unit/drivers/modules/ilo/test_management.py index 80cb07c9fa..e4d891c3d0 100644 --- a/ironic/tests/unit/drivers/modules/ilo/test_management.py +++ b/ironic/tests/unit/drivers/modules/ilo/test_management.py @@ -1525,12 +1525,10 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): def setUp(self): super(Ilo5ManagementTestCase, self).setUp() self.driver = mock.Mock(management=ilo_management.Ilo5Management()) - self.clean_step = {'step': 'erase_devices', - 'interface': 'management'} + n = { 'driver': 'ilo5', 'driver_info': INFO_DICT, - 'clean_step': self.clean_step, } self.config(enabled_hardware_types=['ilo5'], enabled_boot_interfaces=['ilo-virtual-media'], @@ -1547,6 +1545,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True) @mock.patch.object(manager_utils, 'node_power_action', autospec=True) def test_erase_devices_hdd(self, mock_power, ilo_mock, build_agent_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() ilo_mock_object = ilo_mock.return_value ilo_mock_object.get_available_disk_types.return_value = ['HDD'] build_agent_mock.return_value = [] @@ -1572,6 +1573,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True) @mock.patch.object(manager_utils, 'node_power_action', autospec=True) def test_erase_devices_ssd(self, mock_power, ilo_mock, build_agent_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() ilo_mock_object = ilo_mock.return_value ilo_mock_object.get_available_disk_types.return_value = ['SSD'] build_agent_mock.return_value = [] @@ -1601,6 +1605,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): @mock.patch.object(manager_utils, 'node_power_action', autospec=True) def test_erase_devices_ssd_when_hdd_done(self, mock_power, ilo_mock, build_agent_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() build_agent_mock.return_value = [] ilo_mock_object = ilo_mock.return_value ilo_mock_object.get_available_disk_types.return_value = ['HDD', 'SSD'] @@ -1632,6 +1639,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True) def test_erase_devices_completed(self, ilo_mock, disk_status_mock, log_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() ilo_mock_object = ilo_mock.return_value ilo_mock_object.get_available_disk_types.return_value = ['HDD', 'SSD'] disk_status_mock.return_value = True @@ -1655,6 +1665,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): @mock.patch.object(manager_utils, 'node_power_action', autospec=True) def test_erase_devices_hdd_with_erase_pattern_zero( self, mock_power, ilo_mock, build_agent_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() ilo_mock_object = ilo_mock.return_value ilo_mock_object.get_available_disk_types.return_value = ['HDD'] build_agent_mock.return_value = [] @@ -1680,6 +1693,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): @mock.patch.object(ilo_common, 'get_ilo_object', autospec=True) def test_erase_devices_when_no_drive_available( self, ilo_mock, log_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() ilo_mock_object = ilo_mock.return_value ilo_mock_object.get_available_disk_types.return_value = [] with task_manager.acquire(self.context, self.node.uuid, @@ -1689,6 +1705,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): def test_erase_devices_hdd_with_invalid_format_erase_pattern( self): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: self.assertRaises(exception.InvalidParameterValue, @@ -1697,6 +1716,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): def test_erase_devices_hdd_with_invalid_device_type_erase_pattern( self): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: self.assertRaises(exception.InvalidParameterValue, @@ -1705,6 +1727,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): def test_erase_devices_hdd_with_invalid_erase_pattern( self): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: self.assertRaises(exception.InvalidParameterValue, @@ -1716,6 +1741,9 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): autospec=True) def test_erase_devices_hdd_ilo_error(self, clean_err_handler_mock, ilo_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'erase_devices'} + self.node.save() ilo_mock_object = ilo_mock.return_value ilo_mock_object.get_available_disk_types.return_value = ['HDD'] exc = ilo_error.IloError('error') @@ -1776,3 +1804,131 @@ class Ilo5ManagementTestCase(db_base.DbTestCase): errmsg=exc) self.assertTrue( ilo_mock_object.do_one_button_secure_erase.called) + + @mock.patch.object(deploy_utils, 'get_async_step_return_state', + spec_set=True, autospec=True) + @mock.patch.object(deploy_utils, 'set_async_step_flags', + spec_set=True, autospec=True) + @mock.patch.object(deploy_utils, 'build_agent_options', + spec_set=True, autospec=True) + @mock.patch.object(manager_utils, 'node_power_action', autospec=True) + @mock.patch.object(ilo_boot.IloVirtualMediaBoot, 'prepare_ramdisk', + spec_set=True, autospec=True) + @mock.patch.object(ilo_common, 'clear_certificates', spec_set=True, + autospec=True) + def test_clear_ca_certificates(self, clear_certs_mock, + prepare_ramdisk_mock, node_power_mock, + build_agent_mock, set_async_mock, + get_async_ret_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'clear_ca_certificates'} + self.node.save() + build_agent_mock.return_value = {'a': 'x', 'b': 'y'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + + certificate_files = ["/path/to/certsA", "/path/to/certsB"] + + task.driver.management.clear_ca_certificates( + task, certificate_files) + clear_certs_mock.assert_called_once_with( + task, certificate_files) + self.assertTrue(task.node.driver_internal_info.get( + 'clear_ca_certs_flag')) + build_agent_mock.assert_called_once_with(task.node) + prepare_ramdisk_mock.assert_called_once_with( + mock.ANY, task, {'a': 'x', 'b': 'y'}) + node_power_mock.assert_called_once_with(task, states.REBOOT) + set_async_mock.assert_called_once_with( + task.node, reboot=True, skip_current_step=False) + get_async_ret_mock.assert_called_once_with(task.node) + + @mock.patch.object(deploy_utils, 'get_async_step_return_state', + spec_set=True, autospec=True) + @mock.patch.object(deploy_utils, 'set_async_step_flags', + spec_set=True, autospec=True) + @mock.patch.object(deploy_utils, 'build_agent_options', + spec_set=True, autospec=True) + @mock.patch.object(manager_utils, 'node_power_action', autospec=True) + @mock.patch.object(ilo_boot.IloVirtualMediaBoot, 'prepare_ramdisk', + spec_set=True, autospec=True) + @mock.patch.object(ilo_common, 'clear_certificates', spec_set=True, + autospec=True) + def test_clear_ca_certificates_clear_flag( + self, clear_certs_mock, prepare_ramdisk_mock, node_power_mock, + build_agent_mock, set_async_mock, get_async_ret_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'clear_ca_certificates'} + + self.node.save() + build_agent_mock.return_value = {'a': 'x', 'b': 'y'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + + certificate_files = ["/path/to/certsA", "/path/to/certsB"] + driver_internal_info = task.node.driver_internal_info + driver_internal_info['clear_ca_certs_flag'] = True + task.node.driver_internal_info = driver_internal_info + + task.driver.management.clear_ca_certificates( + task, certificate_files) + clear_certs_mock.assert_not_called() + + build_agent_mock.assert_not_called() + prepare_ramdisk_mock.assert_not_called() + node_power_mock.assert_not_called() + set_async_mock.assert_not_called() + get_async_ret_mock.assert_not_called() + + @mock.patch.object(deploy_utils, 'get_async_step_return_state', + spec_set=True, autospec=True) + @mock.patch.object(deploy_utils, 'set_async_step_flags', + spec_set=True, autospec=True) + @mock.patch.object(deploy_utils, 'build_agent_options', + spec_set=True, autospec=True) + @mock.patch.object(manager_utils, 'node_power_action', autospec=True) + @mock.patch.object(ilo_boot.IloVirtualMediaBoot, 'prepare_ramdisk', + spec_set=True, autospec=True) + @mock.patch.object(ilo_common, 'clear_certificates', spec_set=True, + autospec=True) + def test_clear_ca_certificates_ilo_operation_not_supported( + self, clear_certs_mock, prepare_ramdisk_mock, node_power_mock, + build_agent_mock, set_async_mock, get_async_ret_mock): + self.node.clean_step = {'interface': 'management', + 'step': 'clear_ca_certificates'} + self.node.save() + exc = exception.IloOperationNotSupported('error') + clear_certs_mock.side_effect = exc + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + + certificate_files = ["/path/to/certsA", "/path/to/certsB"] + + self.assertRaises(exception.NodeCleaningFailure, + task.driver.management.clear_ca_certificates, + task, certificate_files) + build_agent_mock.assert_not_called() + prepare_ramdisk_mock.assert_not_called() + node_power_mock.assert_not_called() + set_async_mock.assert_not_called() + get_async_ret_mock.assert_not_called() + + @mock.patch.object(ilo_common, 'clear_certificates', spec_set=True, + autospec=True) + def test_clear_ca_certificates_ilo_operation_error(self, clear_certs_mock): + self.node.deploy_step = {'interface': 'management', + 'step': 'clear_ca_certificates'} + self.node.save() + + exc = exception.IloOperationError('error') + clear_certs_mock.side_effect = exc + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + + certificate_files = ["/path/to/certsA", "/path/to/certsB"] + + self.assertRaises(exception.InstanceDeployFailure, + task.driver.management.clear_ca_certificates, + task, certificate_files) diff --git a/releasenotes/notes/clear_ca_cert-db41e7be9723c0fb.yaml b/releasenotes/notes/clear_ca_cert-db41e7be9723c0fb.yaml new file mode 100644 index 0000000000..4df3c382da --- /dev/null +++ b/releasenotes/notes/clear_ca_cert-db41e7be9723c0fb.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Manual clean step ``clear_ca_certificates`` is added to remove the + CA certificates from iLO.