Added full support of snmp v3 usm model
We used to only support partial of the snmp v3 usm model. This patch adds the full support. Change-Id: I3da8b19dea6a5ed3b0625d615ed27856046aa240 Closes-Bug: #1597618
This commit is contained in:
parent
5cebb31c09
commit
dc254e2f78
ceilometer
releasenotes/notes
@ -32,10 +32,26 @@ OPTS = [
|
|||||||
help='SNMPd user name of all nodes running in the cloud.'),
|
help='SNMPd user name of all nodes running in the cloud.'),
|
||||||
cfg.StrOpt('readonly_user_password',
|
cfg.StrOpt('readonly_user_password',
|
||||||
default='password',
|
default='password',
|
||||||
help='SNMPd password of all the nodes running in the cloud.',
|
help='SNMPd v3 authentication password of all the nodes '
|
||||||
|
'running in the cloud.',
|
||||||
secret=True),
|
secret=True),
|
||||||
|
cfg.StrOpt('readonly_user_auth_proto',
|
||||||
|
choices=['md5', 'sha'],
|
||||||
|
help='SNMPd v3 authentication algorithm of all the nodes '
|
||||||
|
'running in the cloud'),
|
||||||
|
cfg.StrOpt('readonly_user_priv_proto',
|
||||||
|
choices=['des', 'aes128', '3des', 'aes192', 'aes256'],
|
||||||
|
help='SNMPd v3 encryption algorithm of all the nodes '
|
||||||
|
'running in the cloud'),
|
||||||
|
cfg.StrOpt('readonly_user_priv_password',
|
||||||
|
help='SNMPd v3 encryption password of all the nodes '
|
||||||
|
'running in the cloud.',
|
||||||
|
secret=True),
|
||||||
|
|
||||||
|
|
||||||
]
|
]
|
||||||
cfg.CONF.register_opts(OPTS, group='hardware')
|
CONF = cfg.CONF
|
||||||
|
CONF.register_opts(OPTS, group='hardware')
|
||||||
|
|
||||||
|
|
||||||
class NodesDiscoveryTripleO(plugin_base.DiscoveryBase):
|
class NodesDiscoveryTripleO(plugin_base.DiscoveryBase):
|
||||||
@ -49,6 +65,31 @@ class NodesDiscoveryTripleO(plugin_base.DiscoveryBase):
|
|||||||
def _address(instance, field):
|
def _address(instance, field):
|
||||||
return instance.addresses['ctlplane'][0].get(field)
|
return instance.addresses['ctlplane'][0].get(field)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _make_resource_url(ip):
|
||||||
|
params = [('readonly_user_auth_proto', 'auth_proto'),
|
||||||
|
('readonly_user_priv_proto', 'priv_proto'),
|
||||||
|
('readonly_user_priv_password', 'priv_password')]
|
||||||
|
hwconf = CONF.hardware
|
||||||
|
url = hwconf.url_scheme
|
||||||
|
username = hwconf.readonly_user_name
|
||||||
|
password = hwconf.readonly_user_password
|
||||||
|
if username:
|
||||||
|
url += username
|
||||||
|
if password:
|
||||||
|
url += ':' + password
|
||||||
|
if username or password:
|
||||||
|
url += '@'
|
||||||
|
url += ip
|
||||||
|
|
||||||
|
query = "&".join(
|
||||||
|
param + "=" + hwconf.get(conf) for (conf, param) in params
|
||||||
|
if hwconf.get(conf))
|
||||||
|
if query:
|
||||||
|
url += '?' + query
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
def discover(self, manager, param=None):
|
def discover(self, manager, param=None):
|
||||||
"""Discover resources to monitor.
|
"""Discover resources to monitor.
|
||||||
|
|
||||||
@ -75,11 +116,7 @@ class NodesDiscoveryTripleO(plugin_base.DiscoveryBase):
|
|||||||
for instance in self.instances.values():
|
for instance in self.instances.values():
|
||||||
try:
|
try:
|
||||||
ip_address = self._address(instance, 'addr')
|
ip_address = self._address(instance, 'addr')
|
||||||
final_address = (
|
final_address = self._make_resource_url(ip_address)
|
||||||
cfg.CONF.hardware.url_scheme +
|
|
||||||
cfg.CONF.hardware.readonly_user_name + ':' +
|
|
||||||
cfg.CONF.hardware.readonly_user_password + '@' +
|
|
||||||
ip_address)
|
|
||||||
|
|
||||||
resource = {
|
resource = {
|
||||||
'resource_id': instance.id,
|
'resource_id': instance.id,
|
||||||
|
@ -16,9 +16,10 @@
|
|||||||
"""Inspector for collecting data over SNMP"""
|
"""Inspector for collecting data over SNMP"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
from pysnmp.entity.rfc3413.oneliner import cmdgen
|
|
||||||
|
|
||||||
|
from pysnmp.entity.rfc3413.oneliner import cmdgen
|
||||||
import six
|
import six
|
||||||
|
import six.moves.urllib.parse as urlparse
|
||||||
|
|
||||||
from ceilometer.hardware.inspector import base
|
from ceilometer.hardware.inspector import base
|
||||||
|
|
||||||
@ -56,6 +57,22 @@ def parse_snmp_return(ret, is_bulk=False):
|
|||||||
EXACT = 'type_exact'
|
EXACT = 'type_exact'
|
||||||
PREFIX = 'type_prefix'
|
PREFIX = 'type_prefix'
|
||||||
|
|
||||||
|
_auth_proto_mapping = {
|
||||||
|
'md5': cmdgen.usmHMACMD5AuthProtocol,
|
||||||
|
'sha': cmdgen.usmHMACSHAAuthProtocol,
|
||||||
|
}
|
||||||
|
_priv_proto_mapping = {
|
||||||
|
'des': cmdgen.usmDESPrivProtocol,
|
||||||
|
'aes128': cmdgen.usmAesCfb128Protocol,
|
||||||
|
'3des': cmdgen.usm3DESEDEPrivProtocol,
|
||||||
|
'aes192': cmdgen.usmAesCfb192Protocol,
|
||||||
|
'aes256': cmdgen.usmAesCfb256Protocol,
|
||||||
|
}
|
||||||
|
_usm_proto_mapping = {
|
||||||
|
'auth_proto': ('authProtocol', _auth_proto_mapping),
|
||||||
|
'priv_proto': ('privProtocol', _priv_proto_mapping),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SNMPInspector(base.Inspector):
|
class SNMPInspector(base.Inspector):
|
||||||
# Default port
|
# Default port
|
||||||
@ -283,9 +300,24 @@ class SNMPInspector(base.Inspector):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_auth_strategy(host):
|
def _get_auth_strategy(host):
|
||||||
|
options = urlparse.parse_qs(host.query)
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
for key in _usm_proto_mapping:
|
||||||
|
opt = options.get(key, [None])[-1]
|
||||||
|
value = _usm_proto_mapping[key][1].get(opt)
|
||||||
|
if value:
|
||||||
|
kwargs[_usm_proto_mapping[key][0]] = value
|
||||||
|
|
||||||
|
priv_pass = options.get('priv_password', [None])[-1]
|
||||||
|
if priv_pass:
|
||||||
|
kwargs['privKey'] = priv_pass
|
||||||
if host.password:
|
if host.password:
|
||||||
|
kwargs['authKey'] = host.password
|
||||||
|
|
||||||
|
if kwargs:
|
||||||
auth_strategy = cmdgen.UsmUserData(host.username,
|
auth_strategy = cmdgen.UsmUserData(host.username,
|
||||||
authKey=host.password)
|
**kwargs)
|
||||||
else:
|
else:
|
||||||
auth_strategy = cmdgen.CommunityData(host.username or 'public')
|
auth_strategy = cmdgen.CommunityData(host.username or 'public')
|
||||||
return auth_strategy
|
return auth_strategy
|
||||||
|
@ -87,11 +87,22 @@ class TestHardwareDiscovery(base.BaseTestCase):
|
|||||||
'flavor_id': 'flavor_id',
|
'flavor_id': 'flavor_id',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expected_usm = {
|
||||||
|
'resource_id': 'resource_id',
|
||||||
|
'resource_url': ''.join(['snmp://ro_snmp_user:password@0.0.0.0',
|
||||||
|
'?priv_proto=aes192',
|
||||||
|
'&priv_password=priv_pass']),
|
||||||
|
'mac_addr': '01-23-45-67-89-ab',
|
||||||
|
'image_id': 'image_id',
|
||||||
|
'flavor_id': 'flavor_id',
|
||||||
|
}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestHardwareDiscovery, self).setUp()
|
super(TestHardwareDiscovery, self).setUp()
|
||||||
self.discovery = hardware.NodesDiscoveryTripleO()
|
self.discovery = hardware.NodesDiscoveryTripleO()
|
||||||
self.discovery.nova_cli = mock.MagicMock()
|
self.discovery.nova_cli = mock.MagicMock()
|
||||||
self.manager = mock.MagicMock()
|
self.manager = mock.MagicMock()
|
||||||
|
self.CONF = self.useFixture(fixture_config.Config()).conf
|
||||||
|
|
||||||
def test_hardware_discovery(self):
|
def test_hardware_discovery(self):
|
||||||
self.discovery.nova_cli.instance_get_all.return_value = [
|
self.discovery.nova_cli.instance_get_all.return_value = [
|
||||||
@ -106,3 +117,13 @@ class TestHardwareDiscovery(base.BaseTestCase):
|
|||||||
self.discovery.nova_cli.instance_get_all.return_value = [instance]
|
self.discovery.nova_cli.instance_get_all.return_value = [instance]
|
||||||
resources = self.discovery.discover(self.manager)
|
resources = self.discovery.discover(self.manager)
|
||||||
self.assertEqual(0, len(resources))
|
self.assertEqual(0, len(resources))
|
||||||
|
|
||||||
|
def test_hardware_discovery_usm(self):
|
||||||
|
self.CONF.set_override('readonly_user_priv_proto', 'aes192',
|
||||||
|
group='hardware')
|
||||||
|
self.CONF.set_override('readonly_user_priv_password', 'priv_pass',
|
||||||
|
group='hardware')
|
||||||
|
self.discovery.nova_cli.instance_get_all.return_value = [
|
||||||
|
self.MockInstance()]
|
||||||
|
resources = self.discovery.discover(self.manager)
|
||||||
|
self.assertEqual(self.expected_usm, resources[0])
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
"""Tests for ceilometer/hardware/inspector/snmp/inspector.py
|
"""Tests for ceilometer/hardware/inspector/snmp/inspector.py
|
||||||
"""
|
"""
|
||||||
|
import mock
|
||||||
from oslo_utils import netutils
|
from oslo_utils import netutils
|
||||||
from oslotest import mockpatch
|
from oslotest import mockpatch
|
||||||
|
|
||||||
@ -202,3 +203,20 @@ class TestSNMPInspector(test_base.BaseTestCase):
|
|||||||
name = rfc1902.ObjectName(oid)
|
name = rfc1902.ObjectName(oid)
|
||||||
|
|
||||||
self.assertEqual(oid, str(name))
|
self.assertEqual(oid, str(name))
|
||||||
|
|
||||||
|
@mock.patch.object(snmp.cmdgen, 'UsmUserData')
|
||||||
|
def test_auth_strategy(self, mock_method):
|
||||||
|
host = ''.join(['snmp://a:b@foo?auth_proto=sha',
|
||||||
|
'&priv_password=pass&priv_proto=aes256'])
|
||||||
|
host = netutils.urlsplit(host)
|
||||||
|
self.inspector._get_auth_strategy(host)
|
||||||
|
mock_method.assert_called_with(
|
||||||
|
'a', authKey='b',
|
||||||
|
authProtocol=snmp.cmdgen.usmHMACSHAAuthProtocol,
|
||||||
|
privProtocol=snmp.cmdgen.usmAesCfb256Protocol,
|
||||||
|
privKey='pass')
|
||||||
|
|
||||||
|
host2 = 'snmp://a:b@foo?&priv_password=pass'
|
||||||
|
host2 = netutils.urlsplit(host2)
|
||||||
|
self.inspector._get_auth_strategy(host2)
|
||||||
|
mock_method.assert_called_with('a', authKey='b', privKey='pass')
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- >
|
||||||
|
[`bug 1597618 <https://bugs.launchpad.net/ceilometer/+bug/1597618>`_]
|
||||||
|
Add the full support of snmp v3 user security model.
|
Loading…
x
Reference in New Issue
Block a user