Clean step to remove CA certificates from iLO

Implements clean step "clear_ca_certificates" to remove any 3rd party
expired/revoked CA certificates from iLO.

Change-Id: I0a3c1da9b94e4037a53ade100354ac51ca08db35
Story: #2008784
Task: #42175
This commit is contained in:
vinay50muddu 2021-07-05 10:09:35 +00:00 committed by Nisha Agarwal
parent 8434b56766
commit df4778605d
8 changed files with 316 additions and 12 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
---
features:
- |
Manual clean step ``clear_ca_certificates`` is added to remove the
CA certificates from iLO.