Merge "[Redfish] Enhance removing TLS certificates"
This commit is contained in:
commit
9a29e32bb2
|
@ -900,13 +900,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)
|
||||
|
|
|
@ -558,10 +558,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.
|
||||
|
|
|
@ -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__)
|
||||
|
||||
|
||||
|
@ -1421,17 +1427,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
|
||||
|
@ -1448,28 +1470,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") %
|
||||
|
|
|
@ -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
|
||||
|
@ -2050,39 +2053,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) = (
|
||||
|
@ -2093,26 +2155,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')
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue