SNMPv3 security features added to the snmp
driver
SNMPv3 message authentication and encryption features added to Ironic ``snmp`` driver. Added support for the ``context_engine_id`` and ``context_name`` parameters of the SNMPv3 message at Ironic's ``snmp`` driver. The ``snmp_security`` parameter of Ironic ``snmp`` driver is obsoleted by the ``snmp_user`` parameter. Though ``snmp_security`` parameter remains supported. For the sake of patch reviewability, it does not touch similar SNMP features in the `iLO` and `irmc` drivers. A followup patch might converge these similar SNMP uses onto the core ``snmp`` driver's functionality this patch introduces. Change-Id: Ic8a4fc37a42377c4ec50ffae421b3f47922ef982 Story: 1710850 Task: 10611
This commit is contained in:
parent
96aeaa8d9b
commit
635f4a9e17
@ -124,10 +124,28 @@ The following property values have to be added to the node's
|
|||||||
- ``snmp_version``: (optional) SNMP protocol version
|
- ``snmp_version``: (optional) SNMP protocol version
|
||||||
(permitted values ``1``, ``2c`` or ``3``). If not specified, SNMPv1
|
(permitted values ``1``, ``2c`` or ``3``). If not specified, SNMPv1
|
||||||
is chosen.
|
is chosen.
|
||||||
- ``snmp_community``: (Required for SNMPv1 and SNMPv2c) SNMP community
|
- ``snmp_community``: (Required for SNMPv1/SNMPv2c) SNMP community
|
||||||
parameter for reads and writes to the PDU.
|
name parameter for reads and writes to the PDU.
|
||||||
- ``snmp_security``: (Required for SNMPv3) SNMPv3 User-based Security Model
|
- ``snmp_user``: (Required for SNMPv3) SNMPv3 User-based Security Model
|
||||||
(USM) user name.
|
(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
|
The following command can be used to enroll a node with the ``snmp`` hardware
|
||||||
type:
|
type:
|
||||||
@ -140,12 +158,3 @@ type:
|
|||||||
--driver-info snmp_outlet=<outlet_index> \
|
--driver-info snmp_outlet=<outlet_index> \
|
||||||
--driver-info snmp_community=<community_string> \
|
--driver-info snmp_community=<community_string> \
|
||||||
--properties capabilities=boot_option:netboot
|
--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.
|
|
||||||
|
@ -44,11 +44,63 @@ if pysnmp:
|
|||||||
from pysnmp.entity.rfc3413.oneliner import cmdgen
|
from pysnmp.entity.rfc3413.oneliner import cmdgen
|
||||||
from pysnmp import error as snmp_error
|
from pysnmp import error as snmp_error
|
||||||
from pysnmp.proto import rfc1902
|
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:
|
else:
|
||||||
cmdgen = None
|
cmdgen = None
|
||||||
snmp_error = None
|
snmp_error = None
|
||||||
rfc1902 = None
|
rfc1902 = None
|
||||||
|
|
||||||
|
snmp_auth_protocols = {
|
||||||
|
'none': None
|
||||||
|
}
|
||||||
|
|
||||||
|
snmp_priv_protocols = {
|
||||||
|
'none': None
|
||||||
|
}
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -65,20 +117,60 @@ REQUIRED_PROPERTIES = {
|
|||||||
OPTIONAL_PROPERTIES = {
|
OPTIONAL_PROPERTIES = {
|
||||||
'snmp_version':
|
'snmp_version':
|
||||||
_("SNMP protocol version: %(v1)s, %(v2c)s or %(v3)s "
|
_("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},
|
% {"v1": SNMP_V1, "v2c": SNMP_V2C, "v3": SNMP_V3},
|
||||||
'snmp_port':
|
'snmp_port':
|
||||||
_("SNMP port, default %(port)d") % {"port": SNMP_PORT},
|
_("SNMP port, default %(port)d.") % {"port": SNMP_PORT},
|
||||||
'snmp_community':
|
'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},
|
% {"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':
|
'snmp_security':
|
||||||
_("SNMPv3 User-based Security Model (USM) username. "
|
_("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},
|
% {"v3": SNMP_V3},
|
||||||
}
|
}
|
||||||
|
|
||||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||||
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
||||||
|
COMMON_PROPERTIES.update(DEPRECATED_PROPERTIES)
|
||||||
|
|
||||||
|
|
||||||
class SNMPClient(object):
|
class SNMPClient(object):
|
||||||
@ -88,27 +180,50 @@ class SNMPClient(object):
|
|||||||
interaction with PySNMP to simplify dynamic importing and unit testing.
|
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.address = address
|
||||||
self.port = port
|
self.port = port
|
||||||
self.version = version
|
self.version = version
|
||||||
if self.version == SNMP_V3:
|
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:
|
else:
|
||||||
self.community = community
|
self.community = community
|
||||||
|
|
||||||
self.cmd_gen = cmdgen.CommandGenerator()
|
self.cmd_gen = cmdgen.CommandGenerator()
|
||||||
|
|
||||||
def _get_auth(self):
|
def _get_auth(self):
|
||||||
"""Return the authorization data for an SNMP request.
|
"""Return the authorization data for an SNMP request.
|
||||||
|
|
||||||
:returns: A
|
:returns: Either
|
||||||
:class:`pysnmp.entity.rfc3413.oneliner.cmdgen.CommunityData`
|
: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:
|
if self.version == SNMP_V3:
|
||||||
# Handling auth/encryption credentials is not (yet) supported.
|
return cmdgen.UsmUserData(
|
||||||
# This version supports a security name analogous to community.
|
self.user,
|
||||||
return cmdgen.UsmUserData(self.security)
|
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:
|
else:
|
||||||
mp_model = 1 if self.version == SNMP_V2C else 0
|
mp_model = 1 if self.version == SNMP_V2C else 0
|
||||||
return cmdgen.CommunityData(self.community, mpModel=mp_model)
|
return cmdgen.CommunityData(self.community, mpModel=mp_model)
|
||||||
@ -223,7 +338,13 @@ def _get_client(snmp_info):
|
|||||||
snmp_info["port"],
|
snmp_info["port"],
|
||||||
snmp_info["version"],
|
snmp_info["version"],
|
||||||
snmp_info.get("community"),
|
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)
|
@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):
|
def _parse_driver_info(node):
|
||||||
"""Parse a node's driver_info values.
|
"""Parse a node's driver_info values.
|
||||||
|
|
||||||
@ -630,11 +869,9 @@ def _parse_driver_info(node):
|
|||||||
"%s.") % snmp_info['version'])
|
"%s.") % snmp_info['version'])
|
||||||
snmp_info['community'] = info.get('snmp_community')
|
snmp_info['community'] = info.get('snmp_community')
|
||||||
elif snmp_info['version'] == SNMP_V3:
|
elif snmp_info['version'] == SNMP_V3:
|
||||||
if 'snmp_security' not in info:
|
snmp_info.update(_parse_driver_info_snmpv3_user(node, info))
|
||||||
raise exception.MissingParameterValue(_(
|
snmp_info.update(_parse_driver_info_snmpv3_crypto(node, info))
|
||||||
"SNMP driver requires snmp_security to be set for version %s.")
|
snmp_info.update(_parse_driver_info_snmpv3_context(node, info))
|
||||||
% (SNMP_V3))
|
|
||||||
snmp_info['security'] = info.get('snmp_security')
|
|
||||||
|
|
||||||
# Target PDU IP address and power outlet identification
|
# Target PDU IP address and power outlet identification
|
||||||
snmp_info['address'] = info['snmp_address']
|
snmp_info['address'] = info['snmp_address']
|
||||||
|
@ -5781,6 +5781,10 @@ class ManagerTestProperties(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
|
|||||||
'force_persistent_boot_device',
|
'force_persistent_boot_device',
|
||||||
'snmp_driver', 'snmp_address', 'snmp_port', 'snmp_version',
|
'snmp_driver', 'snmp_address', 'snmp_port', 'snmp_version',
|
||||||
'snmp_community', 'snmp_security', 'snmp_outlet',
|
'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']
|
'deploy_forces_oob_reboot']
|
||||||
self._check_driver_properties("snmp", expected)
|
self._check_driver_properties("snmp", expected)
|
||||||
|
|
||||||
|
@ -137,7 +137,14 @@ def get_test_snmp_info(**kw):
|
|||||||
if result["snmp_version"] in ("1", "2c"):
|
if result["snmp_version"] in ("1", "2c"):
|
||||||
result["snmp_community"] = kw.get("snmp_community", "public")
|
result["snmp_community"] = kw.get("snmp_community", "public")
|
||||||
elif result["snmp_version"] == "3":
|
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
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class SNMPClientTestCase(base.TestCase):
|
|||||||
self.assertEqual(self.port, client.port)
|
self.assertEqual(self.port, client.port)
|
||||||
self.assertEqual(snmp.SNMP_V1, client.version)
|
self.assertEqual(snmp.SNMP_V1, client.version)
|
||||||
self.assertIsNone(client.community)
|
self.assertIsNone(client.community)
|
||||||
self.assertNotIn('security', client.__dict__)
|
self.assertNotIn('user', client.__dict__)
|
||||||
self.assertEqual(mock_cmdgen.return_value, client.cmd_gen)
|
self.assertEqual(mock_cmdgen.return_value, client.cmd_gen)
|
||||||
|
|
||||||
@mock.patch.object(cmdgen, 'CommunityData', autospec=True)
|
@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 = snmp.SNMPClient(self.address, self.port, snmp.SNMP_V3)
|
||||||
client._get_auth()
|
client._get_auth()
|
||||||
mock_cmdgen.assert_called_once_with()
|
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)
|
@mock.patch.object(cmdgen, 'UdpTransportTarget', autospec=True)
|
||||||
def test__get_transport(self, mock_transport, mock_cmdgen):
|
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_outlet'], str(info['outlet']))
|
||||||
self.assertEqual(INFO_DICT['snmp_version'], info['version'])
|
self.assertEqual(INFO_DICT['snmp_version'], info['version'])
|
||||||
self.assertEqual(INFO_DICT['snmp_community'], info['community'])
|
self.assertEqual(INFO_DICT['snmp_community'], info['community'])
|
||||||
self.assertNotIn('security', info)
|
self.assertNotIn('user', info)
|
||||||
|
|
||||||
def test__parse_driver_info_apc(self):
|
def test__parse_driver_info_apc(self):
|
||||||
# Make sure the APC driver type is parsed.
|
# Make sure the APC driver type is parsed.
|
||||||
@ -314,13 +322,156 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase):
|
|||||||
self.assertEqual('private', info['community'])
|
self.assertEqual('private', info['community'])
|
||||||
|
|
||||||
def test__parse_driver_info_snmp_v3(self):
|
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.
|
# Make sure SNMPv3 is parsed with a security string.
|
||||||
info = db_utils.get_test_snmp_info(snmp_version='3',
|
info = db_utils.get_test_snmp_info(snmp_version='3',
|
||||||
snmp_security='pass')
|
snmp_security='pass')
|
||||||
node = self._get_test_node(info)
|
node = self._get_test_node(info)
|
||||||
info = snmp._parse_driver_info(node)
|
info = snmp._parse_driver_info(node)
|
||||||
self.assertEqual('3', info['version'])
|
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):
|
def test__parse_driver_info_snmp_port_default(self):
|
||||||
# Make sure default SNMP UDP port numbers are correct
|
# 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.
|
# Make sure exception is raised when version is invalid.
|
||||||
info = db_utils.get_test_snmp_info(snmp_version='42',
|
info = db_utils.get_test_snmp_info(snmp_version='42',
|
||||||
snmp_community='public',
|
snmp_community='public',
|
||||||
snmp_security='pass')
|
snmp_user='pass')
|
||||||
node = self._get_test_node(info)
|
node = self._get_test_node(info)
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
snmp._parse_driver_info,
|
snmp._parse_driver_info,
|
||||||
@ -428,10 +579,10 @@ class SNMPValidateParametersTestCase(db_base.DbTestCase):
|
|||||||
snmp._parse_driver_info,
|
snmp._parse_driver_info,
|
||||||
node)
|
node)
|
||||||
|
|
||||||
def test__parse_driver_info_missing_security(self):
|
def test__parse_driver_info_missing_user(self):
|
||||||
# Make sure exception is raised when security is missing with SNMPv3.
|
# Make sure exception is raised when user is missing with SNMPv3.
|
||||||
info = db_utils.get_test_snmp_info(snmp_version='3')
|
info = db_utils.get_test_snmp_info(snmp_version='3')
|
||||||
del info['snmp_security']
|
del info['snmp_user']
|
||||||
node = self._get_test_node(info)
|
node = self._get_test_node(info)
|
||||||
self.assertRaises(exception.MissingParameterValue,
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
snmp._parse_driver_info,
|
snmp._parse_driver_info,
|
||||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user