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:
Lianhao Lu 2016-07-07 09:54:37 +08:00
parent 5cebb31c09
commit dc254e2f78
5 changed files with 122 additions and 9 deletions
ceilometer
hardware
tests/unit
agent
hardware/inspector
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.