Add support to exclude finger prints of certs

Currently while removing the CA certificates from the iLO, when certificate
file list is empty, all the CA certificates from the iLO are removed. In some
cases certain certificates are required to be retained on the iLO. This
patch fixes the issue by accepting the list of certificates to be retained on
the iLO.

Change-Id: I4898361527e5785e181cffaf294557db0078c93d
This commit is contained in:
vmud213 2021-03-08 13:36:45 +00:00
parent 2be2d0e18d
commit 06b4eed744
4 changed files with 208 additions and 157 deletions

View File

@ -913,7 +913,8 @@ class IloClient(operations.IloOperations):
"""
return self._call_method('add_tls_certificate', cert_file_list)
def remove_tls_certificate(self, cert_file_list=[]):
def remove_tls_certificate(self, cert_file_list=[],
excl_cert_file_list=[]):
"""Removes the TLS certificate from the iLO
:param cert_file_list: List of TLS certificate files

View File

@ -558,7 +558,8 @@ class IloOperations(object):
"""
raise exception.IloCommandNotSupportedError(ERRMSG)
def remove_tls_certificate(self, cert_file_list=[]):
def remove_tls_certificate(self, cert_file_list=[],
excl_cert_file_list=[]):
"""Removes the TLS certificate from the iLO
:param cert_file_list: List of TLS certificate files

View File

@ -1692,10 +1692,55 @@ class RedfishOperations(operations.IloOperations):
msg = 'TLS certificate cannot be upload in BIOS boot mode'
raise exception.IloCommandNotSupportedInBiosError(msg)
def remove_tls_certificate(self, cert_file_list=[]):
"""Removes the TLS certificate from the iLO.
def _get_fps_from_file(self, cert_file):
"""Gets the finger prints from the certificate file.
:param cert_file_list: List of TLS certificate files
Parse the file passed in to get the certificates. For each certificate,
find the fingerprint by calculating the digest of base64 decoded
content. Finally return the list of the fingerprints.
:param cert_file: TLS certificate file containing one or more
certificates.
:return: Returns the list of FPs for the certificates in the file.
"""
fp_list = []
with open(cert_file, 'r') as f:
data = json.dumps(f.read())
p = re.sub(r"\"", "", data)
q = re.sub(r"\\n", "\r\n", p).rstrip()
c_list = re.findall(_CERTIFICATE_PATTERN, q, re.DOTALL)
if len(c_list) == 0:
LOG.warning("Could not find any valid certificate in "
"%(cert_file)s. Ignoring." %
{"cert_file": cert_file})
return fp_list
for content in c_list:
pem_lines = [line.strip() for line in (
content.strip().split('\n'))]
try:
der_data = b64decode(''.join(pem_lines[1:-1]))
except ValueError:
LOG.warning("Illegal base64 encountered "
"in the certificate.")
else:
cert = load_certificate(FILETYPE_ASN1, der_data)
fp = cert.digest('sha256').decode('ascii')
fp_list.append(fp)
return fp_list
def remove_tls_certificate(self, cert_file_list=[],
excl_cert_file_list=[]):
"""Removes the TLS CA certificates from the iLO.
:param cert_file_list: List of TLS CA certificate files
:param excl_cert_file_list: List of TLS CA certificate files to be
retained on the iLO. These certificates will not be
removed from the iLO.
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is
@ -1709,49 +1754,42 @@ class RedfishOperations(operations.IloOperations):
cert_dict = {}
del_cert_list = []
exc_fp_list = []
for exc_cert_file in excl_cert_file_list:
efp_list = self._get_fps_from_file(exc_cert_file)
exc_fp_list.extend(efp_list)
LOG.debug("Excluding certificates with FingerPrints: %(exc_fp_list)s",
{'exc_fp_list': exc_fp_list})
if not cert_file_list:
tls_certificates = (sushy_system.bios_settings.tls_config.
tls_certificates)
for cert in tls_certificates:
fp = cert.get("FingerPrint")
if fp not in exc_fp_list:
cert_fp = {
"FingerPrint": fp
}
del_cert_list.append(cert_fp)
else:
all_fp_list = []
for cert_file in cert_file_list:
afp_list = self._get_fps_from_file(cert_file)
all_fp_list.extend(afp_list)
final_fp_set = set(all_fp_list) - set(exc_fp_list)
for fp in final_fp_set:
cert_fp = {
"FingerPrint": fp
}
del_cert_list.append(cert_fp)
else:
for cert_file in cert_file_list:
with open(cert_file, 'r') as f:
data = json.dumps(f.read())
p = re.sub(r"\"", "", data)
q = re.sub(r"\\n", "\r\n", p).rstrip()
c_list = re.findall(_CERTIFICATE_PATTERN, q, re.DOTALL)
if len(c_list) == 0:
LOG.warning("Could not find any valid certificate in "
"%(cert_file)s. Ignoring." %
{"cert_file": cert_file})
continue
for content in c_list:
pem_lines = [line.strip() for line in (
content.strip().split('\n'))]
try:
der_data = b64decode(''.join(pem_lines[1:-1]))
except ValueError:
LOG.warning("Illegal base64 encountered "
"in the certificate.")
else:
cert = load_certificate(FILETYPE_ASN1, der_data)
fp = cert.digest('sha256').decode('ascii')
cert_fp = {
"FingerPrint": fp
}
del_cert_list.append(cert_fp)
if len(del_cert_list) == 0:
msg = (self._("No valid certificate in %(cert_file_list)s.") %
{"cert_file_list": cert_file_list})

View File

@ -43,7 +43,6 @@ from proliantutils.redfish.resources.system.storage import array_controller
from proliantutils.redfish.resources.system.storage \
import common as common_storage
from proliantutils.redfish.resources.system import system as pro_sys
from proliantutils.redfish.resources.system import tls_config
@ddt.ddt
@ -2184,27 +2183,15 @@ class RedfishOperationsTestCase(testtools.TestCase):
@mock.patch.object(redfish, 'load_certificate')
@mock.patch.object(redfish, 'b64decode')
@mock.patch.object(builtins, 'open')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_remove_tls_certificate(self, get_sushy_system_mock,
_uefi_boot_mode_mock, open_mock,
decode_mock, load_cert_mock):
_uefi_boot_mode_mock.return_value = True
decode_mock.side_effect = ['first decoded data',
'second decoded data']
cert1_mock = mock.MagicMock()
cert2_mock = mock.MagicMock()
load_cert_mock.side_effect = [cert1_mock, cert2_mock]
cert1_mock.digest.return_value.decode.return_value = "humptydumpty"
cert2_mock.digest.return_value.decode.return_value = "hickerydickery"
def test__get_fps_from_file(self, open_mock, decode_mock, load_cert_mock):
data = (
"-----BEGIN CERTIFICATE-----\nMIID7TC\nCF"
"g879\n-----END CERTIFICATE-----\n"
"-----BEGIN CERTIFICATE-----\nKHY8UP\nGH"
"f792\n-----END CERTIFICATE-----\n"
)
decode_mock.side_effect = ['first decoded data',
'second decoded data']
fd_mock = mock.MagicMock(spec=io.BytesIO)
open_mock.return_value = fd_mock
fd_mock.__enter__.return_value = fd_mock
@ -2218,97 +2205,155 @@ class RedfishOperationsTestCase(testtools.TestCase):
mock.call(redfish.FILETYPE_ASN1, 'first decoded data'),
mock.call(redfish.FILETYPE_ASN1, 'second decoded data')
]
cert1_mock = mock.MagicMock()
cert2_mock = mock.MagicMock()
load_cert_mock.side_effect = [cert1_mock, cert2_mock]
cert1_mock.digest.return_value.decode.return_value = "hickerydickery"
cert2_mock.digest.return_value.decode.return_value = "humptydumpty"
cert_file = '/path/to/certfile'
self.rf_client.remove_tls_certificate([cert_file])
expected_fp_list = ["hickerydickery", "humptydumpty"]
actual_fp_list = self.rf_client._get_fps_from_file(cert_file)
decode_mock.assert_has_calls(decode_calls)
load_cert_mock.assert_has_calls(load_cert_calls)
self.assertEqual(expected_fp_list, actual_fp_list)
expected_data = {
"DeleteCertificates": [
{
"FingerPrint": "humptydumpty",
},
{
"FingerPrint": "hickerydickery"
}
]
}
(get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
remove_tls_certificate.assert_called_once_with(expected_data))
@mock.patch.object(redfish, 'load_certificate')
@mock.patch.object(redfish, 'b64decode')
@mock.patch.object(builtins, 'open')
@mock.patch.object(redfish.RedfishOperations, '_get_fps_from_file')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_remove_tls_certificate_no_certificate(self,
get_sushy_system_mock,
_uefi_boot_mode_mock,
open_mock, decode_mock,
load_cert_mock):
def test_remove_tls_certificate_default_exclude_list(
self, get_sushy_system_mock, _uefi_boot_mode_mock,
get_fps_mock):
_uefi_boot_mode_mock.return_value = True
get_fps_calls = [
mock.call('/path/to/certfile1'),
mock.call('/path/to/certfile2')
]
get_fps_mock.side_effect = [
["AA:BB:CC", "DD:EE:FF"],
["XX:YY:ZZ"]
]
data = (
"-----UNFORMATED CERTIFICATE-----\nMIID7TC\nCF"
"g879\n-----END CERTIFICATE-----\n"
"-----UNFORMATED CERTIFICATE-----\nKHY8UP\nGH"
"f792\n-----END CERTIFICATE-----\n"
)
expected = ["AA:BB:CC", "DD:EE:FF", "XX:YY:ZZ"]
fd_mock = mock.MagicMock(spec=io.BytesIO)
open_mock.return_value = fd_mock
fd_mock.__enter__.return_value = fd_mock
fd_mock.read.return_value = data
cert_file_list = ['/path/to/certfile1', '/path/to/certfile2']
remove_tls_mock = (get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
remove_tls_certificate)
self.rf_client.remove_tls_certificate(cert_file_list)
get_fps_mock.assert_has_calls(get_fps_calls)
val_delete_certs = remove_tls_mock.call_args[0][0].get(
"DeleteCertificates")
actual = [item.get("FingerPrint") for item in val_delete_certs]
actual.sort()
get_fps_mock.assert_has_calls(get_fps_calls)
self.assertEqual(expected, actual)
cert_file = '/path/to/certfile'
@mock.patch.object(redfish.RedfishOperations, '_get_fps_from_file')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_remove_tls_certificate_empty_exclude_list(
self, get_sushy_system_mock, _uefi_boot_mode_mock,
get_fps_mock):
_uefi_boot_mode_mock.return_value = True
get_fps_calls = [
mock.call('/path/to/certfile1'),
mock.call('/path/to/certfile2')
]
get_fps_mock.side_effect = [
["AA:BB:CC", "DD:EE:FF"],
["XX:YY:ZZ"]
]
expected = ["AA:BB:CC", "DD:EE:FF", "XX:YY:ZZ"]
cert_file_list = ['/path/to/certfile1', '/path/to/certfile2']
excl_cert_file_list = []
remove_tls_mock = (get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
remove_tls_certificate)
self.rf_client.remove_tls_certificate(cert_file_list,
excl_cert_file_list)
val_delete_certs = remove_tls_mock.call_args[0][0].get(
"DeleteCertificates")
actual = [item.get("FingerPrint") for item in val_delete_certs]
actual.sort()
get_fps_mock.assert_has_calls(get_fps_calls)
self.assertEqual(expected, actual)
@mock.patch.object(redfish.RedfishOperations, '_get_fps_from_file')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_remove_tls_certificate_valid_exclude_list(
self, get_sushy_system_mock, _uefi_boot_mode_mock,
get_fps_mock):
_uefi_boot_mode_mock.return_value = True
get_fps_calls = [
mock.call('/path/to/certfile1'),
mock.call('/path/to/certfile2')
]
get_fps_mock.side_effect = [
["DD:EE:FF", "KK:LL:MM"],
["AA:BB:CC", "DD:EE:FF"],
["XX:YY:ZZ"]
]
expected = ["AA:BB:CC", "XX:YY:ZZ"]
cert_file_list = ['/path/to/certfile1', '/path/to/certfile2']
excl_cert_file_list = ['/path/to/certfile3']
remove_tls_mock = (get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
remove_tls_certificate)
self.rf_client.remove_tls_certificate(
cert_file_list, excl_cert_file_list)
val_delete_certs = remove_tls_mock.call_args[0][0].get(
"DeleteCertificates")
actual = [item.get("FingerPrint") for item in val_delete_certs]
actual.sort()
get_fps_mock.assert_has_calls(get_fps_calls)
self.assertEqual(expected, actual)
@mock.patch.object(redfish.RedfishOperations, '_get_fps_from_file')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_remove_tls_certificate_no_certificate(
self, get_sushy_system_mock, _uefi_boot_mode_mock, get_fps_mock):
_uefi_boot_mode_mock.return_value = True
cert_file_list = ['/path/to/certfile1', '/path/to/certfile2']
get_fps_calls = [
mock.call('/path/to/certfile1'),
mock.call('/path/to/certfile2')
]
get_fps_mock.return_value = []
self.assertRaisesRegex(
exception.IloError,
"No valid certificate",
self.rf_client.remove_tls_certificate, [cert_file])
decode_mock.assert_not_called()
load_cert_mock.assert_not_called()
self.rf_client.remove_tls_certificate, cert_file_list)
get_fps_mock.assert_has_calls(get_fps_calls)
(get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
remove_tls_certificate.assert_not_called())
@mock.patch.object(redfish, 'load_certificate')
@mock.patch.object(redfish, 'b64decode')
@mock.patch.object(builtins, 'open')
@mock.patch.object(redfish.RedfishOperations, '_get_fps_from_file')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_remove_tls_certificate_raises_ilo_error(self,
get_sushy_system_mock,
_uefi_boot_mode_mock,
open_mock, decode_mock,
load_cert_mock):
def test_remove_tls_certificate_raises_ilo_error(
self, get_sushy_system_mock, _uefi_boot_mode_mock, get_fps_mock):
_uefi_boot_mode_mock.return_value = True
decode_mock.side_effect = ['first decoded data',
'second decoded data']
cert1_mock = mock.MagicMock()
cert2_mock = mock.MagicMock()
load_cert_mock.side_effect = [cert1_mock, cert2_mock]
cert1_mock.digest.return_value.decode.return_value = "humptydumpty"
cert2_mock.digest.return_value.decode.return_value = "hickerydickery"
get_fps_calls = [
mock.call('/path/to/certfile1'),
mock.call('/path/to/certfile2')
]
get_fps_mock.side_effect = [
["AA:BB:CC", "DD:EE:FF"],
["XX:YY:ZZ"]
]
data = (
"-----BEGIN CERTIFICATE-----\nMIID7TC\nCF"
"g879\n-----END CERTIFICATE-----\n"
"-----BEGIN CERTIFICATE-----\nKHY8UP\nGH"
"f792\n-----END CERTIFICATE-----\n"
)
cert_file_list = ['/path/to/certfile1', '/path/to/certfile2']
fd_mock = mock.MagicMock(spec=io.BytesIO)
open_mock.return_value = fd_mock
fd_mock.__enter__.return_value = fd_mock
fd_mock.read.return_value = data
cert_file = '/path/to/certfile'
(get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
remove_tls_certificate.side_effect) = (
@ -2317,7 +2362,8 @@ class RedfishOperationsTestCase(testtools.TestCase):
self.assertRaisesRegex(
exception.IloError,
'The Redfish controller has failed to remove TLS certificate.',
self.rf_client.remove_tls_certificate, [cert_file])
self.rf_client.remove_tls_certificate, cert_file_list)
get_fps_mock.assert_has_calls(get_fps_calls)
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
@ -2332,41 +2378,6 @@ class RedfishOperationsTestCase(testtools.TestCase):
'TLS certificates cannot be removed in BIOS boot mode',
self.rf_client.remove_tls_certificate, fp)
@mock.patch.object(redfish, 'load_certificate')
@mock.patch.object(redfish, 'b64decode')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_remove_tls_certificate_default(self, get_sushy_system_mock,
_uefi_boot_mode_mock, decode_mock,
load_cert_mock):
_uefi_boot_mode_mock.return_value = True
with open('proliantutils/tests/redfish/'
'json_samples/tls_config.json', 'r') as f:
jsonval = json.loads(f.read())
tlsconfig_mock = mock.MagicMock(spec=tls_config.TLSConfig)
tls_mock = mock.PropertyMock(return_value=tlsconfig_mock)
type(get_sushy_system_mock.return_value.bios_settings).tls_config = (
tls_mock)
certificates = jsonval.get('Certificates')
certs_mock = mock.PropertyMock(return_value=certificates)
type(tlsconfig_mock).tls_certificates = certs_mock
del_cert_list = []
for cert in certificates:
fp = cert.get("FingerPrint")
cert_fp = {
"FingerPrint": fp
}
del_cert_list.append(cert_fp)
self.rf_client.remove_tls_certificate()
(get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
remove_tls_certificate.assert_called_once_with(
{'DeleteCertificates': del_cert_list}))
decode_mock.assert_not_called()
load_cert_mock.assert_not_called()
@mock.patch.object(redfish.RedfishOperations,
'_update_security_parameter')
@mock.patch.object(main.HPESushy, 'get_account_service')