diff --git a/ceilometer/dispatcher/http.py b/ceilometer/dispatcher/http.py index 87b00a6334..5177773adf 100644 --- a/ceilometer/dispatcher/http.py +++ b/ceilometer/dispatcher/http.py @@ -16,6 +16,7 @@ import json from oslo_config import cfg from oslo_log import log +from oslo_utils import strutils import requests from ceilometer import dispatcher @@ -37,6 +38,10 @@ http_dispatcher_opts = [ default=5, help='The max time in seconds to wait for a request to ' 'timeout.'), + cfg.StrOpt('verify_ssl', + help='The path to a server certificate or directory if the ' + 'system CAs are not used or if a self-signed certificate ' + 'is used. Set to False to ignore SSL cert verification.'), ] cfg.CONF.register_opts(http_dispatcher_opts, group="dispatcher_http") @@ -59,6 +64,12 @@ class HttpDispatcher(dispatcher.MeterDispatcherBase, target = www.example.com event_target = www.example.com timeout = 2 + # No SSL verification + #verify_ssl = False + # SSL verification with system-installed CAs + verify_ssl = True + # SSL verification with specific CA or directory of certs + #verify_ssl = /path/to/ca_certificate.crt """ def __init__(self, conf): @@ -68,6 +79,11 @@ class HttpDispatcher(dispatcher.MeterDispatcherBase, self.target = self.conf.dispatcher_http.target self.event_target = (self.conf.dispatcher_http.event_target or self.target) + try: + self.verify_ssl = strutils.bool_from_string( + self.conf.dispatcher_http.verify_ssl, strict=True) + except ValueError: + self.verify_ssl = self.conf.dispatcher_http.verify_ssl or True def record_metering_data(self, data): if self.target == '': @@ -96,6 +112,7 @@ class HttpDispatcher(dispatcher.MeterDispatcherBase, res = requests.post(self.target, data=meter_json, headers=self.headers, + verify=self.verify_ssl, timeout=self.timeout) LOG.debug('Meter message posting finished with status code ' '%d.', res.status_code) @@ -123,6 +140,7 @@ class HttpDispatcher(dispatcher.MeterDispatcherBase, res = requests.post(self.event_target, data=event_json, headers=self.headers, + verify=self.verify_ssl, timeout=self.timeout) LOG.debug('Event Message posting finished with status code ' '%d.', res.status_code) diff --git a/ceilometer/tests/unit/dispatcher/test_http.py b/ceilometer/tests/unit/dispatcher/test_http.py index aed0e84fa7..ab8114b30d 100644 --- a/ceilometer/tests/unit/dispatcher/test_http.py +++ b/ceilometer/tests/unit/dispatcher/test_http.py @@ -71,6 +71,54 @@ class TestDispatcherHttp(base.BaseTestCase): self.assertEqual(1, post.call_count) + def test_http_dispatcher_with_ssl_default(self): + self.CONF.dispatcher_http.target = 'https://example.com' + self.CONF.dispatcher_http.verify_ssl = '' + dispatcher = http.HttpDispatcher(self.CONF) + + self.assertEqual(True, dispatcher.verify_ssl) + + with mock.patch.object(requests, 'post') as post: + dispatcher.record_metering_data(self.msg) + + self.assertEqual(True, post.call_args[1]['verify']) + + def test_http_dispatcher_with_ssl_true(self): + self.CONF.dispatcher_http.target = 'https://example.com' + self.CONF.dispatcher_http.verify_ssl = 'true' + dispatcher = http.HttpDispatcher(self.CONF) + + self.assertEqual(True, dispatcher.verify_ssl) + + with mock.patch.object(requests, 'post') as post: + dispatcher.record_metering_data(self.msg) + + self.assertEqual(True, post.call_args[1]['verify']) + + def test_http_dispatcher_with_ssl_false(self): + self.CONF.dispatcher_http.target = 'https://example.com' + self.CONF.dispatcher_http.verify_ssl = 'false' + dispatcher = http.HttpDispatcher(self.CONF) + + self.assertEqual(False, dispatcher.verify_ssl) + + with mock.patch.object(requests, 'post') as post: + dispatcher.record_metering_data(self.msg) + + self.assertEqual(False, post.call_args[1]['verify']) + + def test_http_dispatcher_with_ssl_path(self): + self.CONF.dispatcher_http.target = 'https://example.com' + self.CONF.dispatcher_http.verify_ssl = '/path/to/cert.crt' + dispatcher = http.HttpDispatcher(self.CONF) + + self.assertEqual('/path/to/cert.crt', dispatcher.verify_ssl) + + with mock.patch.object(requests, 'post') as post: + dispatcher.record_metering_data(self.msg) + + self.assertEqual('/path/to/cert.crt', post.call_args[1]['verify']) + class TestEventDispatcherHttp(base.BaseTestCase): """Test sending events with the http dispatcher""" @@ -124,10 +172,22 @@ class TestEventDispatcherHttp(base.BaseTestCase): self.assertEqual(0, post.call_count) def test_http_dispatcher_share_target(self): - self.CONF.dispatcher_http.target = 'fake' + self.CONF.dispatcher_http.event_target = 'fake' dispatcher = http.HttpDispatcher(self.CONF) with mock.patch.object(requests, 'post') as post: dispatcher.record_events(self.event) self.assertEqual('fake', post.call_args[0][0]) + + def test_http_dispatcher_with_ssl_path(self): + self.CONF.dispatcher_http.event_target = 'https://example.com' + self.CONF.dispatcher_http.verify_ssl = '/path/to/cert.crt' + dispatcher = http.HttpDispatcher(self.CONF) + + self.assertEqual('/path/to/cert.crt', dispatcher.verify_ssl) + + with mock.patch.object(requests, 'post') as post: + dispatcher.record_events(self.event) + + self.assertEqual('/path/to/cert.crt', post.call_args[1]['verify']) diff --git a/releasenotes/notes/http-dispatcher-verify-ssl-551d639f37849c6f.yaml b/releasenotes/notes/http-dispatcher-verify-ssl-551d639f37849c6f.yaml new file mode 100644 index 0000000000..0f4a74bc87 --- /dev/null +++ b/releasenotes/notes/http-dispatcher-verify-ssl-551d639f37849c6f.yaml @@ -0,0 +1,11 @@ +--- +features: + - In the [dispatcher_http] section of ceilometer.conf, verify_ssl can be + set to True to use system-installed certificates (default value) or + False to ignore certificate verification (use in development only!). + verify_ssl can also be set to the location of a certificate file + e.g. /some/path/cert.crt (use for self-signed certs) + or to a directory of certificates. + The value is passed as the 'verify' option to the underlying requests + method, which is documented at + http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification