[iRMC] Add SNMPv3 authentication functionality
Currently when using SNMPv3, iRMC driver does not use SNMPv3 authentication parameters so the SNMPv3 authentication will always fail. And iRMC cannot recognize FIPS mode, so when FIPS mode is enabled, iRMC driver could still use non-FIPS-compliant algorithms. This commit changes iRMC driver to require and use SNMPv3 authentication parameters when 'irmc_snmp_version' is set to v3 and also makes iRMC driver to force 'irmc_snmp_version' to v3, 'irmc_snmp_auth_proto' to SHA and 'irmc_snmp_priv_proto' to AES when FIPS mode is enabled, because currently among the algorithms supported by iRMC, only SHA and AES are FIPS compliant. Change-Id: Id6f8996e4d103f849325f54fe0619b4acb43453a Story: 2010085 Task: 45590
This commit is contained in:
parent
0406fa7531
commit
79f82c0262
@ -140,6 +140,16 @@ Node configuration
|
|||||||
``irmc_deploy_iso`` and ``irmc_boot_iso`` accordingly before the Xena
|
``irmc_deploy_iso`` and ``irmc_boot_iso`` accordingly before the Xena
|
||||||
release.
|
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.
|
||||||
|
|
||||||
* All of the nodes are configured by setting the following configuration
|
* All of the nodes are configured by setting the following configuration
|
||||||
options in the ``[irmc]`` section of ``/etc/ironic/ironic.conf``:
|
options in the ``[irmc]`` section of ``/etc/ironic/ironic.conf``:
|
||||||
|
|
||||||
@ -175,6 +185,18 @@ Node configuration
|
|||||||
and ``v2c``. The default value is ``public``. Optional.
|
and ``v2c``. The default value is ``public``. Optional.
|
||||||
- ``snmp_security``: SNMP security name required for version ``v3``.
|
- ``snmp_security``: SNMP security name required for version ``v3``.
|
||||||
Optional.
|
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.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
We deprecated the ``snmp_security`` option when use SNMPv3 inspection.
|
||||||
|
Support for this option will be removed in the future. Instead, set
|
||||||
|
``driver_info/irmc_snmp_user`` parameter for each node if SNMPv3
|
||||||
|
inspection is needed.
|
||||||
|
|
||||||
* Each node can be further configured by setting the following ironic
|
* Each node can be further configured by setting the following ironic
|
||||||
node object's properties which override the parameter values in
|
node object's properties which override the parameter values in
|
||||||
@ -188,6 +210,10 @@ Node configuration
|
|||||||
- ``driver_info/irmc_snmp_port`` property overrides ``snmp_port``.
|
- ``driver_info/irmc_snmp_port`` property overrides ``snmp_port``.
|
||||||
- ``driver_info/irmc_snmp_community`` property overrides ``snmp_community``.
|
- ``driver_info/irmc_snmp_community`` property overrides ``snmp_community``.
|
||||||
- ``driver_info/irmc_snmp_security`` property overrides ``snmp_security``.
|
- ``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
|
Optional functionalities for the ``irmc`` hardware type
|
||||||
=======================================================
|
=======================================================
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
# These are available on pypi
|
# These are available on pypi
|
||||||
proliantutils>=2.13.0
|
proliantutils>=2.13.0
|
||||||
pysnmp>=4.3.0,<5.0.0
|
pysnmp>=4.3.0,<5.0.0
|
||||||
python-scciclient>=0.8.0
|
python-scciclient>=0.12.2
|
||||||
python-dracclient>=5.1.0,<9.0.0
|
python-dracclient>=5.1.0,<9.0.0
|
||||||
python-xclarityclient>=0.1.6
|
python-xclarityclient>=0.1.6
|
||||||
|
|
||||||
|
@ -669,3 +669,15 @@ def fast_track_enabled(node):
|
|||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise exception.InvalidParameterValue(
|
raise exception.InvalidParameterValue(
|
||||||
_("Invalid value of fast_track: %s") % exc)
|
_("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,22 @@ opts = [
|
|||||||
default='public',
|
default='public',
|
||||||
help=_('SNMP community. Required for versions "v1" and "v2c"')),
|
help=_('SNMP community. Required for versions "v1" and "v2c"')),
|
||||||
cfg.StrOpt('snmp_security',
|
cfg.StrOpt('snmp_security',
|
||||||
help=_('SNMP security name. Required for version "v3"')),
|
help=_("SNMP security name. Required for version 'v3'."),
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason=_("Use irmc_snmp_user")),
|
||||||
cfg.IntOpt('snmp_polling_interval',
|
cfg.IntOpt('snmp_polling_interval',
|
||||||
default=10,
|
default=10,
|
||||||
help='SNMP polling interval in seconds'),
|
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'. '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'. 'aes' is supported.")),
|
||||||
cfg.IntOpt('clean_priority_restore_irmc_bios_config',
|
cfg.IntOpt('clean_priority_restore_irmc_bios_config',
|
||||||
default=0,
|
default=0,
|
||||||
help=_('Priority for restore_irmc_bios_config clean step.')),
|
help=_('Priority for restore_irmc_bios_config clean step.')),
|
||||||
|
@ -22,6 +22,7 @@ from ironic.common import exception
|
|||||||
from ironic.common.i18n import _
|
from ironic.common.i18n import _
|
||||||
from ironic.common import utils
|
from ironic.common import utils
|
||||||
from ironic.conf import CONF
|
from ironic.conf import CONF
|
||||||
|
from ironic.drivers.modules import snmp
|
||||||
|
|
||||||
scci = importutils.try_import('scciclient.irmc.scci')
|
scci = importutils.try_import('scciclient.irmc.scci')
|
||||||
elcm = importutils.try_import('scciclient.irmc.elcm')
|
elcm = importutils.try_import('scciclient.irmc.elcm')
|
||||||
@ -44,18 +45,49 @@ OPTIONAL_PROPERTIES = {
|
|||||||
'irmc_sensor_method': _("Sensor data retrieval method; either "
|
'irmc_sensor_method': _("Sensor data retrieval method; either "
|
||||||
"'ipmitool' or 'scci'. The default value is "
|
"'ipmitool' or 'scci'. The default value is "
|
||||||
"'ipmitool'. Optional."),
|
"'ipmitool'. Optional."),
|
||||||
|
}
|
||||||
|
|
||||||
|
SNMP_PROPERTIES = {
|
||||||
'irmc_snmp_version': _("SNMP protocol version; either 'v1', 'v2c', or "
|
'irmc_snmp_version': _("SNMP protocol version; either 'v1', 'v2c', or "
|
||||||
"'v3'. The default value is 'v2c'. Optional."),
|
"'v3'. The default value is 'v2c'. Optional."),
|
||||||
'irmc_snmp_port': _("SNMP port. The default is 161. Optional."),
|
'irmc_snmp_port': _("SNMP port. The default is 161. Optional."),
|
||||||
'irmc_snmp_community': _("SNMP community required for versions 'v1' and "
|
'irmc_snmp_community': _("SNMP community required for versions 'v1' and "
|
||||||
"'v2c'. The default value is 'public'. "
|
"'v2c'. The default value is 'public'. "
|
||||||
"Optional."),
|
"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."),
|
||||||
|
'irmc_snmp_priv_password': _("SNMPv3 message privacy key. Must be 8+ "
|
||||||
|
"characters long. Required when message "
|
||||||
|
"privacy is used."),
|
||||||
|
}
|
||||||
|
|
||||||
|
SNMP_V3_OPTIONAL_PROPERTIES = {
|
||||||
|
'irmc_snmp_auth_proto': _("SNMPv3 message authentication protocol ID. "
|
||||||
|
"Required for version 'v3'. "
|
||||||
|
"'sha' is supported."),
|
||||||
|
'irmc_snmp_priv_proto': _("SNMPv3 message privacy (encryption) protocol "
|
||||||
|
"ID. Required for version 'v3'. "
|
||||||
|
"'aes' is supported."),
|
||||||
|
}
|
||||||
|
|
||||||
|
SNMP_V3_DEPRECATED_PROPERTIES = {
|
||||||
|
'irmc_snmp_security': _("SNMP security name required for version 'v3'. "
|
||||||
|
"Optional. Deprecated."),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||||
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
||||||
|
COMMON_PROPERTIES.update(SNMP_PROPERTIES)
|
||||||
|
COMMON_PROPERTIES.update(SNMP_V3_REQUIRED_PROPERTIES)
|
||||||
|
COMMON_PROPERTIES.update(SNMP_V3_OPTIONAL_PROPERTIES)
|
||||||
|
COMMON_PROPERTIES.update(SNMP_V3_DEPRECATED_PROPERTIES)
|
||||||
|
|
||||||
|
|
||||||
def parse_driver_info(node):
|
def parse_driver_info(node):
|
||||||
@ -105,37 +137,145 @@ def parse_driver_info(node):
|
|||||||
error_msgs.append(
|
error_msgs.append(
|
||||||
_("Value '%s' is not supported for 'irmc_sensor_method'.") %
|
_("Value '%s' is not supported for 'irmc_sensor_method'.") %
|
||||||
d_info['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."))
|
|
||||||
if error_msgs:
|
if error_msgs:
|
||||||
msg = (_("The following errors were encountered while parsing "
|
msg = (_("The following errors were encountered while parsing "
|
||||||
"driver_info:\n%s") % "\n".join(error_msgs))
|
"driver_info:\n%s") % "\n".join(error_msgs))
|
||||||
raise exception.InvalidParameterValue(msg)
|
raise exception.InvalidParameterValue(msg)
|
||||||
|
|
||||||
|
d_info.update(_parse_snmp_driver_info(node, info))
|
||||||
|
|
||||||
return d_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:
|
||||||
|
snmp_info.update(_parse_snmp_v3_info(node, info))
|
||||||
|
|
||||||
|
return snmp_info
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_snmp_v3_info(node, info):
|
||||||
|
snmp_info = {}
|
||||||
|
missing_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'}
|
||||||
|
|
||||||
|
security = info.get('irmc_snmp_security', CONF.irmc.get('snmp_security'))
|
||||||
|
for param in SNMP_V3_REQUIRED_PROPERTIES:
|
||||||
|
try:
|
||||||
|
snmp_info[param] = info[param]
|
||||||
|
except KeyError:
|
||||||
|
if param == 'irmc_snmp_user':
|
||||||
|
if not security:
|
||||||
|
missing_info.append(param)
|
||||||
|
else:
|
||||||
|
LOG.warning(_("'irmc_snmp_security' parameter is "
|
||||||
|
"deprecated in favor of 'irmc_snmp_user' "
|
||||||
|
"parameter. Please set 'irmc_snmp_user' "
|
||||||
|
"and remove 'irmc_snmp_security' for node "
|
||||||
|
"%s."), node.uuid)
|
||||||
|
# In iRMC, the username must start with a letter, so only
|
||||||
|
# a string can be a valid username and a string from a
|
||||||
|
# number is invalid.
|
||||||
|
if not isinstance(security, str):
|
||||||
|
raise exception.InvalidParameterValue(_(
|
||||||
|
"Value '%s' is not a string for "
|
||||||
|
"'irmc_snmp_security.") %
|
||||||
|
info['irmc_snmp_security'])
|
||||||
|
else:
|
||||||
|
snmp_info['irmc_snmp_user'] = security
|
||||||
|
security = None
|
||||||
|
else:
|
||||||
|
missing_info.append(param)
|
||||||
|
|
||||||
|
if missing_info:
|
||||||
|
raise exception.MissingParameterValue(_(
|
||||||
|
"The following required SNMP parameters "
|
||||||
|
"are missing: %s") % missing_info)
|
||||||
|
|
||||||
|
if security:
|
||||||
|
LOG.warning(_("'irmc_snmp_security' parameter is ignored in favor of "
|
||||||
|
"'irmc_snmp_user' parameter. Please remove "
|
||||||
|
"'irmc_snmp_security' from node %s "
|
||||||
|
"configuration."), node.uuid)
|
||||||
|
if not isinstance(snmp_info['irmc_snmp_user'], str):
|
||||||
|
raise exception.InvalidParameterValue(_(
|
||||||
|
"Value '%s' is not a string for 'irmc_snmp_user'.") %
|
||||||
|
info['irmc_snmp_user'])
|
||||||
|
|
||||||
|
for param in snmp_keys:
|
||||||
|
if not isinstance(snmp_info[param], str):
|
||||||
|
raise exception.InvalidParameterValue(_(
|
||||||
|
"Value %(value)s is not a string for %(param)s.") %
|
||||||
|
{'param': param, 'value': snmp_info[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):
|
def get_irmc_client(node):
|
||||||
"""Gets an iRMC SCCI client.
|
"""Gets an iRMC SCCI client.
|
||||||
|
|
||||||
|
@ -103,11 +103,16 @@ def _get_mac_addresses(node):
|
|||||||
:returns: a list of mac addresses.
|
:returns: a list of mac addresses.
|
||||||
"""
|
"""
|
||||||
d_info = irmc_common.parse_driver_info(node)
|
d_info = irmc_common.parse_driver_info(node)
|
||||||
snmp_client = snmp.SNMPClient(d_info['irmc_address'],
|
snmp_client = snmp.SNMPClient(
|
||||||
d_info['irmc_snmp_port'],
|
address=d_info['irmc_address'],
|
||||||
d_info['irmc_snmp_version'],
|
port=d_info['irmc_snmp_port'],
|
||||||
d_info['irmc_snmp_community'],
|
version=d_info['irmc_snmp_version'],
|
||||||
d_info['irmc_snmp_security'])
|
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)
|
node_classes = snmp_client.get_next(NODE_CLASS_OID)
|
||||||
mac_addresses = [':'.join(['%02x' % x for x in mac])
|
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
|
node = task.node
|
||||||
d_info = irmc_common.parse_driver_info(node)
|
d_info = irmc_common.parse_driver_info(node)
|
||||||
snmp_client = snmp.SNMPClient(d_info['irmc_address'],
|
snmp_client = snmp.SNMPClient(
|
||||||
d_info['irmc_snmp_port'],
|
address=d_info['irmc_address'],
|
||||||
d_info['irmc_snmp_version'],
|
port=d_info['irmc_snmp_port'],
|
||||||
d_info['irmc_snmp_community'],
|
version=d_info['irmc_snmp_version'],
|
||||||
d_info['irmc_snmp_security'])
|
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
|
interval = CONF.irmc.snmp_polling_interval
|
||||||
retry_timeout_soft = timeout or CONF.conductor.soft_power_off_timeout
|
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),
|
utils.is_valid_no_proxy(no_proxy),
|
||||||
msg="'no_proxy' value should be invalid: {}".format(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):
|
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.irmc import management as irmc_management
|
||||||
from ironic.drivers.modules import pxe
|
from ironic.drivers.modules import pxe
|
||||||
from ironic.drivers.modules import pxe_base
|
from ironic.drivers.modules import pxe_base
|
||||||
|
from ironic.drivers.modules import snmp
|
||||||
from ironic.tests import base
|
from ironic.tests import base
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
from ironic.tests.unit.drivers.modules.irmc import test_common
|
from ironic.tests.unit.drivers.modules.irmc import test_common
|
||||||
@ -60,8 +61,7 @@ PARSED_IFNO = {
|
|||||||
'irmc_client_timeout': 60,
|
'irmc_client_timeout': 60,
|
||||||
'irmc_snmp_community': 'public',
|
'irmc_snmp_community': 'public',
|
||||||
'irmc_snmp_port': 161,
|
'irmc_snmp_port': 161,
|
||||||
'irmc_snmp_version': 'v2c',
|
'irmc_snmp_version': snmp.SNMP_V2C,
|
||||||
'irmc_snmp_security': None,
|
|
||||||
'irmc_sensor_method': 'ipmitool',
|
'irmc_sensor_method': 'ipmitool',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,8 +22,10 @@ from oslo_config import cfg
|
|||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
from ironic.common import exception
|
from ironic.common import exception
|
||||||
|
from ironic.common import utils
|
||||||
from ironic.conductor import task_manager
|
from ironic.conductor import task_manager
|
||||||
from ironic.drivers.modules.irmc import common as irmc_common
|
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 base as db_base
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
||||||
@ -54,7 +56,9 @@ class BaseIRMCTest(db_base.DbTestCase):
|
|||||||
|
|
||||||
class IRMCValidateParametersTestCase(BaseIRMCTest):
|
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)
|
info = irmc_common.parse_driver_info(self.node)
|
||||||
|
|
||||||
self.assertEqual('1.2.3.4', info['irmc_address'])
|
self.assertEqual('1.2.3.4', info['irmc_address'])
|
||||||
@ -64,12 +68,38 @@ class IRMCValidateParametersTestCase(BaseIRMCTest):
|
|||||||
self.assertEqual(80, info['irmc_port'])
|
self.assertEqual(80, info['irmc_port'])
|
||||||
self.assertEqual('digest', info['irmc_auth_method'])
|
self.assertEqual('digest', info['irmc_auth_method'])
|
||||||
self.assertEqual('ipmitool', info['irmc_sensor_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(161, info['irmc_snmp_port'])
|
||||||
self.assertEqual('public', info['irmc_snmp_community'])
|
self.assertEqual('public', info['irmc_snmp_community'])
|
||||||
self.assertFalse(info['irmc_snmp_security'])
|
|
||||||
|
|
||||||
def test_parse_driver_option_default(self):
|
def test_parse_driver_info_snmpv3(self):
|
||||||
|
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'
|
||||||
|
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(utils, 'is_fips_enabled',
|
||||||
|
return_value=False, autospec=True)
|
||||||
|
def test_parse_driver_option_default(self, mock_check_fips):
|
||||||
self.node.driver_info = {
|
self.node.driver_info = {
|
||||||
"irmc_address": "1.2.3.4",
|
"irmc_address": "1.2.3.4",
|
||||||
"irmc_username": "admin0",
|
"irmc_username": "admin0",
|
||||||
@ -130,8 +160,16 @@ class IRMCValidateParametersTestCase(BaseIRMCTest):
|
|||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
irmc_common.parse_driver_info, self.node)
|
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):
|
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,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
irmc_common.parse_driver_info, self.node)
|
irmc_common.parse_driver_info, self.node)
|
||||||
|
|
||||||
@ -141,15 +179,98 @@ class IRMCValidateParametersTestCase(BaseIRMCTest):
|
|||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
irmc_common.parse_driver_info, self.node)
|
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)
|
||||||
|
|
||||||
|
def test_parse_driver_info_missing_snmp_auth_password(self):
|
||||||
|
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'
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
irmc_common.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_missing_snmp_priv_password(self):
|
||||||
|
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.assertRaises(exception.MissingParameterValue,
|
||||||
|
irmc_common.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_using_snmp_security(self):
|
||||||
|
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'
|
||||||
|
info = irmc_common.parse_driver_info(self.node)
|
||||||
|
self.assertEqual('admin0', info['irmc_snmp_user'])
|
||||||
|
|
||||||
def test_parse_driver_info_invalid_snmp_security(self):
|
def test_parse_driver_info_invalid_snmp_security(self):
|
||||||
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
self.node.driver_info['irmc_snmp_version'] = 'v3'
|
||||||
self.node.driver_info['irmc_snmp_security'] = 100
|
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,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
irmc_common.parse_driver_info, self.node)
|
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_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)
|
||||||
|
|
||||||
|
def test_parse_driver_info_invalid_snmp_auth_password(self):
|
||||||
|
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'
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
irmc_common.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_short_snmp_auth_password(self):
|
||||||
|
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'
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
irmc_common.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_invalid_snmp_priv_password(self):
|
||||||
|
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
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
irmc_common.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_short_snmp_priv_password(self):
|
||||||
|
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'
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
irmc_common.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_invalid_snmp_auth_proto(self):
|
||||||
|
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'
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
irmc_common.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
def test_parse_driver_info_invalid_snmp_priv_proto(self):
|
||||||
|
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'
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
irmc_common.parse_driver_info, self.node)
|
irmc_common.parse_driver_info, self.node)
|
||||||
|
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds SNMPv3 message authentication and encryption features to iRMC driver.
|
||||||
|
To enable these features, the following parameters should be used in the
|
||||||
|
node's ``driver_info``:
|
||||||
|
|
||||||
|
* ``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_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``
|
||||||
|
|
||||||
|
deprecations:
|
||||||
|
- |
|
||||||
|
Deprecates the ``irmc_snmp_security`` field in ``driver_info`` for iRMC
|
||||||
|
driver, it will be removed in the future. Please use ``irmc_snmp_user``
|
||||||
|
field instead.
|
||||||
|
|
||||||
|
other:
|
||||||
|
- |
|
||||||
|
Updates the minimum version of ``python-scciclient`` library to
|
||||||
|
``0.12.1``.
|
Loading…
Reference in New Issue
Block a user