summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVanou Ishii <ishii.vanou@fujitsu.com>2022-06-01 15:25:25 +0900
committerVanou Ishii <ishii.vanou@fujitsu.com>2022-09-18 12:45:37 +0900
commitdcf37a43bbbc7560ac61ba1f3902235a047d28e7 (patch)
tree5cc8d79a3d0530d5c33f3ad78682c4d7703d858c
parent231a41c924ceb6de2b92716c712f90442d3a79f2 (diff)
downloadironic-dcf37a43bbbc7560ac61ba1f3902235a047d28e7.tar.gz
Fix iRMC driver to use certification file in HTTPS
This patch modifies iRMC driver to use certification file when it connects to iRMC via HTTPS Conflicts: doc/source/admin/drivers/irmc.rst driver-requirements.txt ironic/drivers/modules/irmc/common.py ironic/drivers/modules/irmc/raid.py ironic/tests/unit/drivers/modules/irmc/test_common.py ironic/tests/unit/drivers/modules/irmc/test_power.py lower-constraints.txt releasenotes/notes/irmc-add-certification-file-option-34e7a0062c768e58.yaml Change-Id: If69ce1cf2789d9d60fb8e544596cf7d29eab514d Co-authored-by: Kobayashi Daisuke <kobayashi.da-06@fujitsu.com> Co-authored-by: Song Shukun <song.shukun@jp.fujitsu.com> Story: 2009801 Task: 44345 (cherry picked from commit 64d7a7f3077bc000a18c4a0c56f122941b262483) (cherry picked from commit 6c0152afa141d05ee28cba81178622021574ae17) (cherry picked from commit 931f13d3c547928d57052eeae1d8d2fb8c6bd194) (cherry picked from commit 6177b244a55a1a92dddc6a6763137db608c6c59e) (cherry picked from commit 37a9a95e7703f612e107a617700e189abea977e5) (cherry picked from commit e5b913a042dfdf0977df6c5009f554dd9773ab8c)
-rw-r--r--doc/source/admin/drivers/irmc.rst50
-rw-r--r--driver-requirements.txt2
-rw-r--r--ironic/drivers/modules/irmc/common.py131
-rw-r--r--ironic/drivers/modules/irmc/raid.py6
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_boot.py1
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_common.py328
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_power.py4
-rw-r--r--ironic/tests/unit/drivers/modules/irmc/test_raid.py14
-rw-r--r--lower-constraints.txt1
-rw-r--r--releasenotes/notes/irmc-add-certification-file-option-34e7a0062c768e58.yaml17
-rw-r--r--requirements.txt1
11 files changed, 501 insertions, 54 deletions
diff --git a/doc/source/admin/drivers/irmc.rst b/doc/source/admin/drivers/irmc.rst
index 2788ad374..9a4291ae9 100644
--- a/doc/source/admin/drivers/irmc.rst
+++ b/doc/source/admin/drivers/irmc.rst
@@ -111,6 +111,9 @@ Here is a command example to enroll a node with ``irmc`` hardware type.
Node configuration
^^^^^^^^^^^^^^^^^^
+Configuration via ``driver_info``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
* Each node is configured for ``irmc`` hardware type by setting the following
ironic node object's properties:
@@ -126,6 +129,44 @@ Node configuration
UEFI Secure Boot is required. Please refer to `UEFI Secure Boot Support`_
for more information.
+* If ``port`` in ``[irmc]`` section of ``/etc/ironic/ironic.conf`` or
+ ``driver_info/irmc_port`` is set to 443, ``driver_info/irmc_verify_ca``
+ will take effect:
+
+ ``driver_info/irmc_verify_ca`` property takes one of 4 value (default value
+ is ``False``):
+
+ - ``True``: When set to ``True``, which certification file iRMC driver uses
+ is determined by ``requests`` Python module.
+
+ Value of ``driver_info/irmc_verify_ca`` is passed to ``verify`` argument
+ of functions defined in ``requests`` Python module. So which certification
+ will be used is depend on behavior of ``requests`` module.
+ (maybe certification provided by ``certifi`` Python module)
+
+ - ``False``: When set to ``False``, iRMC driver won't verify server
+ certification with certification file during HTTPS connection with iRMC.
+ Just stop to verify server certification, but does HTTPS.
+
+ .. warning::
+ When set to ``False``, user must notice that it can result in
+ vulnerable situation. Stopping verification of server certification
+ during HTTPS connection means it cannot prevent Man-in-the-middle
+ attack. When set to ``False``, Ironic user must take enough care
+ around infrastructure environment in terms of security.
+ (e.g. make sure network between Ironic conductor and iRMC is secure)
+
+ - string representing filesystem path to directory which contains
+ certification file: In this case, iRMC driver uses certification file
+ stored at specified directory. Ironic conductor must be able to access
+ that directory. For iRMC to recongnize certification file, Ironic user
+ must run ``openssl rehash <path_to_dir>``.
+
+ - string representing filesystem path to certification file: In this case,
+ iRMC driver uses certification file specified. Ironic conductor must have
+ access to that file.
+
+
* The following properties are also required if ``irmc-virtual-media`` boot
interface is used:
@@ -135,6 +176,10 @@ Node configuration
file name, Glance UUID, or Image Service URL. This is optional
property when ``boot_option`` is set to ``netboot``.
+
+Configuration via ``ironic.conf``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
* All of the nodes are configured by setting the following configuration
options in the ``[irmc]`` section of ``/etc/ironic/ironic.conf``:
@@ -171,6 +216,10 @@ Node configuration
- ``snmp_security``: SNMP security name required for version ``v3``.
Optional.
+
+Override ``ironic.conf`` configuration via ``driver_info``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
* Each node can be further configured by setting the following ironic
node object's properties which override the parameter values in
``[irmc]`` section of ``/etc/ironic/ironic.conf``:
@@ -184,6 +233,7 @@ Node configuration
- ``driver_info/irmc_snmp_community`` property overrides ``snmp_community``.
- ``driver_info/irmc_snmp_security`` property overrides ``snmp_security``.
+
Optional functionalities for the ``irmc`` hardware type
=======================================================
diff --git a/driver-requirements.txt b/driver-requirements.txt
index f19778719..68f7be33f 100644
--- a/driver-requirements.txt
+++ b/driver-requirements.txt
@@ -6,7 +6,7 @@
# These are available on pypi
proliantutils>=2.9.1
pysnmp>=4.3.0,<5.0.0
-python-scciclient>=0.8.0
+python-scciclient>=0.8.0,<0.10.0
python-dracclient>=3.0.0,<4.0.0
python-xclarityclient>=0.1.6
diff --git a/ironic/drivers/modules/irmc/common.py b/ironic/drivers/modules/irmc/common.py
index 9f04d74e1..d49a3c387 100644
--- a/ironic/drivers/modules/irmc/common.py
+++ b/ironic/drivers/modules/irmc/common.py
@@ -15,16 +15,22 @@
"""
Common functionalities shared between different iRMC modules.
"""
+import os
+
from oslo_log import log as logging
from oslo_utils import importutils
+from oslo_utils import strutils
+from packaging import version
import six
from ironic.common import exception
from ironic.common.i18n import _
+from ironic.common import utils
from ironic.conf import CONF
scci = importutils.try_import('scciclient.irmc.scci')
elcm = importutils.try_import('scciclient.irmc.elcm')
+scci_mod = importutils.try_import('scciclient')
LOG = logging.getLogger(__name__)
REQUIRED_PROPERTIES = {
@@ -53,9 +59,36 @@ OPTIONAL_PROPERTIES = {
'irmc_snmp_security': _("SNMP security name required for version 'v3'. "
"Optional."),
}
+OPTIONAL_DRIVER_INFO_PROPERTIES = {
+ 'irmc_verify_ca': _('Either a Boolean value, a path to a CA_BUNDLE '
+ 'file or directory with certificates of trusted '
+ 'CAs. If set to True the driver will verify the '
+ 'host certificates; if False the driver will '
+ 'ignore verifying the SSL certificate. If it\'s '
+ 'a path the driver will use the specified '
+ 'certificate or one of the certificates in the '
+ 'directory. Defaults to False. Optional'),
+}
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
+COMMON_PROPERTIES.update(OPTIONAL_DRIVER_INFO_PROPERTIES)
+
+SCCI_CERTIFICATION_SUPPORT_VERSION_RANGES = [
+ {'min': '0.8.2', 'upp': '0.9.0'},
+ {'min': '0.9.5', 'upp': '0.10.0'},
+ {'min': '0.10.1', 'upp': '0.11.0'},
+ {'min': '0.11.3', 'upp': '0.12.0'},
+ {'min': '0.12.0', 'upp': '0.13.0'}]
+
+
+def scci_support_certification():
+ scciclient_version = version.parse(scci_mod.__version__)
+ for rangev in SCCI_CERTIFICATION_SUPPORT_VERSION_RANGES:
+ if (version.parse(rangev['min']) <= scciclient_version
+ < version.parse(rangev['upp'])):
+ return True
+ return False
def parse_driver_info(node):
@@ -84,7 +117,14 @@ def parse_driver_info(node):
# corresponding config names don't have 'irmc_' prefix
opt = {param: info.get(param, CONF.irmc.get(param[len('irmc_'):]))
for param in OPTIONAL_PROPERTIES}
- d_info = dict(req, **opt)
+ opt_driver_info = {param: info.get(param)
+ for param in OPTIONAL_DRIVER_INFO_PROPERTIES}
+ d_info = {}
+ d_info.update(req)
+ d_info.update(opt)
+ d_info.update(opt_driver_info)
+ d_info['irmc_port'] = utils.validate_network_port(
+ d_info['irmc_port'], 'irmc_port')
error_msgs = []
if (d_info['irmc_auth_method'].lower() not in ('basic', 'digest')):
@@ -126,6 +166,39 @@ def parse_driver_info(node):
else:
error_msgs.append(
_("'irmc_snmp_security' has to be set for SNMP version 3."))
+
+ # To pass flake8 (C901 & E501), use complicated syntax
+ d_info['irmc_verify_ca'] = verify_ca = \
+ d_info['irmc_verify_ca'] if d_info['irmc_verify_ca'] is not None \
+ else False
+
+ # Check if verify_ca is a Boolean or a file/directory in the file-system
+ if isinstance(verify_ca, six.string_types):
+ if ((os.path.isdir(verify_ca) and os.path.isabs(verify_ca))
+ or (os.path.isfile(verify_ca) and os.path.isabs(verify_ca))):
+ # If it's fullpath and dir/file, we don't need to do anything
+ pass
+ else:
+ try:
+ d_info['irmc_verify_ca'] = strutils.bool_from_string(
+ verify_ca, strict=True)
+ except ValueError:
+ error_msgs.append(
+ _('Invalid value type set in driver_info/'
+ 'irmc_verify_ca on node %(node)s. '
+ 'The value should be a Boolean or the path '
+ 'to a file/directory, not "%(value)s"'
+ ) % {'value': verify_ca, 'node': node.uuid})
+ elif isinstance(verify_ca, bool):
+ # If it's a boolean it's grand, we don't need to do anything
+ pass
+ else:
+ error_msgs.append(
+ _('Invalid value type set in driver_info/irmc_verify_ca '
+ 'on node %(node)s. The value should be a Boolean or the path '
+ 'to a file/directory, not "%(value)s"') % {'value': verify_ca,
+ 'node': node.uuid})
+
if error_msgs:
msg = (_("The following errors were encountered while parsing "
"driver_info:\n%s") % "\n".join(error_msgs))
@@ -145,16 +218,30 @@ def get_irmc_client(node):
:raises: InvalidParameterValue on invalid inputs.
:raises: MissingParameterValue if some mandatory information
is missing on the node
+ :raises: IRMCOperationError if iRMC operation failed
"""
driver_info = parse_driver_info(node)
- scci_client = scci.get_client(
- driver_info['irmc_address'],
- driver_info['irmc_username'],
- driver_info['irmc_password'],
- port=driver_info['irmc_port'],
- auth_method=driver_info['irmc_auth_method'],
- client_timeout=driver_info['irmc_client_timeout'])
+ if scci_support_certification():
+ scci_client = scci.get_client(
+ driver_info['irmc_address'],
+ driver_info['irmc_username'],
+ driver_info['irmc_password'],
+ port=driver_info['irmc_port'],
+ auth_method=driver_info['irmc_auth_method'],
+ verify=driver_info.get('irmc_verify_ca'),
+ client_timeout=driver_info['irmc_client_timeout'])
+ else:
+ if driver_info['irmc_port'] == 443:
+ LOG.warning("Installed version of python-scciclient doesn't "
+ "support certification on HTTPS connection.")
+ scci_client = scci.get_client(
+ driver_info['irmc_address'],
+ driver_info['irmc_username'],
+ driver_info['irmc_password'],
+ port=driver_info['irmc_port'],
+ auth_method=driver_info['irmc_auth_method'],
+ client_timeout=driver_info['irmc_client_timeout'])
return scci_client
@@ -190,13 +277,27 @@ def get_irmc_report(node):
"""
driver_info = parse_driver_info(node)
- return scci.get_report(
- driver_info['irmc_address'],
- driver_info['irmc_username'],
- driver_info['irmc_password'],
- port=driver_info['irmc_port'],
- auth_method=driver_info['irmc_auth_method'],
- client_timeout=driver_info['irmc_client_timeout'])
+ if scci_support_certification():
+ report = scci.get_report(
+ driver_info['irmc_address'],
+ driver_info['irmc_username'],
+ driver_info['irmc_password'],
+ port=driver_info['irmc_port'],
+ auth_method=driver_info['irmc_auth_method'],
+ verify=driver_info.get('irmc_verify_ca'),
+ client_timeout=driver_info['irmc_client_timeout'])
+ else:
+ if driver_info['irmc_port'] == 443:
+ LOG.warning("Installed version of python-scciclient doesn't "
+ "support certification on HTTPS connection.")
+ report = scci.get_report(
+ driver_info['irmc_address'],
+ driver_info['irmc_username'],
+ driver_info['irmc_password'],
+ port=driver_info['irmc_port'],
+ auth_method=driver_info['irmc_auth_method'],
+ client_timeout=driver_info['irmc_client_timeout'])
+ return report
def set_secure_boot_mode(node, enable):
diff --git a/ironic/drivers/modules/irmc/raid.py b/ironic/drivers/modules/irmc/raid.py
index e366732a3..5a73b1eb1 100644
--- a/ironic/drivers/modules/irmc/raid.py
+++ b/ironic/drivers/modules/irmc/raid.py
@@ -82,7 +82,7 @@ def _get_raid_adapter(node):
:returns: RAID adapter dictionary, None otherwise.
:raises: IRMCOperationError on an error from python-scciclient.
"""
- irmc_info = node.driver_info
+ irmc_info = irmc_common.parse_driver_info(node)
LOG.info('iRMC driver is gathering RAID adapter info for node %s',
node.uuid)
try:
@@ -138,7 +138,7 @@ def _create_raid_adapter(node):
:raises: IRMCOperationError on an error from python-scciclient.
"""
- irmc_info = node.driver_info
+ irmc_info = irmc_common.parse_driver_info(node)
target_raid_config = node.target_raid_config
try:
@@ -165,7 +165,7 @@ def _delete_raid_adapter(node):
:raises: IRMCOperationError if SCCI failed from python-scciclient.
"""
- irmc_info = node.driver_info
+ irmc_info = irmc_common.parse_driver_info(node)
try:
client.elcm.delete_raid_configuration(irmc_info)
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_boot.py b/ironic/tests/unit/drivers/modules/irmc/test_boot.py
index e248ec9b7..857e0ce15 100644
--- a/ironic/tests/unit/drivers/modules/irmc/test_boot.py
+++ b/ironic/tests/unit/drivers/modules/irmc/test_boot.py
@@ -66,6 +66,7 @@ PARSED_IFNO = {
'irmc_snmp_version': 'v2c',
'irmc_snmp_security': None,
'irmc_sensor_method': 'ipmitool',
+ 'irmc_verify_ca': False,
}
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_common.py b/ironic/tests/unit/drivers/modules/irmc/test_common.py
index 11c0da446..d6930dc32 100644
--- a/ironic/tests/unit/drivers/modules/irmc/test_common.py
+++ b/ironic/tests/unit/drivers/modules/irmc/test_common.py
@@ -16,6 +16,8 @@
Test class for common methods used by iRMC modules.
"""
+import os
+
import mock
from oslo_config import cfg
from oslo_utils import uuidutils
@@ -67,6 +69,7 @@ class IRMCValidateParametersTestCase(BaseIRMCTest):
self.assertEqual(161, info['irmc_snmp_port'])
self.assertEqual('public', info['irmc_snmp_community'])
self.assertFalse(info['irmc_snmp_security'])
+ self.assertFalse(info['irmc_verify_ca'])
def test_parse_driver_option_default(self):
self.node.driver_info = {
@@ -80,6 +83,7 @@ class IRMCValidateParametersTestCase(BaseIRMCTest):
self.assertEqual(443, info['irmc_port'])
self.assertEqual(60, info['irmc_client_timeout'])
self.assertEqual('ipmitool', info['irmc_sensor_method'])
+ self.assertEqual(False, info['irmc_verify_ca'])
def test_parse_driver_info_missing_address(self):
del self.node.driver_info['irmc_address']
@@ -152,25 +156,176 @@ class IRMCValidateParametersTestCase(BaseIRMCTest):
self.assertRaises(exception.InvalidParameterValue,
irmc_common.parse_driver_info, self.node)
+ @mock.patch.object(os.path, 'isabs', return_value=True, autospec=True)
+ @mock.patch.object(os.path, 'isdir', return_value=True, autospec=True)
+ def test_parse_driver_info_dir_path_verify_ca(self, mock_isdir,
+ mock_isabs):
+ fake_path = 'absolute/path/to/a/valid/CA'
+ self.node.driver_info['irmc_verify_ca'] = fake_path
+ info = irmc_common.parse_driver_info(self.node)
+ self.assertEqual(fake_path, info['irmc_verify_ca'])
+ mock_isdir.assert_called_once_with(fake_path)
+ mock_isabs.assert_called_once_with(fake_path)
+
+ @mock.patch.object(os.path, 'isabs', return_value=True, autospec=True)
+ @mock.patch.object(os.path, 'isfile', return_value=True, autospec=True)
+ def test_parse_driver_info_file_path_verify_ca(self, mock_isfile,
+ mock_isabs):
+ fake_path = 'absolute/path/to/a/valid/ca.pem'
+ self.node.driver_info['irmc_verify_ca'] = fake_path
+ info = irmc_common.parse_driver_info(self.node)
+ self.assertEqual(fake_path, info['irmc_verify_ca'])
+ mock_isfile.assert_called_once_with(fake_path)
+ mock_isabs.assert_called_once_with(fake_path)
+
+ def test_parse_driver_info_string_bool_verify_ca(self):
+ self.node.driver_info['irmc_verify_ca'] = "True"
+ info = irmc_common.parse_driver_info(self.node)
+ self.assertTrue(info['irmc_verify_ca'])
+
+ def test_parse_driver_info_invalid_verify_ca(self):
+ self.node.driver_info['irmc_verify_ca'] = "1234"
+ self.assertRaises(exception.InvalidParameterValue,
+ irmc_common.parse_driver_info, self.node)
+ self.node.driver_info['irmc_verify_ca'] = 1234
+ self.assertRaises(exception.InvalidParameterValue,
+ irmc_common.parse_driver_info, self.node)
+
class IRMCCommonMethodsTestCase(BaseIRMCTest):
+ @mock.patch.object(irmc_common, 'LOG', autospec=True)
+ @mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
+ @mock.patch.object(irmc_common, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ def test_get_irmc_client_cert_support_http(self, mock_scci,
+ mock_scciclient, mock_LOG):
+ scci_version_list = ['0.8.2', '0.8.3.1', '0.9.5', '0.10.1', '0.10.2',
+ '0.11.3', '0.11.4', '0.12.0', '0.12.1']
+ for ver in scci_version_list:
+ mock_scciclient.__version__ = ver
+ self.info['irmc_port'] = 80
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ self.info['irmc_verify_ca'] = False
+ mock_scci.get_client.return_value = 'get_client'
+ returned_mock_scci_get_client = irmc_common.get_irmc_client(
+ self.node)
+ mock_scci.get_client.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ verify=self.info['irmc_verify_ca'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_client', returned_mock_scci_get_client)
+ mock_LOG.warning.assert_not_called()
+ mock_scci.reset_mock()
+ mock_LOG.warning.reset_mock()
+
+ @mock.patch.object(irmc_common, 'LOG', autospec=True)
+ @mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
@mock.patch.object(irmc_common, 'scci',
spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
- def test_get_irmc_client(self, mock_scci):
- self.info['irmc_port'] = 80
- self.info['irmc_auth_method'] = 'digest'
- self.info['irmc_client_timeout'] = 60
- mock_scci.get_client.return_value = 'get_client'
- returned_mock_scci_get_client = irmc_common.get_irmc_client(self.node)
- mock_scci.get_client.assert_called_with(
- self.info['irmc_address'],
- self.info['irmc_username'],
- self.info['irmc_password'],
- port=self.info['irmc_port'],
- auth_method=self.info['irmc_auth_method'],
- client_timeout=self.info['irmc_client_timeout'])
- self.assertEqual('get_client', returned_mock_scci_get_client)
+ def test_get_irmc_client_cert_support_https(self, mock_scci,
+ mock_scciclient, mock_LOG):
+ scci_version_list = ['0.8.2', '0.8.3.1', '0.9.5', '0.10.1', '0.10.2',
+ '0.11.3', '0.11.4', '0.12.0', '0.12.1']
+ self.node.driver_info = {
+ "irmc_address": "1.2.3.4",
+ "irmc_username": "admin0",
+ "irmc_password": "fake0",
+ "irmc_port": "443",
+ "irmc_auth_method": "digest",
+ }
+
+ for ver in scci_version_list:
+ mock_scciclient.__version__ = ver
+ self.info['irmc_port'] = 443
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ self.info['irmc_verify_ca'] = False
+ mock_scci.get_client.return_value = 'get_client'
+ returned_mock_scci_get_client = irmc_common.get_irmc_client(
+ self.node)
+ mock_scci.get_client.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ verify=self.info['irmc_verify_ca'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_client', returned_mock_scci_get_client)
+ mock_LOG.warning.assert_not_called()
+ mock_scci.reset_mock()
+ mock_LOG.warning.reset_mock()
+
+ @mock.patch.object(irmc_common, 'LOG', autospec=True)
+ @mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
+ @mock.patch.object(irmc_common, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ def test_get_irmc_client_no_cert_support_http(self, mock_scci,
+ mock_scciclient, mock_LOG):
+ scci_version_list = ['0.8.0', '0.8.1', '0.9.0', '0.9.4', '0.10.0',
+ '0.11.2']
+
+ for ver in scci_version_list:
+ mock_scciclient.__version__ = ver
+ self.info['irmc_port'] = 80
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ mock_scci.get_client.return_value = 'get_client'
+ returned_mock_scci_get_client = irmc_common.get_irmc_client(
+ self.node)
+ mock_scci.get_client.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_client', returned_mock_scci_get_client)
+ mock_LOG.warning.assert_not_called()
+ mock_scci.reset_mock()
+ mock_LOG.warning.reset_mock()
+
+ @mock.patch.object(irmc_common, 'LOG', autospec=True)
+ @mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
+ @mock.patch.object(irmc_common, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ def test_get_irmc_client_no_cert_support_https(self, mock_scci,
+ mock_scciclient, mock_LOG):
+ scci_version_list = ['0.8.0', '0.8.1', '0.9.0', '0.9.4', '0.10.0',
+ '0.11.2']
+ self.node.driver_info = {
+ "irmc_address": "1.2.3.4",
+ "irmc_username": "admin0",
+ "irmc_password": "fake0",
+ "irmc_port": "443",
+ "irmc_auth_method": "digest",
+ }
+
+ for ver in scci_version_list:
+ mock_scciclient.__version__ = ver
+ self.info['irmc_port'] = 443
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ mock_scci.get_client.return_value = 'get_client'
+ returned_mock_scci_get_client = irmc_common.get_irmc_client(
+ self.node)
+ mock_scci.get_client.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_client', returned_mock_scci_get_client)
+ mock_LOG.warning.assert_called_once()
+ mock_scci.reset_mock()
+ mock_LOG.warning.reset_mock()
def test_update_ipmi_properties(self):
with task_manager.acquire(self.context, self.node.uuid,
@@ -186,22 +341,139 @@ class IRMCCommonMethodsTestCase(BaseIRMCTest):
expected_info = dict(self.info, **ipmi_info)
self.assertEqual(expected_info, actual_info)
+ @mock.patch.object(irmc_common, 'LOG', autospec=True)
+ @mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
+ @mock.patch.object(irmc_common, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ def test_get_irmc_report_cert_support_http(self, mock_scci,
+ mock_scciclient, mock_LOG):
+ scci_version_list = ['0.8.2', '0.8.3.1', '0.9.5', '0.10.1', '0.10.2',
+ '0.11.3', '0.11.4', '0.12.0', '0.12.1']
+
+ for ver in scci_version_list:
+ mock_scciclient.__version__ = ver
+ self.info['irmc_port'] = 80
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ self.info['irmc_verify_ca'] = False
+ mock_scci.get_report.return_value = 'get_report'
+ returned_mock_scci_get_report = irmc_common.get_irmc_report(
+ self.node)
+ mock_scci.get_report.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ verify=self.info['irmc_verify_ca'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_report', returned_mock_scci_get_report)
+ mock_LOG.warning.assert_not_called()
+ mock_scci.reset_mock()
+ mock_LOG.warning.reset_mock()
+
+ @mock.patch.object(irmc_common, 'LOG', autospec=True)
+ @mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
@mock.patch.object(irmc_common, 'scci',
spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
- def test_get_irmc_report(self, mock_scci):
- self.info['irmc_port'] = 80
- self.info['irmc_auth_method'] = 'digest'
- self.info['irmc_client_timeout'] = 60
- mock_scci.get_report.return_value = 'get_report'
- returned_mock_scci_get_report = irmc_common.get_irmc_report(self.node)
- mock_scci.get_report.assert_called_with(
- self.info['irmc_address'],
- self.info['irmc_username'],
- self.info['irmc_password'],
- port=self.info['irmc_port'],
- auth_method=self.info['irmc_auth_method'],
- client_timeout=self.info['irmc_client_timeout'])
- self.assertEqual('get_report', returned_mock_scci_get_report)
+ def test_get_irmc_report_cert_support_https(self, mock_scci,
+ mock_scciclient, mock_LOG):
+ scci_version_list = ['0.8.2', '0.8.3.1', '0.9.5', '0.10.1', '0.10.2',
+ '0.11.3', '0.11.4', '0.12.0', '0.12.1']
+ self.node.driver_info = {
+ "irmc_address": "1.2.3.4",
+ "irmc_username": "admin0",
+ "irmc_password": "fake0",
+ "irmc_port": "443",
+ "irmc_auth_method": "digest",
+ }
+
+ for ver in scci_version_list:
+ mock_scciclient.__version__ = ver
+ self.info['irmc_port'] = 443
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ self.info['irmc_verify_ca'] = False
+ mock_scci.get_report.return_value = 'get_report'
+ returned_mock_scci_get_report = irmc_common.get_irmc_report(
+ self.node)
+ mock_scci.get_report.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ verify=self.info['irmc_verify_ca'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_report', returned_mock_scci_get_report)
+ mock_LOG.warning.assert_not_called()
+ mock_scci.reset_mock()
+ mock_LOG.warning.reset_mock()
+
+ @mock.patch.object(irmc_common, 'LOG', autospec=True)
+ @mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
+ @mock.patch.object(irmc_common, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ def test_get_irmc_report_no_cert_support_http(self, mock_scci,
+ mock_scciclient, mock_LOG):
+ scci_version_list = ['0.8.0', '0.8.1', '0.9.0', '0.9.4', '0.10.0',
+ '0.11.2']
+
+ for ver in scci_version_list:
+ mock_scciclient.__version__ = ver
+ self.info['irmc_port'] = 80
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ mock_scci.get_report.return_value = 'get_report'
+ returned_mock_scci_get_report = irmc_common.get_irmc_report(
+ self.node)
+ mock_scci.get_report.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_report', returned_mock_scci_get_report)
+ mock_LOG.warning.assert_not_called()
+ mock_scci.reset_mock()
+ mock_LOG.warning.reset_mock()
+
+ @mock.patch.object(irmc_common, 'LOG', autospec=True)
+ @mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
+ @mock.patch.object(irmc_common, 'scci',
+ spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
+ def test_get_irmc_report_no_cert_support_https(self, mock_scci,
+ mock_scciclient, mock_LOG):
+ scci_version_list = ['0.8.0', '0.8.1', '0.9.0', '0.9.4', '0.10.0',
+ '0.11.2']
+ self.node.driver_info = {
+ "irmc_address": "1.2.3.4",
+ "irmc_username": "admin0",
+ "irmc_password": "fake0",
+ "irmc_port": "443",
+ "irmc_auth_method": "digest",
+ }
+
+ for ver in scci_version_list:
+ mock_scciclient.__version__ = ver
+ self.info['irmc_port'] = 443
+ self.info['irmc_auth_method'] = 'digest'
+ self.info['irmc_client_timeout'] = 60
+ mock_scci.get_report.return_value = 'get_report'
+ returned_mock_scci_get_report = irmc_common.get_irmc_report(
+ self.node)
+ mock_scci.get_report.assert_called_with(
+ self.info['irmc_address'],
+ self.info['irmc_username'],
+ self.info['irmc_password'],
+ port=self.info['irmc_port'],
+ auth_method=self.info['irmc_auth_method'],
+ client_timeout=self.info['irmc_client_timeout'])
+ self.assertEqual('get_report', returned_mock_scci_get_report)
+ mock_LOG.warning.assert_called_once()
+ mock_scci.reset_mock()
+ mock_LOG.warning.reset_mock()
def test_out_range_port(self):
self.assertRaises(ValueError, cfg.CONF.set_override,
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_power.py b/ironic/tests/unit/drivers/modules/irmc/test_power.py
index 66dbb4e66..9bbfaed9e 100644
--- a/ironic/tests/unit/drivers/modules/irmc/test_power.py
+++ b/ironic/tests/unit/drivers/modules/irmc/test_power.py
@@ -204,13 +204,15 @@ class IRMCPowerInternalMethodsTestCase(test_common.BaseIRMCTest):
_wait_power_state_mock.assert_called_once_with(task, target_state,
timeout=None)
+ @mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
@mock.patch.object(irmc_power, '_wait_power_state', spec_set=True,
autospec=True)
@mock.patch.object(irmc_boot, 'attach_boot_iso_if_needed')
def test__set_power_state_invalid_target_state(
self,
attach_boot_iso_if_needed_mock,
- _wait_power_state_mock):
+ _wait_power_state_mock, mock_scciclient):
+ mock_scciclient.__version__ = '0.8.2'
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
self.assertRaises(exception.InvalidParameterValue,
diff --git a/ironic/tests/unit/drivers/modules/irmc/test_raid.py b/ironic/tests/unit/drivers/modules/irmc/test_raid.py
index 8dc2421ed..bb4a90b8e 100644
--- a/ironic/tests/unit/drivers/modules/irmc/test_raid.py
+++ b/ironic/tests/unit/drivers/modules/irmc/test_raid.py
@@ -20,6 +20,7 @@ import mock
from ironic.common import exception
from ironic.conductor import task_manager
+from ironic.drivers.modules.irmc import common as irmc_common
from ironic.drivers.modules.irmc import raid
from ironic.tests.unit.drivers.modules.irmc import test_common
@@ -574,10 +575,11 @@ class IRMCRaidConfigurationInternalMethodsTestCase(test_common.BaseIRMCTest):
disk, self.valid_disk_slots)
@mock.patch('ironic.common.raid.update_raid_info')
- @mock.patch('ironic.drivers.modules.irmc.raid.client')
- def test__commit_raid_config_with_logical_drives(self, client_mock,
- update_raid_info_mock):
- client_mock.elcm.get_raid_adapter.return_value = {
+ @mock.patch('ironic.drivers.modules.irmc.raid.client.elcm'
+ '.get_raid_adapter')
+ def test__commit_raid_config_with_logical_drives(
+ self, get_raid_adapter_mock, update_raid_info_mock):
+ get_raid_adapter_mock.return_value = {
"Server": {
"HWConfigurationIrmc": {
"Adapters": {
@@ -665,8 +667,8 @@ class IRMCRaidConfigurationInternalMethodsTestCase(test_common.BaseIRMCTest):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
raid._commit_raid_config(task)
- client_mock.elcm.get_raid_adapter.assert_called_once_with(
- task.node.driver_info)
+ irmc_info = irmc_common.parse_driver_info(task.node)
+ get_raid_adapter_mock.assert_called_once_with(irmc_info)
update_raid_info_mock.assert_called_once_with(
task.node, task.node.raid_config)
self.assertEqual(task.node.raid_config['logical_disks'],
diff --git a/lower-constraints.txt b/lower-constraints.txt
index e81276f7c..580abbe6b 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -41,6 +41,7 @@ oslo.utils==3.33.0
oslo.versionedobjects==1.31.2
oslotest==3.2.0
osprofiler==1.5.0
+packaging==16.5
pbr==2.0.0
pecan==1.0.0
psutil==3.2.2
diff --git a/releasenotes/notes/irmc-add-certification-file-option-34e7a0062c768e58.yaml b/releasenotes/notes/irmc-add-certification-file-option-34e7a0062c768e58.yaml
new file mode 100644
index 000000000..1c6333a9f
--- /dev/null
+++ b/releasenotes/notes/irmc-add-certification-file-option-34e7a0062c768e58.yaml
@@ -0,0 +1,17 @@
+---
+fixes:
+ - |
+ Adds ``driver_info/irmc_verify_ca`` option to specify certification file.
+ Default value of driver_info/irmc_verify_ca is False.
+security:
+ - |
+ Modifies the ``irmc`` hardware type to include a capability to control
+ enforcement of HTTPS certificate verification. By default this is enforced.
+ python-scciclient version must be >=0.8.2,<0.9.0 or >=0.9.5,<0.10.0
+ Or certificate verification will not occur.
+upgrade:
+ - |
+ On Train release, to use certification file on HTTPS connection,
+ iRMC driver requires python-scciclient version to be >=0.8.2,<0.9.0 or
+ >=0.9.5,<0.10.0
+ and packaging >=16.5
diff --git a/requirements.txt b/requirements.txt
index 44c4d681b..f81dba9ec 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -48,3 +48,4 @@ psutil>=3.2.2 # BSD
futurist>=1.2.0 # Apache-2.0
tooz>=1.58.0 # Apache-2.0
openstacksdk>=0.31.2 # Apache-2.0
+packaging>=16.5 # Apache-2.0