diff --git a/doc/source/admin/drivers/snmp.rst b/doc/source/admin/drivers/snmp.rst index 5c06a0e9af..f71a9b241f 100644 --- a/doc/source/admin/drivers/snmp.rst +++ b/doc/source/admin/drivers/snmp.rst @@ -124,10 +124,28 @@ The following property values have to be added to the node's - ``snmp_version``: (optional) SNMP protocol version (permitted values ``1``, ``2c`` or ``3``). If not specified, SNMPv1 is chosen. -- ``snmp_community``: (Required for SNMPv1 and SNMPv2c) SNMP community - parameter for reads and writes to the PDU. -- ``snmp_security``: (Required for SNMPv3) SNMPv3 User-based Security Model - (USM) user name. +- ``snmp_community``: (Required for SNMPv1/SNMPv2c) SNMP community + name parameter for reads and writes to the PDU. +- ``snmp_user``: (Required for SNMPv3) SNMPv3 User-based Security Model + (USM) user name. Synonym for now obsolete ``snmp_security`` parameter. +- ``snmp_auth_protocol``: SNMPv3 message authentication protocol ID. + Valid values include: ``none``, ``md5``, ``sha`` for all pysnmp versions + and additionally ``sha224``, ``sha256``, ``sha384``, ``sha512`` for + pysnmp versions 4.4.1 and later. Default is ``none`` unless ``snmp_auth_key`` + is provided. In the latter case ``md5`` is the default. +- ``snmp_auth_key``: SNMPv3 message authentication key. Must be 8+ + characters long. Required when message authentication is used. +- ``snmp_priv_protocol``: SNMPv3 message privacy (encryption) protocol ID. + Valid values include: ``none``, ``des``, ``3des``, ``aes``, ``aes192``, + ``aes256`` for all pysnmp version and additionally ``aes192blmt``, + ``aes256blmt`` for pysnmp versions 4.4.3+. Note that message privacy + requires using message authentication. Default is ``none`` unless + ``snmp_priv_key`` is provided. In the latter case ``des`` is the default. +- ``snmp_priv_key``: SNMPv3 message privacy (encryption) key. Must be 8+ + characters long. Required when message encryption is used. +- ``snmp_context_engine_id``: SNMPv3 context engine ID. Default is + the value of authoritative engine ID. +- ``snmp_context_name``: SNMPv3 context name. Default is an empty string. The following command can be used to enroll a node with the ``snmp`` hardware type: @@ -140,12 +158,3 @@ type: --driver-info snmp_outlet= \ --driver-info snmp_community= \ --properties capabilities=boot_option:netboot - -PDU Configuration -================= - -This version of the SNMP power interface does not support SNMPv3 authentication -or encryption features. When using SNMPv3, the SNMPv3 agent at the PDU must -be configured in ``noAuthNoPriv`` mode. Also, the ``snmp_security`` parameter -is used to configure SNMP USM user name to the SNMP manager at the power -interface. The same USM user name must be configured to the target SNMP agent. diff --git a/ironic/drivers/modules/snmp.py b/ironic/drivers/modules/snmp.py index fa7ba75fd5..a67ca2ffbf 100644 --- a/ironic/drivers/modules/snmp.py +++ b/ironic/drivers/modules/snmp.py @@ -44,11 +44,63 @@ if pysnmp: from pysnmp.entity.rfc3413.oneliner import cmdgen from pysnmp import error as snmp_error from pysnmp.proto import rfc1902 + + snmp_auth_protocols = { + 'md5': cmdgen.usmHMACMD5AuthProtocol, + 'sha': cmdgen.usmHMACSHAAuthProtocol, + 'none': cmdgen.usmNoAuthProtocol, + } + + # available since pysnmp 4.4.1 + try: + snmp_auth_protocols.update( + { + 'sha224': cmdgen.usmHMAC128SHA224AuthProtocol, + 'sha256': cmdgen.usmHMAC192SHA256AuthProtocol, + 'sha384': cmdgen.usmHMAC256SHA384AuthProtocol, + 'sha512': cmdgen.usmHMAC384SHA512AuthProtocol, + + } + ) + + except AttributeError: + pass + + snmp_priv_protocols = { + 'des': cmdgen.usmDESPrivProtocol, + '3des': cmdgen.usm3DESEDEPrivProtocol, + 'aes': cmdgen.usmAesCfb128Protocol, + 'aes192': cmdgen.usmAesCfb192Protocol, + 'aes256': cmdgen.usmAesCfb256Protocol, + 'none': cmdgen.usmNoPrivProtocol, + } + + # available since pysnmp 4.4.3 + try: + snmp_priv_protocols.update( + { + 'aes192blmt': cmdgen.usmAesBlumenthalCfb192Protocol, + 'aes256blmt': cmdgen.usmAesBlumenthalCfb256Protocol, + + } + ) + + except AttributeError: + pass + else: cmdgen = None snmp_error = None rfc1902 = None + snmp_auth_protocols = { + 'none': None + } + + snmp_priv_protocols = { + 'none': None + } + LOG = logging.getLogger(__name__) @@ -65,20 +117,60 @@ REQUIRED_PROPERTIES = { OPTIONAL_PROPERTIES = { 'snmp_version': _("SNMP protocol version: %(v1)s, %(v2c)s or %(v3)s " - "(optional, default %(v1)s)") + "(optional, default %(v1)s).") % {"v1": SNMP_V1, "v2c": SNMP_V2C, "v3": SNMP_V3}, 'snmp_port': - _("SNMP port, default %(port)d") % {"port": SNMP_PORT}, + _("SNMP port, default %(port)d.") % {"port": SNMP_PORT}, 'snmp_community': - _("SNMP community. Required for versions %(v1)s and %(v2c)s") + _("SNMP community. Required for versions %(v1)s and %(v2c)s.") % {"v1": SNMP_V1, "v2c": SNMP_V2C}, + 'snmp_user': + _("SNMPv3 User-based Security Model (USM) username. " + "Required for version %(v3)s.") + % {"v3": SNMP_V3}, + 'snmp_auth_protocol': + _("SNMPv3 message authentication protocol ID. " + "Known values are: %(auth)s. " + "Default is 'none' unless 'snmp_auth_key' is provided. " + "In the latter case 'md5' is the default.") + % {'auth': sorted(snmp_auth_protocols)}, + 'snmp_auth_key': + _("SNMPv3 message authentication key. " + "Must be 8+ characters long. " + "Required when message authentication is used. " + "This key is used by the 'snmp_auth_protocol' algorithm."), + 'snmp_priv_protocol': + _("SNMPv3 message privacy (encryption) protocol ID. " + "Known values are: %(priv)s. " + "Using message privacy requires using message authentication. " + "Default is 'none' unless 'snmp_priv_key' is provided. " + "In the latter case 'des' is the default.") + % {'priv': sorted(snmp_priv_protocols)}, + 'snmp_priv_key': + _("SNMPv3 message authentication key. " + "Must be 8+ characters long. " + "Required when message authentication is used. " + "This key is used by the 'snmp_priv_protocol' algorithm."), + 'snmp_context_engine_id': + _("SNMPv3 context engine ID. " + "Default is the value of authoritative engine ID."), + 'snmp_context_name': + _("SNMPv3 context name. " + "Default is an empty string ('')."), +} + +DEPRECATED_PROPERTIES = { + # synonym for `snmp_user` 'snmp_security': _("SNMPv3 User-based Security Model (USM) username. " - "Required for version %(v3)s") + "Required for version %(v3)s. " + "This property is deprecated, please use `snmp_user` instead.") % {"v3": SNMP_V3}, } + COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy() COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES) +COMMON_PROPERTIES.update(DEPRECATED_PROPERTIES) class SNMPClient(object): @@ -88,27 +180,50 @@ class SNMPClient(object): interaction with PySNMP to simplify dynamic importing and unit testing. """ - def __init__(self, address, port, version, community=None, security=None): + def __init__(self, address, port, version, community=None, + user=None, auth_proto=None, + auth_key=None, priv_proto=None, + priv_key=None, context_engine_id=None, context_name=None): + if not cmdgen: + raise exception.DriverLoadError( + driver=self.__class__.__name__, + reason=_("Unable to import python-pysnmp library") + ) + self.address = address self.port = port self.version = version if self.version == SNMP_V3: - self.security = security + self.user = user + self.auth_proto = auth_proto + self.auth_key = auth_key + self.priv_proto = priv_proto + self.priv_key = priv_key + self.context_engine_id = context_engine_id + self.context_name = context_name or '' else: self.community = community + self.cmd_gen = cmdgen.CommandGenerator() def _get_auth(self): """Return the authorization data for an SNMP request. - :returns: A + :returns: Either :class:`pysnmp.entity.rfc3413.oneliner.cmdgen.CommunityData` - object. + or :class:`pysnmp.entity.rfc3413.oneliner.cmdgen.UsmUserData` + object depending on SNMP version being used. """ if self.version == SNMP_V3: - # Handling auth/encryption credentials is not (yet) supported. - # This version supports a security name analogous to community. - return cmdgen.UsmUserData(self.security) + return cmdgen.UsmUserData( + self.user, + authKey=self.auth_key, + authProtocol=self.auth_proto, + privKey=self.priv_key, + privProtocol=self.priv_proto, + contextEngineId=self.context_engine_id, + contextName=self.context_name + ) else: mp_model = 1 if self.version == SNMP_V2C else 0 return cmdgen.CommunityData(self.community, mpModel=mp_model) @@ -223,7 +338,13 @@ def _get_client(snmp_info): snmp_info["port"], snmp_info["version"], snmp_info.get("community"), - snmp_info.get("security")) + snmp_info.get("user"), + snmp_info.get("auth_proto"), + snmp_info.get("auth_key"), + snmp_info.get("priv_proto"), + snmp_info.get("priv_key"), + snmp_info.get("context_engine_id"), + snmp_info.get("context_name")) @six.add_metaclass(abc.ABCMeta) @@ -580,6 +701,124 @@ DRIVER_CLASSES = { } +def _parse_driver_info_snmpv3_user(node, info): + snmp_info = {} + + if 'snmp_user' not in info and 'snmp_security' not in info: + raise exception.MissingParameterValue(_( + "SNMP driver requires `driver_info/snmp_user` to be set in " + "node %(node)s configuration for SNMP version %(ver)s.") % + {'node': node.uuid, 'ver': SNMP_V3}) + + snmp_info['user'] = info.get('snmp_user', info.get('snmp_security')) + + if 'snmp_security' in info: + LOG.warning("The `driver_info/snmp_security` parameter is deprecated " + "in favor of `driver_info/snmp_user` parameter. Please " + "remove the `driver_info/snmp_security` parameter from " + "node %(node)s configuration.", {'node': node.uuid}) + + if 'snmp_user' in info: + LOG.warning("The `driver_info/snmp_security` parameter is ignored " + "in favor of `driver_info/snmp_user` parameter in " + "node %(node)s configuration.", {'node': node.uuid}) + + return snmp_info + + +def _parse_driver_info_snmpv3_crypto(node, info): + snmp_info = {} + + if 'snmp_auth_protocol' in info: + auth_p = info['snmp_auth_protocol'] + try: + snmp_info['auth_protocol'] = snmp_auth_protocols[auth_p] + + except KeyError: + raise exception.InvalidParameterValue(_( + "SNMPPowerDriver: unknown SNMPv3 authentication protocol " + "`driver_info/snmp_auth_protocol` %(proto)s in node %(node)s " + "configuration, known protocols are: %(protos)s") % + {'node': node.uuid, 'proto': auth_p, + 'protos': ', '.join(snmp_auth_protocols)} + ) + if 'snmp_priv_protocol' in info: + priv_p = info['snmp_priv_protocol'] + try: + snmp_info['priv_protocol'] = snmp_priv_protocols[priv_p] + + except KeyError: + raise exception.InvalidParameterValue(_( + "SNMPPowerDriver: unknown SNMPv3 privacy protocol " + "`driver_info/snmp_priv_protocol` %(proto)s in node " + "%(node)s configuration, known protocols are: %(protos)s") % + {'node': node.uuid, 'proto': priv_p, + 'protos': ', '.join(snmp_priv_protocols)} + ) + if 'snmp_auth_key' in info: + auth_k = info['snmp_auth_key'] + if len(auth_k) < 8: + raise exception.InvalidParameterValue(_( + "SNMPPowerDriver: short SNMPv3 authentication key " + "`driver_info/snmp_auth_key` in node %(node)s configuration " + "(8+ chars required)") % {'node': node.uuid}) + + snmp_info['auth_key'] = auth_k + + if 'auth_protocol' not in snmp_info: + snmp_info['auth_protocol'] = snmp_auth_protocols['md5'] + + if 'snmp_priv_key' in info: + priv_k = info['snmp_priv_key'] + if len(priv_k) < 8: + raise exception.InvalidParameterValue(_( + "SNMPPowerDriver: short SNMPv3 privacy key " + "`driver_info/snmp_priv_key` node %(node)s configuration " + "(8+ chars required)") % {'node': node.uuid}) + + snmp_info['priv_key'] = priv_k + + if 'priv_protocol' not in snmp_info: + snmp_info['priv_protocol'] = snmp_priv_protocols['des'] + + if ('priv_protocol' in snmp_info and + 'auth_protocol' not in snmp_info): + raise exception.MissingParameterValue(_( + "SNMPPowerDriver: SNMPv3 privacy requires authentication. " + "Please add `driver_info/auth_protocol` property to node " + "%(node)s configuration.") % {'node': node.uuid}) + + if ('auth_protocol' in snmp_info and + 'auth_key' not in snmp_info): + raise exception.MissingParameterValue(_( + "SNMPPowerDriver: missing SNMPv3 authentication key while " + "`driver_info/snmp_auth_protocol` is present. Please " + "add `driver_info/snmp_auth_key` to node %(node)s " + "configuration.") % {'node': node.uuid}) + + if ('priv_protocol' in snmp_info and + 'priv_key' not in snmp_info): + raise exception.MissingParameterValue(_( + "SNMPPowerDriver: missing SNMPv3 privacy key while " + "`driver_info/snmp_priv_protocol` is present. Please" + "add `driver_info/snmp_priv_key` to node %(node)s " + "configuration.") % {'node': node.uuid}) + + return snmp_info + + +def _parse_driver_info_snmpv3_context(node, info): + snmp_info = {} + + if 'snmp_context_engine_id' in info: + snmp_info['context_engine_id'] = info['snmp_context_engine_id'] + + if 'snmp_context_name' in info: + snmp_info['context_name'] = info['snmp_context_name'] + + return snmp_info + + def _parse_driver_info(node): """Parse a node's driver_info values. @@ -630,11 +869,9 @@ def _parse_driver_info(node): "%s.") % snmp_info['version']) snmp_info['community'] = info.get('snmp_community') elif snmp_info['version'] == SNMP_V3: - if 'snmp_security' not in info: - raise exception.MissingParameterValue(_( - "SNMP driver requires snmp_security to be set for version %s.") - % (SNMP_V3)) - snmp_info['security'] = info.get('snmp_security') + snmp_info.update(_parse_driver_info_snmpv3_user(node, info)) + snmp_info.update(_parse_driver_info_snmpv3_crypto(node, info)) + snmp_info.update(_parse_driver_info_snmpv3_context(node, info)) # Target PDU IP address and power outlet identification snmp_info['address'] = info['snmp_address'] diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index 64ef8906a7..38c9ab0547 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -5781,6 +5781,10 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): 'force_persistent_boot_device', 'snmp_driver', 'snmp_address', 'snmp_port', 'snmp_version', 'snmp_community', 'snmp_security', 'snmp_outlet', + 'snmp_user', + 'snmp_context_engine_id', 'snmp_context_name', + 'snmp_auth_key', 'snmp_auth_protocol', + 'snmp_priv_key', 'snmp_priv_protocol', 'deploy_forces_oob_reboot'] self._check_driver_properties("snmp", expected) diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py index e6427dc872..bfbf73e39a 100644 --- a/ironic/tests/unit/db/utils.py +++ b/ironic/tests/unit/db/utils.py @@ -137,7 +137,14 @@ def get_test_snmp_info(**kw): if result["snmp_version"] in ("1", "2c"): result["snmp_community"] = kw.get("snmp_community", "public") elif result["snmp_version"] == "3": - result["snmp_security"] = kw.get("snmp_security", "public") + result["snmp_user"] = kw.get( + "snmp_user", kw.get("snmp_security", "snmpuser") + ) + for option in ('snmp_auth_protocol', 'snmp_auth_key', + 'snmp_priv_protocol', 'snmp_priv_key', + 'snmp_context_engine_id', 'snmp_context_name'): + if option in kw: + result[option] = kw[option] return result diff --git a/ironic/tests/unit/drivers/modules/test_snmp.py b/ironic/tests/unit/drivers/modules/test_snmp.py index 80a51cb06c..648009ca87 100644 --- a/ironic/tests/unit/drivers/modules/test_snmp.py +++ b/ironic/tests/unit/drivers/modules/test_snmp.py @@ -52,7 +52,7 @@ class SNMPClientTestCase(base.TestCase): self.assertEqual(self.port, client.port) self.assertEqual(snmp.SNMP_V1, client.version) self.assertIsNone(client.community) - self.assertNotIn('security', client.__dict__) + self.assertNotIn('user', client.__dict__) self.assertEqual(mock_cmdgen.return_value, client.cmd_gen) @mock.patch.object(cmdgen, 'CommunityData', autospec=True) @@ -67,7 +67,15 @@ class SNMPClientTestCase(base.TestCase): client = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3) client._get_auth() mock_cmdgen.assert_called_once_with() - mock_user.assert_called_once_with(client.security) + mock_user.assert_called_once_with( + client.user, + authKey=client.auth_key, + authProtocol=client.auth_proto, + privKey=client.priv_key, + privProtocol=client.priv_proto, + contextEngineId=client.context_engine_id, + contextName=client.context_name + ) @mock.patch.object(cmdgen, 'UdpTransportTarget', autospec=True) def test__get_transport(self, mock_transport, mock_cmdgen): @@ -237,7 +245,7 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase): self.assertEqual(INFO_DICT['snmp_outlet'], str(info['outlet'])) self.assertEqual(INFO_DICT['snmp_version'], info['version']) self.assertEqual(INFO_DICT['snmp_community'], info['community']) - self.assertNotIn('security', info) + self.assertNotIn('user', info) def test__parse_driver_info_apc(self): # Make sure the APC driver type is parsed. @@ -314,13 +322,156 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase): self.assertEqual('private', info['community']) def test__parse_driver_info_snmp_v3(self): + # Make sure SNMPv3 is parsed with user string. + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('3', info['version']) + self.assertEqual('pass', info['user']) + + def test__parse_driver_info_snmp_v3_auth_default_proto(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_auth_key='12345678') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('12345678', info['auth_key']) + self.assertEqual(snmp.snmp_auth_protocols['md5'], + info['auth_protocol']) + + def test__parse_driver_info_snmp_v3_auth_key_proto(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_auth_key='12345678', + snmp_auth_protocol='sha') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('12345678', info['auth_key']) + self.assertEqual(snmp.snmp_auth_protocols['sha'], + info['auth_protocol']) + + def test__parse_driver_info_snmp_v3_auth_nokey(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_auth_protocol='sha') + node = self._get_test_node(info) + self.assertRaisesRegex( + exception.InvalidParameterValue, + 'missing.*authentication key', + snmp._parse_driver_info, + node + ) + + def test__parse_driver_info_snmp_v3_auth_badproto(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_auth_key='12345678', + snmp_auth_protocol='whatever') + node = self._get_test_node(info) + self.assertRaisesRegex( + exception.InvalidParameterValue, + '.*?unknown SNMPv3 authentication protocol.*', + snmp._parse_driver_info, + node + ) + + def test__parse_driver_info_snmp_v3_auth_short_key(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_auth_key='1234567') + node = self._get_test_node(info) + self.assertRaisesRegex( + exception.InvalidParameterValue, + '.*?short SNMPv3 authentication key.*', + snmp._parse_driver_info, + node + ) + + def test__parse_driver_info_snmp_v3_priv_default_proto(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_auth_key='12345678', + snmp_priv_key='87654321') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('87654321', info['priv_key']) + self.assertEqual(snmp.snmp_priv_protocols['des'], + info['priv_protocol']) + + def test__parse_driver_info_snmp_v3_priv_key_proto(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_auth_key='12345678', + snmp_priv_protocol='3des', + snmp_priv_key='87654321') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('87654321', info['priv_key']) + self.assertEqual(snmp.snmp_priv_protocols['3des'], + info['priv_protocol']) + + def test__parse_driver_info_snmp_v3_priv_nokey(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_priv_protocol='3des') + node = self._get_test_node(info) + self.assertRaisesRegex( + exception.InvalidParameterValue, + '.*?SNMPv3 privacy requires authentication.*', + snmp._parse_driver_info, + node + ) + + def test__parse_driver_info_snmp_v3_priv_badproto(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_priv_key='12345678', + snmp_priv_protocol='whatever') + node = self._get_test_node(info) + self.assertRaisesRegex( + exception.InvalidParameterValue, + '.*?unknown SNMPv3 privacy protocol.*', + snmp._parse_driver_info, + node + ) + + def test__parse_driver_info_snmp_v3_priv_short_key(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_priv_key='1234567') + node = self._get_test_node(info) + self.assertRaisesRegex( + exception.InvalidParameterValue, + '.*?short SNMPv3 privacy key.*', + snmp._parse_driver_info, + node + ) + + def test__parse_driver_info_snmp_v3_compat(self): # Make sure SNMPv3 is parsed with a security string. info = db_utils.get_test_snmp_info(snmp_version='3', snmp_security='pass') node = self._get_test_node(info) info = snmp._parse_driver_info(node) self.assertEqual('3', info['version']) - self.assertEqual('pass', info['security']) + self.assertEqual('pass', info['user']) + + def test__parse_driver_info_snmp_v3_context_engine_id(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_context_engine_id='whatever') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('whatever', info['context_engine_id']) + + def test__parse_driver_info_snmp_v3_context_name(self): + info = db_utils.get_test_snmp_info(snmp_version='3', + snmp_user='pass', + snmp_context_name='whatever') + node = self._get_test_node(info) + info = snmp._parse_driver_info(node) + self.assertEqual('whatever', info['context_name']) def test__parse_driver_info_snmp_port_default(self): # Make sure default SNMP UDP port numbers are correct @@ -394,7 +545,7 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase): # Make sure exception is raised when version is invalid. info = db_utils.get_test_snmp_info(snmp_version='42', snmp_community='public', - snmp_security='pass') + snmp_user='pass') node = self._get_test_node(info) self.assertRaises(exception.InvalidParameterValue, snmp._parse_driver_info, @@ -428,10 +579,10 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase): snmp._parse_driver_info, node) - def test__parse_driver_info_missing_security(self): - # Make sure exception is raised when security is missing with SNMPv3. + def test__parse_driver_info_missing_user(self): + # Make sure exception is raised when user is missing with SNMPv3. info = db_utils.get_test_snmp_info(snmp_version='3') - del info['snmp_security'] + del info['snmp_user'] node = self._get_test_node(info) self.assertRaises(exception.MissingParameterValue, snmp._parse_driver_info, diff --git a/releasenotes/notes/add-snmpv3-security-features-bbefb8b844813a53.yaml b/releasenotes/notes/add-snmpv3-security-features-bbefb8b844813a53.yaml new file mode 100644 index 0000000000..eea5cb0d7e --- /dev/null +++ b/releasenotes/notes/add-snmpv3-security-features-bbefb8b844813a53.yaml @@ -0,0 +1,22 @@ +--- +features: + - | + Adds SNMPv3 message authentication and encryption features to ironic + ``snmp`` hardware type. To enable these features, the following + parameters should be used in the node's ``driver_info``: + + * ``snmp_user`` + * ``snmp_auth_protocol`` + * ``snmp_auth_key`` + * ``snmp_priv_protocol`` + * ``snmp_priv_key`` + + Also adds support for the ``context_engine_id`` and ``context_name`` + parameters of SNMPv3 message at ironic ``snmp`` hardware type. They + can be configured in the node's ``driver_info``. + +deprecations: + - | + Deprecates the ``snmp_security`` field in ``driver_info`` for ironic + ``snmp`` hardware type, it will be removed in Stein release. Please use + ``snmp_user`` field instead.