[Redfish] Enhance removing TLS certificates

Enhances the TLS certificate removal logic so that it now accepts
the list of TLS certificate files instead of fingerprints. Also
enhances the logic of exporting TLS certificates if multiple
certificates are present in a single file.

Change-Id: Ida1f13c27cea1134a38f5f8465398c63272494fb
This commit is contained in:
vmud213 2020-08-13 13:10:41 +00:00
parent af96983b3d
commit 92b9d4ac1b
5 changed files with 280 additions and 43 deletions

View File

@ -885,13 +885,15 @@ class IloClient(operations.IloOperations):
def add_tls_certificate(self, cert_file_list):
"""Adds the TLS certificate to the iLO
:param cert_file_list: List of TLS certificate files
:raises: IloError, on an error from iLO.
"""
return self._call_method('add_tls_certificate', cert_file_list)
def remove_tls_certificate(self, fp_list):
def remove_tls_certificate(self, cert_file_list):
"""Removes the TLS certificate from the iLO
:param cert_file_list: List of TLS certificate files
:raises: IloError, on an error from iLO.
"""
return self._call_method('remove_tls_certificate', fp_list)
return self._call_method('remove_tls_certificate', cert_file_list)

View File

@ -544,10 +544,10 @@ class IloOperations(object):
"""
raise exception.IloCommandNotSupportedError(ERRMSG)
def remove_tls_certificate(self, fp_list):
def remove_tls_certificate(self, cert_file_list):
"""Removes the TLS certificate from the iLO
:param fp_list: List of finger prints of the certificates
:param cert_file_list: List of TLS certificate files
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is
not supported on the server.

View File

