Merge "Add SNMPv3 authentication functionality" into stable/yoga
This commit is contained in:
@@ -181,6 +181,25 @@ Configuration via ``driver_info``
|
||||
``irmc_deploy_iso`` and ``irmc_boot_iso`` accordingly before the Xena
|
||||
release.
|
||||
|
||||
* The following properties are also required if ``irmc`` inspect interface is
|
||||
enabled and SNMPv3 inspection is desired.
|
||||
|
||||
- ``driver_info/irmc_snmp_user`` property to be the SNMPv3 username. SNMPv3
|
||||
functionality should be enabled for this user on iRMC server side.
|
||||
- ``driver_info/irmc_snmp_auth_password`` property to be the auth protocol
|
||||
pass phrase. The length of pass phrase should be at least 8 characters.
|
||||
- ``driver_info/irmc_snmp_priv_password`` property to be the privacy protocol
|
||||
pass phrase. The length of pass phrase should be at least 8 characters.
|
||||
|
||||
.. note::
|
||||
When using SNMPv3, python-scciclient in old version (before 0.12.2) can
|
||||
only interact with iRMC with no authentication protocol setted. This means
|
||||
the passwords and protocol settings of the snmp user in iRMC side should
|
||||
all be blank, otherwise python-scciclient will encounter an communication
|
||||
error. If you are using such old version python-scciclient, the
|
||||
``irmc_snmp_auth_password`` and ``irmc_snmp_priv_password`` properties
|
||||
will be ignored. If you want to set passwords, please update
|
||||
python-scciclient to some newer version (>= 0.12.2).
|
||||
|
||||
Configuration via ``ironic.conf``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -220,6 +239,17 @@ Configuration via ``ironic.conf``
|
||||
and ``v2c``. The default value is ``public``. Optional.
|
||||
- ``snmp_security``: SNMP security name required for version ``v3``.
|
||||
Optional.
|
||||
- ``snmp_auth_proto``: The SNMPv3 auth protocol. The valid value and the
|
||||
default value are both ``sha``. We will add more supported valid values
|
||||
in the future. Optional.
|
||||
- ``snmp_priv_proto``: The SNMPv3 privacy protocol. The valid value and
|
||||
the default value are both ``aes``. We will add more supported valid values
|
||||
in the future. Optional.
|
||||
|
||||
.. note::
|
||||
``snmp_security`` will be ignored if ``driver_info/irmc_snmp_user`` is
|
||||
set. ``snmp_auth_proto`` and ``snmp_priv_proto`` will be ignored if the
|
||||
version of python-scciclient is before 0.12.2.
|
||||
|
||||
|
||||
Override ``ironic.conf`` configuration via ``driver_info``
|
||||
@@ -237,6 +267,10 @@ Override ``ironic.conf`` configuration via ``driver_info``
|
||||
- ``driver_info/irmc_snmp_port`` property overrides ``snmp_port``.
|
||||
- ``driver_info/irmc_snmp_community`` property overrides ``snmp_community``.
|
||||
- ``driver_info/irmc_snmp_security`` property overrides ``snmp_security``.
|
||||
- ``driver_info/irmc_snmp_auth_proto`` property overrides
|
||||
``snmp_auth_proto``.
|
||||
- ``driver_info/irmc_snmp_priv_proto`` property overrides
|
||||
``snmp_priv_proto``.
|
||||
|
||||
|
||||
Optional functionalities for the ``irmc`` hardware type
|
||||
|
||||
@@ -669,3 +669,15 @@ def fast_track_enabled(node):
|
||||
except ValueError as exc:
|
||||
raise exception.InvalidParameterValue(
|
||||
_("Invalid value of fast_track: %s") % exc)
|
||||
|
||||
|
||||
def is_fips_enabled():
|
||||
"""Check if FIPS mode is enabled in the system."""
|
||||
try:
|
||||
with open('/proc/sys/crypto/fips_enabled', 'r') as f:
|
||||
content = f.read()
|
||||
if content == "1\n":
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
@@ -73,10 +73,25 @@ opts = [
|
||||
default='public',
|
||||
help=_('SNMP community. Required for versions "v1" and "v2c"')),
|
||||
cfg.StrOpt('snmp_security',
|
||||
help=_('SNMP security name. Required for version "v3"')),
|
||||
help=_("SNMP security name. Required for version 'v3'. Will be "
|
||||
"ignored if driver_info/irmc_snmp_uer is set.")),
|
||||
cfg.IntOpt('snmp_polling_interval',
|
||||
default=10,
|
||||
help='SNMP polling interval in seconds'),
|
||||
cfg.StrOpt('snmp_auth_proto',
|
||||
default='sha',
|
||||
choices=[('sha', _('Secure Hash Algorithm 1'))],
|
||||
help=_("SNMPv3 message authentication protocol ID. "
|
||||
"Required for version 'v3'. Will be ignored if the "
|
||||
"version of python-scciclient is before 0.12.2. 'sha' "
|
||||
"is supported.")),
|
||||
cfg.StrOpt('snmp_priv_proto',
|
||||
default='aes',
|
||||
choices=[('aes', _('Advanced Encryption Standard'))],
|
||||
help=_("SNMPv3 message privacy (encryption) protocol ID. "
|
||||
"Required for version 'v3'. Will be ignored if the "
|
||||
"version of python-scciclient is before 0.12.2. "
|
||||
"'aes' is supported.")),
|
||||
cfg.IntOpt('clean_priority_restore_irmc_bios_config',
|
||||
default=0,
|
||||
help=_('Priority for restore_irmc_bios_config clean step.')),
|
||||
|
||||
@@ -26,6 +26,7 @@ from ironic.common.i18n import _
|
||||
from ironic.common import utils
|
||||
from ironic.conf import CONF
|
||||
import ironic.drivers.modules.irmc.packaging_version as version
|
||||
from ironic.drivers.modules import snmp
|
||||
|
||||
scci = importutils.try_import('scciclient.irmc.scci')
|
||||
elcm = importutils.try_import('scciclient.irmc.elcm')
|
||||
@@ -49,15 +50,8 @@ OPTIONAL_PROPERTIES = {
|
||||
'irmc_sensor_method': _("Sensor data retrieval method; either "
|
||||
"'ipmitool' or 'scci'. The default value is "
|
||||
"'ipmitool'. Optional."),
|
||||
'irmc_snmp_version': _("SNMP protocol version; either 'v1', 'v2c', or "
|
||||
"'v3'. The default value is 'v2c'. Optional."),
|
||||
'irmc_snmp_port': _("SNMP port. The default is 161. Optional."),
|
||||
'irmc_snmp_community': _("SNMP community required for versions 'v1' and "
|
||||
"'v2c'. The default value is 'public'. "
|
||||
"Optional."),
|
||||
'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 '
|
||||
@@ -69,23 +63,68 @@ OPTIONAL_DRIVER_INFO_PROPERTIES = {
|
||||
'directory. Defaults to True. Optional'),
|
||||
}
|
||||
|
||||
SNMP_PROPERTIES = {
|
||||
'irmc_snmp_version': _("SNMP protocol version; either 'v1', 'v2c', or "
|
||||
"'v3'. The default value is 'v2c'. Optional."),
|
||||
'irmc_snmp_port': _("SNMP port. The default is 161. Optional."),
|
||||
'irmc_snmp_community': _("SNMP community required for versions 'v1' and "
|
||||
"'v2c'. The default value is 'public'. "
|
||||
"Optional."),
|
||||
'irmc_snmp_security': _("SNMP security name required for version 'v3'. "
|
||||
"Optional."),
|
||||
}
|
||||
|
||||
SNMP_V3_REQUIRED_PROPERTIES = {
|
||||
'irmc_snmp_user': _("SNMPv3 User-based Security Model (USM) username. "
|
||||
"Required for version 'v3’. "),
|
||||
'irmc_snmp_auth_password': _("SNMPv3 message authentication key. Must be "
|
||||
"8+ characters long. Required when message "
|
||||
"authentication is used. Will be ignored if "
|
||||
"the version of python-scciclient is before "
|
||||
"0.12.2."),
|
||||
'irmc_snmp_priv_password': _("SNMPv3 message privacy key. Must be 8+ "
|
||||
"characters long. Required when message "
|
||||
"privacy is used. Will be ignored if the "
|
||||
"version of python-scciclient is before "
|
||||
"0.12.2."),
|
||||
}
|
||||
|
||||
SNMP_V3_OPTIONAL_PROPERTIES = {
|
||||
'irmc_snmp_auth_proto': _("SNMPv3 message authentication protocol ID. "
|
||||
"Required for version 'v3'. Will be ignored if "
|
||||
"the version of python-scciclient is before "
|
||||
"0.12.2. 'sha' is supported."),
|
||||
'irmc_snmp_priv_proto': _("SNMPv3 message privacy (encryption) protocol "
|
||||
"ID. Required for version 'v3'. Will be ignored "
|
||||
"if the version of python-scciclient is before "
|
||||
"0.12.2. 'aes' is supported."),
|
||||
}
|
||||
|
||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
||||
COMMON_PROPERTIES.update(OPTIONAL_DRIVER_INFO_PROPERTIES)
|
||||
COMMON_PROPERTIES.update(SNMP_PROPERTIES)
|
||||
COMMON_PROPERTIES.update(SNMP_V3_REQUIRED_PROPERTIES)
|
||||
COMMON_PROPERTIES.update(SNMP_V3_OPTIONAL_PROPERTIES)
|
||||
|
||||
SCCI_CERTIFICATION_SUPPORT_VERSION_RANGES = [
|
||||
{'min': '0.8.2', 'upp': '0.9.0'},
|
||||
{'min': '0.9.4', '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'}]
|
||||
{'min': '0.8.2', 'max': '0.9.0'},
|
||||
{'min': '0.9.4', 'max': '0.10.0'},
|
||||
{'min': '0.10.1', 'max': '0.11.0'},
|
||||
{'min': '0.11.3', 'max': '0.12.0'},
|
||||
{'min': '0.12.0', 'max': '0.13.0'}]
|
||||
|
||||
SCCI_SNMPv3_AUTHENTICATION_SUPPORT_VERSION_RANGES = [
|
||||
{'min': '0.10.1', 'max': '0.11.0'},
|
||||
{'min': '0.11.3', 'max': '0.12.0'},
|
||||
{'min': '0.12.2', 'max': '0.13.0'}]
|
||||
|
||||
|
||||
def scci_support_certification():
|
||||
def _scci_version_in(version_ranges):
|
||||
scciclient_version = version.parse(scci_mod.__version__)
|
||||
for rangev in SCCI_CERTIFICATION_SUPPORT_VERSION_RANGES:
|
||||
for rangev in version_ranges:
|
||||
if (version.parse(rangev['min']) <= scciclient_version
|
||||
< version.parse(rangev['upp'])):
|
||||
< version.parse(rangev['max'])):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -139,29 +178,6 @@ def parse_driver_info(node):
|
||||
error_msgs.append(
|
||||
_("Value '%s' is not supported for 'irmc_sensor_method'.") %
|
||||
d_info['irmc_sensor_method'])
|
||||
if d_info['irmc_snmp_version'].lower() not in ('v1', 'v2c', 'v3'):
|
||||
error_msgs.append(
|
||||
_("Value '%s' is not supported for 'irmc_snmp_version'.") %
|
||||
d_info['irmc_snmp_version'])
|
||||
if not isinstance(d_info['irmc_snmp_port'], int):
|
||||
error_msgs.append(
|
||||
_("Value '%s' is not an integer for 'irmc_snmp_port'") %
|
||||
d_info['irmc_snmp_port'])
|
||||
if (d_info['irmc_snmp_version'].lower() in ('v1', 'v2c')
|
||||
and d_info['irmc_snmp_community']
|
||||
and not isinstance(d_info['irmc_snmp_community'], str)):
|
||||
error_msgs.append(
|
||||
_("Value '%s' is not a string for 'irmc_snmp_community'") %
|
||||
d_info['irmc_snmp_community'])
|
||||
if d_info['irmc_snmp_version'].lower() == 'v3':
|
||||
if d_info['irmc_snmp_security']:
|
||||
if not isinstance(d_info['irmc_snmp_security'], str):
|
||||
error_msgs.append(
|
||||
_("Value '%s' is not a string for "
|
||||
"'irmc_snmp_security'") % d_info['irmc_snmp_security'])
|
||||
else:
|
||||
error_msgs.append(
|
||||
_("'irmc_snmp_security' has to be set for SNMP version 3."))
|
||||
|
||||
verify_ca = d_info.get('irmc_verify_ca')
|
||||
if verify_ca is None:
|
||||
@@ -199,9 +215,143 @@ def parse_driver_info(node):
|
||||
"driver_info:\n%s") % "\n".join(error_msgs))
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
|
||||
d_info.update(_parse_snmp_driver_info(node, info))
|
||||
|
||||
return d_info
|
||||
|
||||
|
||||
def _parse_snmp_driver_info(node, info):
|
||||
"""Parses the SNMP related driver_info parameters.
|
||||
|
||||
:param node: An Ironic node object.
|
||||
:param info: driver_info dictionary.
|
||||
:returns: A dictionary containing SNMP information.
|
||||
:raises: MissingParameterValue if any of the mandatory
|
||||
parameter values are not provided.
|
||||
:raises: InvalidParameterValue if there is any invalid
|
||||
value provided.
|
||||
"""
|
||||
snmp_info = {param: info.get(param, CONF.irmc.get(param[len('irmc_'):]))
|
||||
for param in SNMP_PROPERTIES}
|
||||
valid_versions = {"v1": snmp.SNMP_V1,
|
||||
"v2c": snmp.SNMP_V2C,
|
||||
"v3": snmp.SNMP_V3}
|
||||
|
||||
if snmp_info['irmc_snmp_version'].lower() not in valid_versions:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Value '%s' is not supported for 'irmc_snmp_version'.") %
|
||||
snmp_info['irmc_snmp_version']
|
||||
)
|
||||
snmp_info["irmc_snmp_version"] = \
|
||||
valid_versions[snmp_info["irmc_snmp_version"].lower()]
|
||||
|
||||
snmp_info['irmc_snmp_port'] = utils.validate_network_port(
|
||||
snmp_info['irmc_snmp_port'], 'irmc_snmp_port')
|
||||
|
||||
if snmp_info['irmc_snmp_version'] != snmp.SNMP_V3:
|
||||
if (snmp_info['irmc_snmp_community']
|
||||
and not isinstance(snmp_info['irmc_snmp_community'], str)):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Value '%s' is not a string for 'irmc_snmp_community'") %
|
||||
snmp_info['irmc_snmp_community'])
|
||||
if utils.is_fips_enabled():
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"'v3' has to be set for 'irmc_snmp_version' "
|
||||
"when FIPS mode is enabled."))
|
||||
|
||||
else:
|
||||
# Parse snmp user info
|
||||
if 'irmc_snmp_user' in info:
|
||||
if not isinstance(info['irmc_snmp_user'], str):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Value %s is not a string for 'irmc_snmp_user'.") %
|
||||
info['irmc_snmp_user'])
|
||||
snmp_info['irmc_snmp_user'] = info['irmc_snmp_user']
|
||||
if snmp_info['irmc_snmp_security']:
|
||||
LOG.warning(_("'irmc_snmp_security' is ignored in favor of "
|
||||
"'irmc_snmp_user'. Please remove "
|
||||
"'irmc_snmp_security' from node %s "
|
||||
"configuration."), node.uuid)
|
||||
else:
|
||||
if not snmp_info['irmc_snmp_security']:
|
||||
raise exception.MissingParameterValue(_(
|
||||
"'irmc_snmp_user' should be set when using SNMPv3."))
|
||||
if not isinstance(snmp_info['irmc_snmp_security'], str):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Value %s is not a string for 'irmc_snmp_security'.") %
|
||||
snmp_info['irmc_snmp_security'])
|
||||
snmp_info['irmc_snmp_user'] = snmp_info['irmc_snmp_security']
|
||||
|
||||
if _scci_version_in(SCCI_SNMPv3_AUTHENTICATION_SUPPORT_VERSION_RANGES):
|
||||
snmp_info.update(_parse_snmp_v3_crypto_info(info))
|
||||
else:
|
||||
# For compatible with old version of python-scciclient
|
||||
snmp_info['irmc_snmp_security'] = snmp_info['irmc_snmp_user']
|
||||
if 'irmc_snmp_auth_password' in info or \
|
||||
'irmc_snmp_priv_password' in info:
|
||||
LOG.warning(_("'irmc_snmp_auth_password' and "
|
||||
"'irmc_snmp_priv_password' in node %(node)s "
|
||||
"configuration are ignored. "
|
||||
"Python-scciclient version %(version)s can only "
|
||||
"communicate with iRMC with no authentication "
|
||||
"protocol setted. This means the authentication "
|
||||
"protocol, private protocol and password of the "
|
||||
"server's SNMPv3 user should all be blank, "
|
||||
"otherwise python-scciclient will encounter an "
|
||||
"authentication error. If you want to set "
|
||||
"password, please update python-scciclient to "
|
||||
"a newer version (>=0.12.2, <0.13.0)."),
|
||||
{'node': node.uuid,
|
||||
'version': scci_mod.__version__})
|
||||
|
||||
return snmp_info
|
||||
|
||||
|
||||
def _parse_snmp_v3_crypto_info(info):
|
||||
snmp_info = {}
|
||||
valid_values = {'irmc_snmp_auth_proto': ['sha'],
|
||||
'irmc_snmp_priv_proto': ['aes']}
|
||||
valid_protocols = {'irmc_snmp_auth_proto': snmp.snmp_auth_protocols,
|
||||
'irmc_snmp_priv_proto': snmp.snmp_priv_protocols}
|
||||
snmp_keys = {'irmc_snmp_auth_password', 'irmc_snmp_priv_password'}
|
||||
|
||||
for param in snmp_keys:
|
||||
try:
|
||||
snmp_info[param] = info[param]
|
||||
except KeyError:
|
||||
raise exception.MissingParameterValue(_(
|
||||
"%s should be set when using SNMPv3.") % param)
|
||||
|
||||
if not isinstance(snmp_info[param], str):
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"The value of %s is not a string.") % param)
|
||||
if len(snmp_info[param]) < 8:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"%s is too short. (8+ chars required)") % param)
|
||||
|
||||
for param in SNMP_V3_OPTIONAL_PROPERTIES:
|
||||
value = None
|
||||
try:
|
||||
value = info[param]
|
||||
if value not in valid_values[param]:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Invalid value %(value)s given for driver info parameter "
|
||||
"%(param)s, the valid values are %(valid_values)s.") %
|
||||
{'param': param,
|
||||
'value': value,
|
||||
'valid_values': valid_values[param]})
|
||||
except KeyError:
|
||||
value = CONF.irmc.get(param[len('irmc_'):])
|
||||
snmp_info[param] = valid_protocols[param].get(value)
|
||||
if not snmp_info[param]:
|
||||
raise exception.InvalidParameterValue(_(
|
||||
"Unknown SNMPv3 protocol %(value)s given for "
|
||||
"driver info parameter %(param)s") % {'param': param,
|
||||
'value': value})
|
||||
|
||||
return snmp_info
|
||||
|
||||
|
||||
def get_irmc_client(node):
|
||||
"""Gets an iRMC SCCI client.
|
||||
|
||||
@@ -217,7 +367,7 @@ def get_irmc_client(node):
|
||||
"""
|
||||
driver_info = parse_driver_info(node)
|
||||
|
||||
if scci_support_certification():
|
||||
if _scci_version_in(SCCI_CERTIFICATION_SUPPORT_VERSION_RANGES):
|
||||
scci_client = scci.get_client(
|
||||
driver_info['irmc_address'],
|
||||
driver_info['irmc_username'],
|
||||
@@ -272,7 +422,7 @@ def get_irmc_report(node):
|
||||
"""
|
||||
driver_info = parse_driver_info(node)
|
||||
|
||||
if scci_support_certification():
|
||||
if _scci_version_in(SCCI_CERTIFICATION_SUPPORT_VERSION_RANGES):
|
||||
report = scci.get_report(
|
||||
driver_info['irmc_address'],
|
||||
driver_info['irmc_username'],
|
||||
|
||||
@@ -103,11 +103,16 @@ def _get_mac_addresses(node):
|
||||
:returns: a list of mac addresses.
|
||||
"""
|
||||
d_info = irmc_common.parse_driver_info(node)
|
||||
snmp_client = snmp.SNMPClient(d_info['irmc_address'],
|
||||
d_info['irmc_snmp_port'],
|
||||
d_info['irmc_snmp_version'],
|
||||
d_info['irmc_snmp_community'],
|
||||
d_info['irmc_snmp_security'])
|
||||
snmp_client = snmp.SNMPClient(
|
||||
address=d_info['irmc_address'],
|
||||
port=d_info['irmc_snmp_port'],
|
||||
version=d_info['irmc_snmp_version'],
|
||||
read_community=d_info['irmc_snmp_community'],
|
||||
user=d_info.get('irmc_snmp_user'),
|
||||
auth_proto=d_info.get('irmc_snmp_auth_proto'),
|
||||
auth_key=d_info.get('irmc_snmp_auth_password'),
|
||||
priv_proto=d_info.get('irmc_snmp_priv_proto'),
|
||||
priv_key=d_info.get('irmc_snmp_priv_password'))
|
||||
|
||||
node_classes = snmp_client.get_next(NODE_CLASS_OID)
|
||||
mac_addresses = [':'.join(['%02x' % x for x in mac])
|
||||
|
||||
@@ -93,11 +93,16 @@ def _wait_power_state(task, target_state, timeout=None):
|
||||
"""
|
||||
node = task.node
|
||||
d_info = irmc_common.parse_driver_info(node)
|
||||
snmp_client = snmp.SNMPClient(d_info['irmc_address'],
|
||||
d_info['irmc_snmp_port'],
|
||||
d_info['irmc_snmp_version'],
|
||||
d_info['irmc_snmp_community'],
|
||||
d_info['irmc_snmp_security'])
|
||||
snmp_client = snmp.SNMPClient(
|
||||
address=d_info['irmc_address'],
|
||||
port=d_info['irmc_snmp_port'],
|
||||
version=d_info['irmc_snmp_version'],
|
||||
read_community=d_info['irmc_snmp_community'],
|
||||
user=d_info.get('irmc_snmp_user'),
|
||||
auth_proto=d_info.get('irmc_snmp_auth_proto'),
|
||||
auth_key=d_info.get('irmc_snmp_auth_password'),
|
||||
priv_proto=d_info.get('irmc_snmp_priv_proto'),
|
||||
priv_key=d_info.get('irmc_snmp_priv_password'))
|
||||
|
||||
interval = CONF.irmc.snmp_polling_interval
|
||||
retry_timeout_soft = timeout or CONF.conductor.soft_power_off_timeout
|
||||
|
||||
@@ -306,6 +306,21 @@ class GenericUtilsTestCase(base.TestCase):
|
||||
utils.is_valid_no_proxy(no_proxy),
|
||||
msg="'no_proxy' value should be invalid: {}".format(no_proxy))
|
||||
|
||||
def test_is_fips_enabled(self):
|
||||
with mock.patch('builtins.open', mock.mock_open(read_data='1\n')) as m:
|
||||
self.assertTrue(utils.is_fips_enabled())
|
||||
m.assert_called_once_with('/proc/sys/crypto/fips_enabled', 'r')
|
||||
|
||||
with mock.patch('builtins.open', mock.mock_open(read_data='0\n')) as m:
|
||||
self.assertFalse(utils.is_fips_enabled())
|
||||
m.assert_called_once_with('/proc/sys/crypto/fips_enabled', 'r')
|
||||
|
||||
mock_open = mock.mock_open()
|
||||
mock_open.side_effect = FileNotFoundError
|
||||
with mock.patch('builtins.open', mock_open) as m:
|
||||
self.assertFalse(utils.is_fips_enabled())
|
||||
m.assert_called_once_with('/proc/sys/crypto/fips_enabled', 'r')
|
||||
|
||||
|
||||
class TempFilesTestCase(base.TestCase):
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ from ironic.drivers.modules.irmc import common as irmc_common
|
||||
from ironic.drivers.modules.irmc import management as irmc_management
|
||||
from ironic.drivers.modules import pxe
|
||||
from ironic.drivers.modules import pxe_base
|
||||
from ironic.drivers.modules import snmp
|
||||
from ironic.tests import base
|
||||
from ironic.tests.unit.db import utils as db_utils
|
||||
from ironic.tests.unit.drivers.modules.irmc import test_common
|
||||
@@ -60,10 +61,10 @@ PARSED_IFNO = {
|
||||
'irmc_client_timeout': 60,
|
||||
'irmc_snmp_community': 'public',
|
||||
'irmc_snmp_port': 161,
|
||||
'irmc_snmp_version': 'v2c',
|
||||
'irmc_snmp_security': None,
|
||||
'irmc_snmp_version': snmp.SNMP_V2C,
|
||||
'irmc_sensor_method': 'ipmitool',
|
||||
'irmc_verify_ca': True,
|
||||
'irmc_snmp_security': None,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,8 +23,10 @@ from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules.irmc import common as irmc_common
|
||||
from ironic.drivers.modules import snmp
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
from ironic.tests.unit.db import utils as db_utils
|
||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
||||
@@ -55,7 +57,9 @@ class BaseIRMCTest(db_base.DbTestCase):
|
||||
|
||||
class IRMCValidateParametersTestCase(BaseIRMCTest):
|
||||
|
||||
def test_parse_driver_info(self):
|
||||
@mock.patch.object(utils, 'is_fips_enabled',
|
||||
return_value=False, autospec=True)
|
||||
def test_parse_driver_info(self, mock_check_fips):
|
||||
info = irmc_common.parse_driver_info(self.node)
|
||||
|
||||
self.assertEqual('1.2.3.4', info['irmc_address'])
|
||||
@@ -65,13 +69,81 @@ class IRMCValidateParametersTestCase(BaseIRMCTest):
|
||||
self.assertEqual(80, info['irmc_port'])
|
||||
self.assertEqual('digest', info['irmc_auth_method'])
|
||||
self.assertEqual('ipmitool', info['irmc_sensor_method'])
|
||||
self.assertEqual('v2c', info['irmc_snmp_version'])
|
||||
self.assertEqual(snmp.SNMP_V2C, info['irmc_snmp_version'])
|
||||
self.assertEqual(161, info['irmc_snmp_port'])
|
||||
self.assertEqual('public', info['irmc_snmp_community'])
|
||||
self.assertFalse(info['irmc_snmp_security'])
|
||||
self.assertTrue(info['irmc_verify_ca'])
|
||||
|
||||
def test_parse_driver_option_default(self):
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_snmpv3_support_auth(self, mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
|
||||
scci_version_list = ['0.10.1', '0.11.3', '0.12.2']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
info = irmc_common.parse_driver_info(self.node)
|
||||
|
||||
self.assertEqual('1.2.3.4', info['irmc_address'])
|
||||
self.assertEqual('admin0', info['irmc_username'])
|
||||
self.assertEqual('fake0', info['irmc_password'])
|
||||
self.assertEqual(60, info['irmc_client_timeout'])
|
||||
self.assertEqual(80, info['irmc_port'])
|
||||
self.assertEqual('digest', info['irmc_auth_method'])
|
||||
self.assertEqual('ipmitool', info['irmc_sensor_method'])
|
||||
self.assertEqual(snmp.SNMP_V3, info['irmc_snmp_version'])
|
||||
self.assertEqual(161, info['irmc_snmp_port'])
|
||||
self.assertEqual('public', info['irmc_snmp_community'])
|
||||
self.assertEqual('admin0', info['irmc_snmp_user'])
|
||||
self.assertEqual(snmp.snmp_auth_protocols['sha'],
|
||||
info['irmc_snmp_auth_proto'])
|
||||
self.assertEqual('valid_key', info['irmc_snmp_auth_password'])
|
||||
self.assertEqual(snmp.snmp_priv_protocols['aes'],
|
||||
info['irmc_snmp_priv_proto'])
|
||||
self.assertEqual('valid_key', info['irmc_snmp_priv_password'])
|
||||
|
||||
@mock.patch.object(irmc_common, 'LOG', autospec=True)
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_snmpv3_not_support_auth(self, mock_scci_module,
|
||||
mock_LOG):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
|
||||
scci_version_list = ['0.10.0', '0.11.0', '0.11.2',
|
||||
'0.12.0', '0.12.1', '0.13.0']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
info = irmc_common.parse_driver_info(self.node)
|
||||
|
||||
self.assertEqual('1.2.3.4', info['irmc_address'])
|
||||
self.assertEqual('admin0', info['irmc_username'])
|
||||
self.assertEqual('fake0', info['irmc_password'])
|
||||
self.assertEqual(60, info['irmc_client_timeout'])
|
||||
self.assertEqual(80, info['irmc_port'])
|
||||
self.assertEqual('digest', info['irmc_auth_method'])
|
||||
self.assertEqual('ipmitool', info['irmc_sensor_method'])
|
||||
self.assertEqual(snmp.SNMP_V3, info['irmc_snmp_version'])
|
||||
self.assertEqual(161, info['irmc_snmp_port'])
|
||||
self.assertEqual('public', info['irmc_snmp_community'])
|
||||
self.assertEqual('admin0', info['irmc_snmp_user'])
|
||||
self.assertEqual('admin0', info['irmc_snmp_security'])
|
||||
self.assertNotIn('irmc_snmp_auth_proto', info)
|
||||
self.assertNotIn('irmc_snmp_auth_password', info)
|
||||
self.assertNotIn('irmc_snmp_priv_proto', info)
|
||||
self.assertNotIn('irmc_snmp_priv_password', info)
|
||||
mock_LOG.warning.assert_called_once()
|
||||
mock_LOG.warning.reset_mock()
|
||||
|
||||
@mock.patch.object(utils, 'is_fips_enabled',
|
||||
return_value=False, autospec=True)
|
||||
def test_parse_driver_option_default(self, mock_check_fips):
|
||||
self.node.driver_info = {
|
||||
"irmc_address": "1.2.3.4",
|
||||
"irmc_username": "admin0",
|
||||
@@ -133,8 +205,16 @@ class IRMCValidateParametersTestCase(BaseIRMCTest):
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(utils, 'is_fips_enabled',
|
||||
return_value=True, autospec=True)
|
||||
def test_parse_driver_info_invalid_snmp_version_fips(self,
|
||||
mock_check_fips):
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
self.assertEqual(1, mock_check_fips.call_count)
|
||||
|
||||
def test_parse_driver_info_invalid_snmp_port(self):
|
||||
self.node.driver_info['irmc_snmp_port'] = '161'
|
||||
self.node.driver_info['irmc_snmp_port'] = '161p'
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@@ -144,18 +224,164 @@ class IRMCValidateParametersTestCase(BaseIRMCTest):
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
def test_parse_driver_info_missing_snmp_user(self):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_missing_snmp_auth_password(self,
|
||||
mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
scci_version_list = ['0.10.1', '0.11.3', '0.12.2']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_missing_snmp_priv_password(self,
|
||||
mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
scci_version_list = ['0.10.1', '0.11.3', '0.12.2']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
self.assertRaises(exception.MissingParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'LOG', autospec=True)
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_ignoring_snmp_security(self, mock_scci_module,
|
||||
mock_LOG):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_security'] = 'security'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
mock_scci_module.__version__ = '0.12.2'
|
||||
info = irmc_common.parse_driver_info(self.node)
|
||||
self.assertEqual('admin0', info['irmc_snmp_user'])
|
||||
mock_LOG.warning.assert_called_once()
|
||||
mock_LOG.warning.reset_mock
|
||||
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_using_snmp_security_(self, mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_security'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
mock_scci_module.__version__ = '0.12.2'
|
||||
info = irmc_common.parse_driver_info(self.node)
|
||||
self.assertEqual('admin0', info['irmc_snmp_user'])
|
||||
|
||||
def test_parse_driver_info_invalid_snmp_security(self):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_security'] = 100
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
def test_parse_driver_info_empty_snmp_security(self):
|
||||
def test_parse_driver_info_invalid_snmp_user(self):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_security'] = ''
|
||||
self.node.driver_info['irmc_snmp_user'] = 100
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_invalid_snmp_auth_password(self,
|
||||
mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 100
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
scci_version_list = ['0.10.1', '0.11.3', '0.12.2']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_short_snmp_auth_password(self,
|
||||
mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'short'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
scci_version_list = ['0.10.1', '0.11.3', '0.12.2']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_invalid_snmp_priv_password(self,
|
||||
mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 100
|
||||
scci_version_list = ['0.10.1', '0.11.3', '0.12.2']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_short_snmp_priv_password(self,
|
||||
mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'short'
|
||||
scci_version_list = ['0.10.1', '0.11.3', '0.12.2']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_invalid_snmp_auth_proto(self, mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_auth_proto'] = 'invalid'
|
||||
scci_version_list = ['0.10.1', '0.11.3', '0.12.2']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
irmc_common.parse_driver_info, self.node)
|
||||
|
||||
@mock.patch.object(irmc_common, 'scci_mod', spec_set=['__version__'])
|
||||
def test_parse_driver_info_invalid_snmp_priv_proto(self, mock_scci_module):
|
||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||
self.node.driver_info['irmc_snmp_user'] = 'admin0'
|
||||
self.node.driver_info['irmc_snmp_auth_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_password'] = 'valid_key'
|
||||
self.node.driver_info['irmc_snmp_priv_proto'] = 'invalid'
|
||||
scci_version_list = ['0.10.1', '0.11.3', '0.12.2']
|
||||
for ver in scci_version_list:
|
||||
with self.subTest(ver=ver):
|
||||
mock_scci_module.__version__ = ver
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Fixes SNMPv3 message authentication and encryption functionality of iRMC
|
||||
driver. The SNMPv3 authentication between iRMC driver and iRMC was only
|
||||
by the security name with no passwords and encryption.
|
||||
To increase security, the following parameters are now added to the node's
|
||||
``driver_info``, and can be used for authentication:
|
||||
|
||||
* ``irmc_snmp_user``
|
||||
* ``irmc_snmp_auth_password``
|
||||
* ``irmc_snmp_priv_password``
|
||||
* ``irmc_snmp_auth_proto`` (Optional, defaults to ``sha``)
|
||||
* ``irmc_snmp_priv_proto`` (Optional, defaults to ``aes``)
|
||||
|
||||
``irmc_snmp_user`` replaces ``irmc_snmp_security``. ``irmc_snmp_security``
|
||||
will be ignored if ``irmc_snmp_user`` is set.
|
||||
``irmc_snmp_auth_proto`` and ``irmc_snmp_priv_proto`` can also be set
|
||||
through the following options in the ``[irmc]`` section of
|
||||
``/etc/ironic/ironic.conf``:
|
||||
|
||||
* ``snmp_auth_proto``
|
||||
* ``snmp_priv_proto``
|
||||
|
||||
other:
|
||||
- |
|
||||
Updates the minimum version of ``python-scciclient`` library to
|
||||
``0.12.2``.
|
||||
Reference in New Issue
Block a user