From dc254e2f78a4bb42b0df6556df8347c7137ab5b2 Mon Sep 17 00:00:00 2001 From: Lianhao Lu <lianhao.lu@intel.com> Date: Thu, 7 Jul 2016 09:54:37 +0800 Subject: [PATCH] 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 --- ceilometer/hardware/discovery.py | 51 ++++++++++++++++--- ceilometer/hardware/inspector/snmp.py | 36 ++++++++++++- ceilometer/tests/unit/agent/test_discovery.py | 21 ++++++++ .../unit/hardware/inspector/test_snmp.py | 18 +++++++ ...l-snmpv3-usm-support-ab540c902fa89b9d.yaml | 5 ++ 5 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/add-full-snmpv3-usm-support-ab540c902fa89b9d.yaml diff --git a/ceilometer/hardware/discovery.py b/ceilometer/hardware/discovery.py index b15896c786..d21ff8edc6 100644 --- a/ceilometer/hardware/discovery.py +++ b/ceilometer/hardware/discovery.py @@ -32,10 +32,26 @@ OPTS = [ help='SNMPd user name of all nodes running in the cloud.'), cfg.StrOpt('readonly_user_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), + 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): @@ -49,6 +65,31 @@ class NodesDiscoveryTripleO(plugin_base.DiscoveryBase): def _address(instance, 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): """Discover resources to monitor. @@ -75,11 +116,7 @@ class NodesDiscoveryTripleO(plugin_base.DiscoveryBase): for instance in self.instances.values(): try: ip_address = self._address(instance, 'addr') - final_address = ( - cfg.CONF.hardware.url_scheme + - cfg.CONF.hardware.readonly_user_name + ':' + - cfg.CONF.hardware.readonly_user_password + '@' + - ip_address) + final_address = self._make_resource_url(ip_address) resource = { 'resource_id': instance.id, diff --git a/ceilometer/hardware/inspector/snmp.py b/ceilometer/hardware/inspector/snmp.py index 339344d11b..69d1a2fdb2 100644 --- a/ceilometer/hardware/inspector/snmp.py +++ b/ceilometer/hardware/inspector/snmp.py @@ -16,9 +16,10 @@ """Inspector for collecting data over SNMP""" import copy -from pysnmp.entity.rfc3413.oneliner import cmdgen +from pysnmp.entity.rfc3413.oneliner import cmdgen import six +import six.moves.urllib.parse as urlparse from ceilometer.hardware.inspector import base @@ -56,6 +57,22 @@ def parse_snmp_return(ret, is_bulk=False): EXACT = 'type_exact' 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): # Default port @@ -283,9 +300,24 @@ class SNMPInspector(base.Inspector): @staticmethod 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: + kwargs['authKey'] = host.password + + if kwargs: auth_strategy = cmdgen.UsmUserData(host.username, - authKey=host.password) + **kwargs) else: auth_strategy = cmdgen.CommunityData(host.username or 'public') return auth_strategy diff --git a/ceilometer/tests/unit/agent/test_discovery.py b/ceilometer/tests/unit/agent/test_discovery.py index bf68c26ba0..a95d2b5689 100644 --- a/ceilometer/tests/unit/agent/test_discovery.py +++ b/ceilometer/tests/unit/agent/test_discovery.py @@ -87,11 +87,22 @@ class TestHardwareDiscovery(base.BaseTestCase): '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): super(TestHardwareDiscovery, self).setUp() self.discovery = hardware.NodesDiscoveryTripleO() self.discovery.nova_cli = mock.MagicMock() self.manager = mock.MagicMock() + self.CONF = self.useFixture(fixture_config.Config()).conf def test_hardware_discovery(self): 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] resources = self.discovery.discover(self.manager) 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]) diff --git a/ceilometer/tests/unit/hardware/inspector/test_snmp.py b/ceilometer/tests/unit/hardware/inspector/test_snmp.py index 6943b8680f..28e584ecbd 100644 --- a/ceilometer/tests/unit/hardware/inspector/test_snmp.py +++ b/ceilometer/tests/unit/hardware/inspector/test_snmp.py @@ -14,6 +14,7 @@ # under the License. """Tests for ceilometer/hardware/inspector/snmp/inspector.py """ +import mock from oslo_utils import netutils from oslotest import mockpatch @@ -202,3 +203,20 @@ class TestSNMPInspector(test_base.BaseTestCase): name = rfc1902.ObjectName(oid) 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') diff --git a/releasenotes/notes/add-full-snmpv3-usm-support-ab540c902fa89b9d.yaml b/releasenotes/notes/add-full-snmpv3-usm-support-ab540c902fa89b9d.yaml new file mode 100644 index 0000000000..7e9dd5cd0a --- /dev/null +++ b/releasenotes/notes/add-full-snmpv3-usm-support-ab540c902fa89b9d.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - > + [`bug 1597618 <https://bugs.launchpad.net/ceilometer/+bug/1597618>`_] + Add the full support of snmp v3 user security model.