Merge "[iRMC] Add SNMPv3 authentication functionality"

This commit is contained in:
Zuul 2022-07-22 00:54:38 +00:00 committed by Gerrit Code Review
commit b7c71bdbb8
11 changed files with 412 additions and 46 deletions

View File

@ -140,6 +140,16 @@ Node configuration
``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.
* All of the nodes are configured by setting the following configuration
options in the ``[irmc]`` section of ``/etc/ironic/ironic.conf``:
@ -175,6 +185,18 @@ Node configuration
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.
.. 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
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_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
=======================================================

View File

@ -6,7 +6,7 @@
# These are available on pypi
proliantutils>=2.13.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-xclarityclient>=0.1.6

View File

@ -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

View File

@ -73,10 +73,22 @@ 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'."),
deprecated_for_removal=True,
deprecated_reason=_("Use irmc_snmp_user")),
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'. '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',
default=0,
help=_('Priority for restore_irmc_bios_config clean step.')),

View File

@ -22,6 +22,7 @@ from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import utils
from ironic.conf import CONF
from ironic.drivers.modules import snmp
scci = importutils.try_import('scciclient.irmc.scci')
elcm = importutils.try_import('scciclient.irmc.elcm')
@ -44,18 +45,49 @@ OPTIONAL_PROPERTIES = {
'irmc_sensor_method': _("Sensor data retrieval method; either "
"'ipmitool' or 'scci'. The default value is "
"'ipmitool'. 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."),
'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.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):
@ -105,37 +137,145 @@ 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."))
if error_msgs:
msg = (_("The following errors were encountered while parsing "
"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:
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):
"""Gets an iRMC SCCI client.

View File

@ -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])

View File

@ -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

View File

@ -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):

View File

@ -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,8 +61,7 @@ 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',
}

View File

@ -22,8 +22,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 \
@ -54,7 +56,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'])
@ -64,12 +68,38 @@ 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'])
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 = {
"irmc_address": "1.2.3.4",
"irmc_username": "admin0",
@ -130,8 +160,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)
@ -141,15 +179,98 @@ 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)
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):
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)
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,
irmc_common.parse_driver_info, self.node)

View File

@ -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``.