diff --git a/ceilometer/collector/manager.py b/ceilometer/collector/manager.py index 7a435040..33b15a75 100644 --- a/ceilometer/collector/manager.py +++ b/ceilometer/collector/manager.py @@ -23,6 +23,7 @@ from nova.rpc import dispatcher as rpc_dispatcher from ceilometer import cfg from ceilometer import log +from ceilometer import meter from ceilometer import publish from ceilometer import rpc from ceilometer.collector import dispatcher @@ -84,8 +85,11 @@ class CollectorManager(manager.Manager): data['event_type'], data['resource_id'], data['counter_volume']) - try: - self.storage_conn.record_metering_data(data) - except Exception as err: - LOG.error('Failed to record metering data: %s', err) - LOG.exception(err) + if not meter.verify_signature(data): + LOG.warning('message signature invalid, discarding message') + else: + try: + self.storage_conn.record_metering_data(data) + except Exception as err: + LOG.error('Failed to record metering data: %s', err) + LOG.exception(err) diff --git a/ceilometer/meter.py b/ceilometer/meter.py index 802704f9..1e4471f8 100644 --- a/ceilometer/meter.py +++ b/ceilometer/meter.py @@ -52,6 +52,15 @@ def compute_signature(message): return digest_maker.hexdigest() +def verify_signature(message): + """Check the signature in the message against the value computed + from the rest of the contents. + """ + old_sig = message.get('message_signature') + new_sig = compute_signature(message) + return new_sig == old_sig + + def meter_message_from_counter(counter): """Make a metering message ready to be published or stored. diff --git a/tests/collector/test_manager.py b/tests/collector/test_manager.py new file mode 100644 index 00000000..317990c1 --- /dev/null +++ b/tests/collector/test_manager.py @@ -0,0 +1,71 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 New Dream Network, LLC (DreamHost) +# +# Author: Doug Hellmann +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Tests for ceilometer/agent/manager.py +""" + +import datetime + +from nova import context +from nova import test + +from ceilometer import meter +from ceilometer.collector import manager +from ceilometer.storage import base + + +class TestCollectorManager(test.TestCase): + + def setUp(self): + super(TestCollectorManager, self).setUp() + self.mgr = manager.CollectorManager() + self.ctx = context.RequestContext("user", "project") + + def test_valid_message(self): + msg = {'event_type': 'test', + 'resource_id': self.id(), + 'counter_volume': 1, + } + msg['message_signature'] = meter.compute_signature(msg) + + self.mgr.storage_conn = self.mox.CreateMock(base.Connection) + self.mgr.storage_conn.record_metering_data(msg) + self.mox.ReplayAll() + + self.mgr.record_metering_data(self.ctx, msg) + self.mox.VerifyAll() + + def test_invalid_message(self): + msg = {'event_type': 'test', + 'resource_id': self.id(), + 'counter_volume': 1, + } + msg['message_signature'] = 'invalid-signature' + + class ErrorConnection: + + called = False + + def record_metering_data(self, data): + self.called = True + + self.mgr.storage_conn = ErrorConnection() + + self.mgr.record_metering_data(self.ctx, msg) + + assert not self.mgr.storage_conn.called, \ + 'Should not have called the storage connection' diff --git a/tests/storage/test_register_opts.py b/tests/storage/test_register_opts.py index 8998cfdd..71518e5e 100644 --- a/tests/storage/test_register_opts.py +++ b/tests/storage/test_register_opts.py @@ -44,3 +44,4 @@ class RegisterOpts(test.TestCase): self._faux_engine.register_opts(flags.FLAGS) self.mox.ReplayAll() storage.register_opts(flags.FLAGS) + self.mox.VerifyAll() diff --git a/tests/test_meter.py b/tests/test_meter.py index fc431eed..ef921cf4 100644 --- a/tests/test_meter.py +++ b/tests/test_meter.py @@ -61,6 +61,24 @@ def test_compute_signature_use_configured_secret(): assert sig1 != sig2 +def test_verify_signature_signed(): + data = {'a': 'A', 'b': 'B'} + sig1 = meter.compute_signature(data) + data['message_signature'] = sig1 + assert meter.verify_signature(data) + + +def test_verify_signature_unsigned(): + data = {'a': 'A', 'b': 'B'} + assert not meter.verify_signature(data) + + +def test_verify_signature_incorrect(): + data = {'a': 'A', 'b': 'B', + 'message_signature': 'Not the same'} + assert not meter.verify_signature(data) + + TEST_COUNTER = counter.Counter(source='src', name='name', type='typ', diff --git a/tox.ini b/tox.ini index c1dc90c6..d5848441 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,9 @@ deps = -r{toxinidir}/tools/pip-requires commands = {toxinidir}/run_tests.sh sitepackages = True +[testenv:py27] +commands = {toxinidir}/run_tests.sh --with-coverage --cover-erase --cover-package=ceilometer --cover-inclusive + [testenv:pep8] deps = pep8 commands = pep8 --repeat --show-source ceilometer setup.py bin/ceilometer-agent bin/ceilometer-collector