[Redfish] Provides APIs to add/remove TLS certificate

Change-Id: I25c827b5dbd443c2827c0faeb9c92c06e7f778f7
This commit is contained in:
vmud213 2020-06-11 11:45:49 +00:00
parent c57437dc85
commit af96983b3d
11 changed files with 562 additions and 1 deletions

View File

@ -121,7 +121,9 @@ SUPPORTED_REDFISH_METHODS = [
'get_iscsi_initiator_info',
'set_iscsi_initiator_info',
'set_http_boot_url',
'get_http_boot_url'
'get_http_boot_url',
'add_tls_certificate',
'remove_tls_certificate'
]
LOG = log.get_logger(__name__)
@ -879,3 +881,17 @@ class IloClient(operations.IloOperations):
:raises: IloError, on an error from iLO.
"""
return self._call_method('get_available_disk_types')
def add_tls_certificate(self, cert_file_list):
"""Adds the TLS certificate to the iLO
:raises: IloError, on an error from iLO.
"""
return self._call_method('add_tls_certificate', cert_file_list)
def remove_tls_certificate(self, fp_list):
"""Removes the TLS certificate from the iLO
:raises: IloError, on an error from iLO.
"""
return self._call_method('remove_tls_certificate', fp_list)

View File

@ -533,3 +533,23 @@ class IloOperations(object):
not supported on the server.
"""
raise exception.IloCommandNotSupportedError(ERRMSG)
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.
:raises: IloCommandNotSupportedError, if the command is
not supported on the server.
"""
raise exception.IloCommandNotSupportedError(ERRMSG)
def remove_tls_certificate(self, fp_list):
"""Removes the TLS certificate from the iLO
:param fp_list: List of finger prints of the certificates
:raises: IloError, on an error from iLO.
:raises: IloCommandNotSupportedError, if the command is
not supported on the server.
"""
raise exception.IloCommandNotSupportedError(ERRMSG)

View File

@ -15,6 +15,7 @@
__author__ = 'HPE'
import json
import re
from six.moves.urllib import parse
import sushy
@ -1327,3 +1328,72 @@ class RedfishOperations(operations.IloOperations):
{'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)
def add_tls_certificate(self, cert_file_list):
"""Adds the TLS certificates to the iLO.
: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_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)
cert_dict = {}
cert_dict['NewCertificates'] = cert_list
try:
(sushy_system.bios_settings.tls_config.
tls_config_settings.add_tls_certificate(cert_dict))
except sushy.exceptions.SushyError as e:
msg = (self._("The Redfish controller has failed to upload "
"TLS certificate. Error %(error)s") %
{'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)
else:
msg = 'TLS certificate cannot be upload in BIOS boot mode'
raise exception.IloCommandNotSupportedInBiosError(msg)
def remove_tls_certificate(self, fp_list):
"""Removes the TLS certificate from the iLO.
:param fp_list: List of finger prints of the TLS certificates
: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 = {}
del_cert_list = []
for fp in fp_list:
cert_fp = {
"FingerPrint": fp
}
del_cert_list.append(cert_fp)
cert.update({"DeleteCertificates": del_cert_list})
try:
(sushy_system.bios_settings.tls_config.
tls_config_settings.remove_tls_certificate(cert))
except sushy.exceptions.SushyError as e:
msg = (self._("The Redfish controller has failed to remove "
"TLS certificate. Error %(error)s") %
{'error': str(e)})
LOG.debug(msg)
raise exception.IloError(msg)
else:
msg = 'TLS certificate cannot be removed in BIOS boot mode'
raise exception.IloCommandNotSupportedInBiosError(msg)

View File

@ -21,6 +21,7 @@ from proliantutils import log
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import iscsi
from proliantutils.redfish.resources.system import mappings
from proliantutils.redfish.resources.system import tls_config
from proliantutils.redfish import utils
LOG = log.get_logger(__name__)
@ -94,6 +95,19 @@ class BIOSSettings(base.ResourceBase):
self, ["Oem", "Hpe", "Links", "iScsi"]),
redfish_version=self.redfish_version)
@property
@sushy_utils.cache_it
def tls_config(self):
"""Property to provide reference to BIOS TLS configuration instance
It is calculated once when the first time it is queried. On refresh,
this property gets reset.
"""
return tls_config.TLSConfig(
self._conn, utils.get_subresource_path_by(
self, ["Oem", "Hpe", "Links", "TlsConfig"]),
redfish_version=self.redfish_version)
@property
@sushy_utils.cache_it
def bios_mappings(self):

View File

@ -0,0 +1,62 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from sushy.resources import base
from sushy import utils as sushy_utils
from proliantutils.redfish import utils
class TLSConfig(base.ResourceBase):
"""Class that represents the TLS Configuration.
This class extends the functionality of base resource class
from sushy.
"""
@property
@sushy_utils.cache_it
def tls_config_settings(self):
"""Property to provide reference to TLS configuration settings instance
It is calculated once when the first time it is queried. On refresh,
this property gets reset.
"""
return TLSConfigSettings(
self._conn,
utils.get_subresource_path_by(
self, ["@Redfish.Settings", "SettingsObject"]),
redfish_version=self.redfish_version)
class TLSConfigSettings(base.ResourceBase):
"""Class that represents the TLS configuration settings.
This class extends the functionality of base resource class
from sushy.
"""
def add_tls_certificate(self, cert_data):
"""Update tls certificate
:param data: default tls certs data
"""
self._conn.put(self.path, data=cert_data)
def remove_tls_certificate(self, cert_data):
"""Update tls certificate
:param data: default tls certs data
"""
self._conn.put(self.path, data=cert_data)

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID7TCCAtWgAwIBAgIJAPpdOt6Qt3FFMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD
VQQGEwJJTjESMBAGA1UECAwJVGVsYW5nYW5hMRIwEAYDVQQHDAlIeWRlcmFiYWQx
DDAKBgNVBAoMA0hQRTELMAkGA1UECwwCU0cxFDASBgNVBAMMCzE3Mi4xNy4xLjg4
MSQwIgYJKoZIhvcNAQkBFhV2aW5heS5tLmt1bWFyQGhwZS5jb20wHhcNMTkwNDI0
MDg1MTI4WhcNMjAwNDIzMDg1MTI4WjCBjDELMAkGA1UEBhMCSU4xEjAQBgNVBAgM
CVRlbGFuZ2FuYTESMBAGA1UEBwwJSHlkZXJhYmFkMQwwCgYDVQQKDANIUEUxCzAJ
BgNVBAsMAlNHMRQwEgYDVQQDDAsxNzIuMTcuMS44ODEkMCIGCSqGSIb3DQEJARYV
dmluYXkubS5rdW1hckBocGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA3afHTbbueWjZMp0w0g4XN5VaGD17kk2fjjpJn0Ltgx2L4SSbEabM/491
A79wZlr0WSo53IYH7AB2ZA0Ze8ZBML6F4FBhSf6lPoGJN/cHDz5z/bDhNV/KrXLA
uqpghDQS0hiv0KHsk3JXaRz/FM+MmmoKdOWSLCWReGOOIGYwEyaB4CFAXefppTlP
ii0IyzGLMfUERxP3x/UpsR/hejun8QNOKcf5mpTbGbh1Ro+yvoJbeXy7ivkOX9QG
7w8UMzGxFXgQ/U3VvpfY5C1A23wAvX9F+lwNQQ71XfmB9ascC7luzWQ3WqVHVpKh
Ksv0vQ3MM17xEuHzlUrUJJSzltsb+wIDAQABo1AwTjAdBgNVHQ4EFgQUB6xKvLMe
R0JVdSSJZH37aEBh5zQwHwYDVR0jBBgwFoAUB6xKvLMeR0JVdSSJZH37aEBh5zQw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAU3fBFSyx19SMgvBVzelP
NyudGhNKm+3zmKndi2HYdKQWHqg6dMSR9zE8FE6viUgB2v1V+5JpF7NhDbgCaNng
DmC8sm3p6lNpvcEDnPak6759K6yT/k6tlPsZ5GsIGXQhBMJdVw0zJPNHEMIV8SnV
D0SGSG5F+pcPvnQtdLzGl18kIOj0NmjzKnz+l/jBd7bckb7vXM+M/KRmrTE6cLF0
yB6IQ/UPWiuOIflxSxhSPaVYNWiaRALCJEiIYagoWr6mOUxqCnAdR50Pfwxz7KGI
txLjc4+qa6ZgWEBx3uDQ9ehysBrMOmWg5nXRlO/nbtyFXi+GzZChiNA75fgnb6e/
YQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,57 @@
{
"@Redfish.Settings":
{
"@odata.type": "#Settings.v1_0_0.Settings",
"ETag": "",
"Messages":
[
{
"MessageId": "Base.1.0.Success"
}
],
"SettingsObject":
{
"@odata.id": "/redfish/v1/systems/1/bios/tlsconfig/settings/"
},
"Time": null
},
"@odata.context": "/redfish/v1/$metadata#HpeTlsConfig.HpeTlsConfig",
"@odata.etag": "W/\"32F7F4DB0288E0E0E071C693DD579D6C\"",
"@odata.id": "/redfish/v1/systems/1/bios/tlsconfig/",
"@odata.type": "#HpeTlsConfig.v1_0_0.HpeTlsConfig",
"Certificates":
[
],
"Ciphers": "AES128-SHA:AES256-SHA:AES128-SHA256:AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384",
"DeleteCertificates":
[
],
"HostnameCheck": "Disabled",
"Id": "tlsconfig",
"Name": "TLS Current Settings",
"NewCertificates":
[
],
"Oem":
{
"Hpe":
{
"@odata.type": "#HpeBiosExt.v2_0_0.HpeBiosExt",
"Links":
{
"BaseConfigs":
{
"@odata.id": "/redfish/v1/systems/1/bios/tlsconfig/baseconfigs/"
}
},
"SettingsObject":
{
"UnmodifiedETag": "W/\"28F385EA989AD0D0D053F745E614B9D6\""
}
}
},
"ProtocolVersion": "AUTO",
"TlsCaCertificateCount": 0,
"VerifyMode": "PEER"
}

View File

@ -0,0 +1,66 @@
{
"@Redfish.Settings":
{
"@odata.type": "#Settings.v1_0_0.Settings",
"ETag": "C6239FAE",
"Messages":
[
{
"MessageId": "Base.1.0.Success"
}
],
"SettingsObject":
{
"@odata.id": "/redfish/v1/systems/1/bios/tlsconfig/settings/"
},
"Time": "2020-06-11T21:20:31+00:00"
},
"@odata.context": "/redfish/v1/$metadata#HpeTlsConfig.HpeTlsConfig",
"@odata.etag": "W/\"885481367F69969696DE63CCE9D97509\"",
"@odata.id": "/redfish/v1/systems/1/bios/tlsconfig/",
"@odata.type": "#HpeTlsConfig.v1_0_0.HpeTlsConfig",
"Certificates":
[
{
"FingerPrint": "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",
"Issuer": "C=IN, ST=Karnataka, L=Bengaluru, O=HPE, OU=BCOS, CN=Vinay Muddu, emailAddress=vinay.m.kumar@hpe.com",
"SerialNumber": "92DF813625F950E5",
"Subject": "C=IN, ST=Karnataka, L=Bengaluru, O=HPE, OU=BCOS, CN=Vinay Muddu, emailAddress=vinay.m.kumar@hpe.com",
"ValidNotAfter": "06/08/2021 06:40",
"ValidNotBefore": "06/08/2020 06:40"
}
],
"Ciphers": "AES128-SHA:AES256-SHA:AES128-SHA256:AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384",
"DeleteCertificates":
[
],
"HostnameCheck": "Disabled",
"Id": "tlsconfig",
"Name": "TLS Current Settings",
"NewCertificates":
[
],
"Oem":
{
"Hpe":
{
"@odata.type": "#HpeBiosExt.v2_0_0.HpeBiosExt",
"Links":
{
"BaseConfigs":
{
"@odata.id": "/redfish/v1/systems/1/bios/tlsconfig/baseconfigs/"
}
},
"SettingsObject":
{
"UnmodifiedETag": "W/\"89BE572CAA977F7F7FE56E1ADBF4F043\""
}
}
},
"ProtocolVersion": "AUTO",
"TlsCaCertificateCount": 1,
"VerifyMode": "PEER"
}

View File

@ -23,6 +23,7 @@ from proliantutils import exception
from proliantutils.redfish.resources.system import bios
from proliantutils.redfish.resources.system import constants as sys_cons
from proliantutils.redfish.resources.system import iscsi
from proliantutils.redfish.resources.system import tls_config
class BIOSSettingsTestCase(testtools.TestCase):
@ -111,6 +112,22 @@ class BIOSSettingsTestCase(testtools.TestCase):
self.bios_inst.iscsi_resource)
self.conn.get.return_value.json.assert_not_called()
def test_tls_config(self):
self.conn.get.return_value.json.reset_mock()
with open('proliantutils/tests/redfish/'
'json_samples/tls_config.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read()))
actual_settings = self.bios_inst.tls_config
self.assertIsInstance(actual_settings,
tls_config.TLSConfig)
self.conn.get.return_value.json.assert_called_once_with()
# reset mock
self.conn.get.return_value.json.reset_mock()
self.assertIs(actual_settings,
self.bios_inst.tls_config)
self.conn.get.return_value.json.assert_not_called()
def test__get_base_configs(self):
with open('proliantutils/tests/redfish/'
'json_samples/bios_base_configs.json', 'r') as f:

View File

@ -0,0 +1,119 @@
# Copyright 2017 Hewlett Packard Enterprise Development LP
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
import testtools
from proliantutils.redfish.resources.system import tls_config
class TLSConfigTestCase(testtools.TestCase):
def setUp(self):
super(TLSConfigTestCase, self).setUp()
self.conn = mock.MagicMock()
with open('proliantutils/tests/redfish/'
'json_samples/tls_config.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read()))
self.tls_config_inst = tls_config.TLSConfig(
self.conn, '/redfish/v1/Systems/1/bios/tlsconfig',
redfish_version='1.0.2')
def test_tls_config_settings(self):
self.conn.get.return_value.json.reset_mock()
with open('proliantutils/tests/redfish/'
'json_samples/tls_config_settings.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read()))
actual_settings = self.tls_config_inst.tls_config_settings
self.assertIsInstance(actual_settings,
tls_config.TLSConfigSettings)
self.conn.get.return_value.json.assert_called_once_with()
# reset mock
self.conn.get.return_value.json.reset_mock()
self.assertIs(actual_settings,
self.tls_config_inst.tls_config_settings)
self.conn.get.return_value.json.assert_not_called()
def test_iscsi_settings_on_refresh(self):
with open('proliantutils/tests/redfish/'
'json_samples/tls_config_settings.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read()))
actual_settings = self.tls_config_inst.tls_config_settings
self.assertIsInstance(actual_settings,
tls_config.TLSConfigSettings)
with open('proliantutils/tests/redfish/'
'json_samples/tls_config.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read()))
self.tls_config_inst.invalidate()
self.tls_config_inst.refresh(force=False)
self.assertTrue(actual_settings._is_stale)
with open('proliantutils/tests/redfish/'
'json_samples/tls_config_settings.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read()))
self.assertIsInstance(self.tls_config_inst.tls_config_settings,
tls_config.TLSConfigSettings)
self.assertFalse(actual_settings._is_stale)
class TLSConfigSettingsTestCase(testtools.TestCase):
def setUp(self):
super(TLSConfigSettingsTestCase, self).setUp()
self.conn = mock.MagicMock()
with open('proliantutils/tests/redfish/'
'json_samples/tls_config_settings.json', 'r') as f:
self.conn.get.return_value.json.return_value = (
json.loads(f.read()))
self.tls_config_settings_inst = tls_config.TLSConfigSettings(
self.conn, '/redfish/v1/Systems/1/bios/tlsconfig/settings',
redfish_version='1.0.2')
def test_add_tls_certificate(self):
target_uri = '/redfish/v1/Systems/1/bios/tlsconfig/settings'
cert_data = {
"NewCertificates": [
{
"X509Certificate": "abc"
}
]
}
self.tls_config_settings_inst.add_tls_certificate(cert_data)
self.tls_config_settings_inst._conn.put.assert_called_once_with(
target_uri, data=cert_data)
def test_remove_tls_certificate(self):
target_uri = '/redfish/v1/Systems/1/bios/tlsconfig/settings'
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')
cert = {}
del_cert_list = [{"FingerPrint": fp}]
cert.update({"DeleteCertificates": del_cert_list})
self.tls_config_settings_inst.remove_tls_certificate(cert)
self.tls_config_settings_inst._conn.put.assert_called_once_with(
target_uri, data=cert)

View File

@ -2008,3 +2008,99 @@ class RedfishOperationsTestCase(testtools.TestCase):
exception.IloError,
'Could not set HTTPS URL on the iLO.',
self.rf_client.set_http_boot_url, url, dhcp_enabled)
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_add_tls_certificate_bios(self, get_sushy_system_mock,
_uefi_boot_mode_mock):
_uefi_boot_mode_mock.return_value = False
data = {
"NewCertificates": [
{
"X509Certificate": "Some data"
}
]
}
self.assertRaisesRegex(
exception.IloCommandNotSupportedInBiosError,
'TLS certificate cannot be upload in BIOS boot mode',
self.rf_client.add_tls_certificate,
data)
@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.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()
import re
cert_data = cert_data.rstrip()
ref_data = re.sub(r"\n", "\r\n", cert_data)
data = {
"NewCertificates": [
{
"X509Certificate": ref_data
}
]
}
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))
@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.return_value = True
cert_file = 'proliantutils/tests/redfish/json_samples/certfile.crt'
(get_sushy_system_mock.return_value.
bios_settings.tls_config.tls_config_settings.
add_tls_certificate.side_effect) = (
sushy.exceptions.SushyError)
self.assertRaisesRegex(
exception.IloError,
'The Redfish controller has failed to upload TLS certificate.',
self.rf_client.add_tls_certificate, [cert_file])
@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.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')
cert = {}
del_cert_list = []
cert_fp = {
"FingerPrint": fp
}
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))
@mock.patch.object(redfish.RedfishOperations, '_is_boot_mode_uefi')
@mock.patch.object(redfish.RedfishOperations, '_get_sushy_system')
def test_remove_tls_certificate_bios(self, get_sushy_system_mock,
_uefi_boot_mode_mock):
_uefi_boot_mode_mock.return_value = False
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')
self.assertRaisesRegex(
exception.IloCommandNotSupportedInBiosError,
'TLS certificate cannot be removed in BIOS boot mode',
self.rf_client.remove_tls_certificate, fp)