@ -14,9 +14,12 @@
__author__ = 'HPE'
from base64 import b64decode
import json
import re
from OpenSSL.crypto import FILETYPE_ASN1
from OpenSSL.crypto import load_certificate
from six.moves.urllib import parse
import sushy
from sushy.resources.system import mappings as sushy_map
@ -114,6 +117,9 @@ SUPPORTED_BOOT_MODE_MAP = {
ilo_cons.SUPPORTED_BOOT_MODE_LEGACY_BIOS_AND_UEFI)
}
_CERTIFICATE_PATTERN = (
r'-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----')
LOG = log.get_logger(__name__)
@ -1339,17 +1345,33 @@ class RedfishOperations(operations.IloOperations):
not supported on the server.
"""
sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID)
if(self._is_boot_mode_uefi()):
cert_list = []
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)
r = q.rstrip()
cert = {}
cert['X509Certificate'] = r
cert_list.append(cert)
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:
cert = {}
cert['X509Certificate'] = content
cert_list.append(cert)
if len(cert_list) == 0:
msg = (self._("No valid certificate in %(cert_file_list)s.") %
{"cert_file_list": cert_file_list})
LOG.debug(msg)
raise exception.IloError(msg)
cert_dict = {}
cert_dict['NewCertificates'] = cert_list
@ -1366,28 +1388,61 @@ class RedfishOperations(operations.IloOperations):
msg = 'TLS certificate cannot be upload in BIOS boot mode'
raise exception.IloCommandNotSupportedInBiosError(msg)
def remove_tls_certificate(self, fp_list):
def remove_tls_certificate(self, cert_file_list):
"""Removes the TLS certificate from the iLO.
:param fp_list: List of finger prints of the TLS certificates
:param cert_file_list: List of TLS certificate files
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is
not supported on the server.
"""
sushy_system = self._get_sushy_system(PROLIANT_SYSTEM_ID)
if(self._is_boot_mode_uefi()):
cert = {}
cert_dict = {}
del_cert_list = []
for fp in fp_list:
cert_fp = {
"FingerPrint": fp
}
del_cert_list.append(cert_fp)
cert.update({"DeleteCertificates": del_cert_list})
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})
raise exception.IloError(msg)
cert_dict.update({"DeleteCertificates": del_cert_list})
try:
(sushy_system.bios_settings.tls_config.
tls_config_settings.remove_tls_certificate(cert))
tls_config_settings.remove_tls_certificate(cert_dict))
except sushy.exceptions.SushyError as e:
msg = (self._("The Redfish controller has failed to remove "
"TLS certificate. Error %(error)s") %

View File

@ -13,7 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import builtins
import collections
import io
import json
import ddt
@ -2028,39 +2031,98 @@ class RedfishOperationsTestCase(testtools.TestCase):
self.rf_client.add_tls_certificate,
data)
@mock.patch.object(builtins, 'open')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_add_tls_certificate(self, get_sushy_system_mock,
_uefi_boot_mode_mock):
_uefi_boot_mode_mock, open_mock):
_uefi_boot_mode_mock.return_value = True
cert_file = 'proliantutils/tests/redfish/json_samples/certfile.crt'
with open('proliantutils/tests/redfish/'
'json_samples/certfile.crt', 'r') as f:
cert_data = f.read()
data = (
"-----BEGIN CERTIFICATE-----\nMIID7TC\nCF"
"g879\n-----END CERTIFICATE-----\n"
"-----BEGIN CERTIFICATE-----\nKHY8UP\nGH"
"f792\n-----END CERTIFICATE-----\n"
)
import re
cert_data = cert_data.rstrip()
ref_data = re.sub(r"\n", "\r\n", cert_data)
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
c_l = [
"-----BEGIN CERTIFICATE-----\r\nMIID7TC\r\nCF"
"g879\r\n-----END CERTIFICATE-----",
"-----BEGIN CERTIFICATE-----\r\nKHY8UP\r\nGH"
"f792\r\n-----END CERTIFICATE-----"
]
data = {
expected_data = {
"NewCertificates": [
{
"X509Certificate": ref_data
"X509Certificate": c_l[0],
},
{
"X509Certificate": c_l[1],
}
]
}
cert_file = '/path/to/certfile'
self.rf_client.add_tls_certificate([cert_file])
(get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
add_tls_certificate.assert_called_once_with(data))
add_tls_certificate.assert_called_once_with(expected_data))
@mock.patch.object(builtins, 'open')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_add_tls_certificate_no_certificate(self, get_sushy_system_mock,
_uefi_boot_mode_mock,
open_mock):
_uefi_boot_mode_mock.return_value = True
data = (
"-----UNFORMATED CERTIFICATE-----\nMIID7TC\nCF"
"g879\n-----END CERTIFICATE-----\n"
"-----UNFORMATED CERTIFICATE-----\nKHY8UP\nGH"
"f792\n-----END CERTIFICATE-----\n"
)
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"
self.assertRaisesRegex(
exception.IloError,
"No valid certificate",
self.rf_client.add_tls_certificate, [cert_file])
(get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
add_tls_certificate.assert_not_called())
@mock.patch.object(builtins, 'open')
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_add_tls_certificate_raises_ilo_error(self, get_sushy_system_mock,
_uefi_boot_mode_mock):
_uefi_boot_mode_mock,
open_mock):
_uefi_boot_mode_mock.return_value = True
cert_file = 'proliantutils/tests/redfish/json_samples/certfile.crt'
data = (
"-----BEGIN CERTIFICATE-----\nMIID7TC\nCF"
"g879\n-----END CERTIFICATE-----\n"
"-----BEGIN CERTIFICATE-----\nKHY8UP\nGH"
"f792\n-----END CERTIFICATE-----\n"
)
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.
add_tls_certificate.side_effect) = (
@ -2071,26 +2133,143 @@ class RedfishOperationsTestCase(testtools.TestCase):
'The Redfish controller has failed to upload TLS certificate.',
self.rf_client.add_tls_certificate, [cert_file])
@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):
_uefi_boot_mode_mock, open_mock,
decode_mock, load_cert_mock):
_uefi_boot_mode_mock.return_value = True
fp = ('FA:3A:68:C7:7E:ED:90:21:D2:FA:3E:54:6B:0C:14:D3:'
'2F:8D:43:50:F7:05:A7:0F:1C:68:35:DB:5C:D2:53:28')
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"
cert = {}
del_cert_list = []
cert_fp = {
"FingerPrint": fp
data = (
"-----BEGIN CERTIFICATE-----\nMIID7TC\nCF"
"g879\n-----END CERTIFICATE-----\n"
"-----BEGIN CERTIFICATE-----\nKHY8UP\nGH"
"f792\n-----END CERTIFICATE-----\n"
)
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
decode_calls = [
mock.call('MIID7TCCFg879'),
mock.call('KHY8UPGHf792')
]
load_cert_calls = [
mock.call(redfish.FILETYPE_ASN1, 'first decoded data'),
mock.call(redfish.FILETYPE_ASN1, 'second decoded data')
]
cert_file = '/path/to/certfile'
self.rf_client.remove_tls_certificate([cert_file])
decode_mock.assert_has_calls(decode_calls)
load_cert_mock.assert_has_calls(load_cert_calls)
expected_data = {
"DeleteCertificates": [
{
"FingerPrint": "humptydumpty",
},
{
"FingerPrint": "hickerydickery"
}
]
}
del_cert_list.append(cert_fp)
cert.update({"DeleteCertificates": del_cert_list})
self.rf_client.remove_tls_certificate([fp])
(get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
remove_tls_certificate.assert_called_once_with(cert))
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, '_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):
_uefi_boot_mode_mock.return_value = True
data = (
"-----UNFORMATED CERTIFICATE-----\nMIID7TC\nCF"
"g879\n-----END CERTIFICATE-----\n"
"-----UNFORMATED CERTIFICATE-----\nKHY8UP\nGH"
"f792\n-----END CERTIFICATE-----\n"
)
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'
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()
(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, '_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):
_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"
data = (
"-----BEGIN CERTIFICATE-----\nMIID7TC\nCF"
"g879\n-----END CERTIFICATE-----\n"
"-----BEGIN CERTIFICATE-----\nKHY8UP\nGH"
"f792\n-----END CERTIFICATE-----\n"
)
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) = (
sushy.exceptions.SushyError)
self.assertRaisesRegex(
exception.IloError,
'The Redfish controller has failed to remove TLS certificate.',
self.rf_client.remove_tls_certificate, [cert_file])
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')

View File

@ -10,3 +10,4 @@ pysnmp>=4.2.3,<5.0.0 # BSD
# Redfish communication uses the Sushy library
sushy>=3.1.0
pyOpenSSL>=19.1.0