Add mTLS option for redfish test
When the BMC only enables mTLS auth type at build time. Robot test
should be test normally. Adding "MTLS_ENABLED" environment variable as
an option and modifications to achieve it.
Signed-off-by: Tony Lee <tony.lee@quantatw.com>
Change-Id: I68c753a453f0c958c900f8ba3e1d73145e093d6c
diff --git a/docs/redfish_request_via_mTLS.md b/docs/redfish_request_via_mTLS.md
new file mode 100644
index 0000000..daeb7f8
--- /dev/null
+++ b/docs/redfish_request_via_mTLS.md
@@ -0,0 +1,81 @@
+Redfish Request Via mTLS
+=========================
+
+When the BMC only enables mTLS type for authentication. Redfish request in robot
+test should be tested normally.
+
+## Required environment variables in Robot
+
+ - **MTLS_ENABLED** indicates whether mTLS is enabled in BMC.
+ False by default:
+
+ ```
+ ${MTLS_ENABLED} False
+ ```
+
+ - **VALID_CERT** indicates valid mTLS certificate for authentication.
+ When a redfish request doesn't specify a certificate, no certificate by
+ default.
+
+ ```
+ ${VALID_CERT} ${EMPTY}
+ ```
+
+ - **CERT_DIR_PATH** indicates path of mTLS certificates directory:
+
+ ```
+ ${CERT_DIR_PATH} ${EMPTY}
+ ```
+## How to send a redfish request with certificate
+
+- When a redfish request is executed, it will be executed through the python
+ library **requests** with certificate. It supports for all Redfish REST
+ requests (get, head, post, put, patch, delete):
+
+ ```
+ import requests
+
+ cert_dict = kwargs.pop('certificate', {"certificate_name":VALID_CERT})
+ response = requests.get(
+ url='https://'+ host + args[0],
+ cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+ verify=False,
+ headers={"Cache-Control": "no-cache"})
+ ```
+
+- Original robot code of redfish request doesn’t need to modify. It will send
+ the request with the default certificate ${VALID_CERT}.
+
+- The example provides Redfish request to use other certificate in the Robot
+ code below:
+
+ ```
+ ${certificate_dict}= Create Dictionary certificate_name=${CERT}
+ Redfish.Get ${VALID_URL} certificate=&{certificate_dict}
+ ... valid_status_codes=[${HTTP_OK}]
+ ```
+
+## Test Cases for mTLS authentication
+
+mTLS authentication is only a means to connect to the BMC, not for testing
+purposes. Therefore, some test cases need to write a new one to match it for
+mTLS authentication. (Requires test certificate with different privileges or
+username) Some cases don’t need to be tested because the purpose of
+them are inapplicable to mTLS. Case studies are as follows:
+
+- **Create_IPMI_User_And_Verify_Login_Via_Redfish**
+
+ In this case, it uses IPMI to create a random user with password and
+ privilege, and then verifies the login via Redfish. Therefore, it will
+ logout the default user and then login with the user just created by IPMI.
+ So it does not need to use mTLS to authenticate login and logout.
+ It can be replaced as follows: Prepare a certificate with the user name
+ "admin_user" in advance. Use IPMI to create a user named admin_user. Then
+ you can use the Redfish request with the admin_user certificate to provide
+ the server for verification.
+
+- **Attempt_Login_With_Expired_Session**
+
+ Most cases related to sessions don't require mTLS because Redfish requests
+ don't need to create a session first. Therefore, there is no need to test
+ these cases when mTLS is enabled.
diff --git a/lib/bmc_redfish.py b/lib/bmc_redfish.py
index 61b8177..036c990 100644
--- a/lib/bmc_redfish.py
+++ b/lib/bmc_redfish.py
@@ -14,6 +14,9 @@
import gen_print as gp
+MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
+
+
class bmc_redfish(redfish_plus):
r"""
bmc_redfish is a child class of redfish_plus that is designed to provide
@@ -39,8 +42,11 @@
"""
self.__inited__ = False
try:
- super(bmc_redfish, self).__init__(*args, **kwargs)
- self.__inited__ = True
+ if MTLS_ENABLED == 'True':
+ self.__inited__ = True
+ else:
+ super(bmc_redfish, self).__init__(*args, **kwargs)
+ self.__inited__ = True
except ValueError as get_exception:
except_type, except_value, except_traceback = sys.exc_info()
regex = r"The HTTP status code was not valid:[\r\n]+status:[ ]+502"
@@ -62,6 +68,8 @@
kwargs See parent class method prolog for details.
"""
+ if MTLS_ENABLED == 'True':
+ return None
if not self.__inited__:
message = "bmc_redfish.__init__() was never successfully run. It "
message += "is likely that the target BMC firmware code level "
@@ -77,6 +85,13 @@
super(bmc_redfish, self).login(username, password, auth,
*args, **kwargs)
+ def logout(self):
+
+ if MTLS_ENABLED == 'True':
+ return None
+ else:
+ super(bmc_redfish, self).logout()
+
def get_properties(self, *args, **kwargs):
r"""
Return dictionary of attributes for a given path.
diff --git a/lib/bmc_redfish_utils.py b/lib/bmc_redfish_utils.py
index a39c5d9..ea39930 100644
--- a/lib/bmc_redfish_utils.py
+++ b/lib/bmc_redfish_utils.py
@@ -9,6 +9,8 @@
from robot.libraries.BuiltIn import BuiltIn
import gen_print as gp
+MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
+
class bmc_redfish_utils(object):
@@ -22,15 +24,18 @@
self.__inited__ = False
self._redfish_ = BuiltIn().get_library_instance('redfish')
- # There is a possibility that a given driver support both redfish and
- # legacy REST.
- self._redfish_.login()
- self._rest_response_ = \
- self._redfish_.get("/xyz/openbmc_project/", valid_status_codes=[200, 404])
-
- # If REST URL /xyz/openbmc_project/ is supported.
- if self._rest_response_.status == 200:
+ if MTLS_ENABLED == 'True':
self.__inited__ = True
+ else:
+ # There is a possibility that a given driver support both redfish and
+ # legacy REST.
+ self._redfish_.login()
+ self._rest_response_ = \
+ self._redfish_.get("/xyz/openbmc_project/", valid_status_codes=[200, 404])
+
+ # If REST URL /xyz/openbmc_project/ is supported.
+ if self._rest_response_.status == 200:
+ self.__inited__ = True
BuiltIn().set_global_variable("${REDFISH_REST_SUPPORTED}", self.__inited__)
diff --git a/lib/redfish_plus.py b/lib/redfish_plus.py
index 2257fc0..8364b01 100755
--- a/lib/redfish_plus.py
+++ b/lib/redfish_plus.py
@@ -7,6 +7,15 @@
from redfish.rest.v1 import HttpClient
import gen_print as gp
import func_args as fa
+import requests
+import json
+from robot.libraries.BuiltIn import BuiltIn
+
+
+host = BuiltIn().get_variable_value("${OPENBMC_HOST}")
+MTLS_ENABLED = BuiltIn().get_variable_value("${MTLS_ENABLED}")
+CERT_DIR_PATH = BuiltIn().get_variable_value("${CERT_DIR_PATH}")
+VALID_CERT = BuiltIn().get_variable_value("${VALID_CERT}")
def valid_http_status_code(status, valid_status_codes):
@@ -110,28 +119,134 @@
# Define rest function wrappers.
def get(self, *args, **kwargs):
- return self.rest_request(super(redfish_plus, self).get, *args,
- **kwargs)
+
+ if MTLS_ENABLED == 'True':
+ return self.rest_request(self.get_with_mtls, *args, **kwargs)
+ else:
+ return self.rest_request(super(redfish_plus, self).get, *args,
+ **kwargs)
def head(self, *args, **kwargs):
- return self.rest_request(super(redfish_plus, self).head, *args,
- **kwargs)
+
+ if MTLS_ENABLED == 'True':
+ return self.rest_request(self.head_with_mtls, *args, **kwargs)
+ else:
+ return self.rest_request(super(redfish_plus, self).head, *args,
+ **kwargs)
def post(self, *args, **kwargs):
- return self.rest_request(super(redfish_plus, self).post, *args,
- **kwargs)
+
+ if MTLS_ENABLED == 'True':
+ return self.rest_request(self.post_with_mtls, *args, **kwargs)
+ else:
+ return self.rest_request(super(redfish_plus, self).post, *args,
+ **kwargs)
def put(self, *args, **kwargs):
- return self.rest_request(super(redfish_plus, self).put, *args,
- **kwargs)
+
+ if MTLS_ENABLED == 'True':
+ return self.rest_request(self.put_with_mtls, *args, **kwargs)
+ else:
+ return self.rest_request(super(redfish_plus, self).put, *args,
+ **kwargs)
def patch(self, *args, **kwargs):
- return self.rest_request(super(redfish_plus, self).patch, *args,
- **kwargs)
+
+ if MTLS_ENABLED == 'True':
+ return self.rest_request(self.patch_with_mtls, *args, **kwargs)
+ else:
+ return self.rest_request(super(redfish_plus, self).patch, *args,
+ **kwargs)
def delete(self, *args, **kwargs):
- return self.rest_request(super(redfish_plus, self).delete, *args,
- **kwargs)
+
+ if MTLS_ENABLED == 'True':
+ return self.rest_request(self.delete_with_mtls, *args, **kwargs)
+ else:
+ return self.rest_request(super(redfish_plus, self).delete, *args,
+ **kwargs)
def __del__(self):
del self
+
+ def get_with_mtls(self, *args, **kwargs):
+
+ cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+ response = requests.get(url='https://' + host + args[0],
+ cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+ verify=False,
+ headers={"Cache-Control": "no-cache"})
+
+ response.status = response.status_code
+ if response.status == 200:
+ response.dict = json.loads(response.text)
+
+ return response
+
+ def post_with_mtls(self, *args, **kwargs):
+
+ cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+ body = kwargs.pop('body', {})
+ response = requests.post(url='https://' + host + args[0],
+ json=body,
+ cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+ verify=False,
+ headers={"Content-Type": "application/json"})
+
+ response.status = response.status_code
+
+ return response
+
+ def patch_with_mtls(self, *args, **kwargs):
+
+ cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+ body = kwargs.pop('body', {})
+ response = requests.patch(url='https://' + host + args[0],
+ json=body,
+ cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+ verify=False,
+ headers={"Content-Type": "application/json"})
+
+ response.status = response.status_code
+
+ return response
+
+ def delete_with_mtls(self, *args, **kwargs):
+
+ cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+ response = requests.delete(url='https://' + host + args[0],
+ cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+ verify=False,
+ headers={"Content-Type": "application/json"})
+
+ response.status = response.status_code
+
+ return response
+
+ def put_with_mtls(self, *args, **kwargs):
+
+ cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+ body = kwargs.pop('body', {})
+ response = requests.put(url='https://' + host + args[0],
+ json=body,
+ cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+ verify=False,
+ headers={"Content-Type": "application/json"})
+
+ response.status = response.status_code
+
+ return response
+
+ def head_with_mtls(self, *args, **kwargs):
+
+ cert_dict = kwargs.pop('certificate', {"certificate_name": VALID_CERT})
+ body = kwargs.pop('body', {})
+ response = requests.head(url='https://' + host + args[0],
+ json=body,
+ cert=CERT_DIR_PATH + '/' + cert_dict['certificate_name'],
+ verify=False,
+ headers={"Content-Type": "application/json"})
+
+ response.status = response.status_code
+
+ return response
diff --git a/lib/resource.robot b/lib/resource.robot
index 9d5379e..91d81bb 100755
--- a/lib/resource.robot
+++ b/lib/resource.robot
@@ -21,6 +21,14 @@
${OPENBMC_PASSWORD} 0penBmc
${REST_USERNAME} root
${REST_PASSWORD} 0penBmc
+
+# MTLS_ENABLED indicates whether mTLS is enabled.
+${MTLS_ENABLED} False
+# Valid mTLS certificate for authentication.
+${VALID_CERT} ${EMPTY}
+# Path of mTLS certificates directory.
+${CERT_DIR_PATH} ${EMPTY}
+
${IPMI_PASSWORD} 0penBmc
${MACHINE_TYPE} palmetto
${DBUS_POLL_INTERVAL} 15s