From 2f5d9e28ccdfd7accfc3cc18c7f8cf419caf9459 Mon Sep 17 00:00:00 2001 From: Felipe Monteiro Date: Fri, 17 Feb 2017 16:06:05 -0500 Subject: [PATCH] Adds TLS/SSL Version Support to Murano Engine. Currently, Murano supports enabling SSL in Murano Engine [0], but does not have a param for the user to specify the desired SSL version. This is important because some versions of SSL are less secure than others [1]. [0] https://docs.openstack.org/developer/murano/administrator-guide/deploy_murano/configure_ssl.html [1] https://www.wolfssl.com/wolfSSL/Blog/Entries/2010/10/7_Differences_between_SSL_and_TLS_Protocol_Versions.html Change-Id: I71c36c455cde658f402a19c59d7966cee8544cf1 Partially-Implements: blueprint add-tls-support --- murano/common/config.py | 7 +++ murano/common/messaging/mqclient.py | 13 ++++- .../unit/common/messaging/test_mqclient.py | 58 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) diff --git a/murano/common/config.py b/murano/common/config.py index 656b30d2..92b5c650 100644 --- a/murano/common/config.py +++ b/murano/common/config.py @@ -55,6 +55,13 @@ rabbit_opts = [ help='Boolean flag to enable SSL communication through the ' 'RabbitMQ broker between murano-engine and guest agents.'), + cfg.StrOpt('ssl_version', + default='', + help='SSL version to use (valid only if SSL enabled). ' + 'Valid values are TLSv1 and SSLv23. SSLv2, SSLv3, ' + 'TLSv1_1, and TLSv1_2 may be available on some ' + 'distributions.'), + cfg.StrOpt('ca_certs', default='', help='SSL cert file (valid only if SSL enabled).'), diff --git a/murano/common/messaging/mqclient.py b/murano/common/messaging/mqclient.py index d2057ee1..cd7afd49 100644 --- a/murano/common/messaging/mqclient.py +++ b/murano/common/messaging/mqclient.py @@ -17,7 +17,9 @@ import ssl as ssl_module from eventlet import patcher from oslo_serialization import jsonutils +from oslo_service import sslutils +from murano.common.i18n import _ from murano.common.messaging import subscription kombu = patcher.import_patched('kombu') @@ -25,7 +27,7 @@ kombu = patcher.import_patched('kombu') class MqClient(object): def __init__(self, login, password, host, port, virtual_host, - ssl=False, ca_certs=None, insecure=False): + ssl=False, ssl_version=None, ca_certs=None, insecure=False): ssl_params = None if ssl: @@ -35,11 +37,20 @@ class MqClient(object): cert_reqs = ssl_module.CERT_OPTIONAL else: cert_reqs = ssl_module.CERT_NONE + ssl_params = { 'ca_certs': ca_certs, 'cert_reqs': cert_reqs } + if ssl_version: + key = ssl_version.lower() + try: + ssl_params['ssl_version'] = sslutils._SSL_PROTOCOLS[key] + except KeyError: + raise RuntimeError( + _("Invalid SSL version: %s") % ssl_version) + self._connection = kombu.Connection( 'amqp://{0}:{1}@{2}:{3}/{4}'.format( login, diff --git a/murano/tests/unit/common/messaging/test_mqclient.py b/murano/tests/unit/common/messaging/test_mqclient.py index c4a41bc1..4afce636 100644 --- a/murano/tests/unit/common/messaging/test_mqclient.py +++ b/murano/tests/unit/common/messaging/test_mqclient.py @@ -14,12 +14,16 @@ # under the License. import mock +from oslo_config import cfg from oslo_serialization import jsonutils import ssl as ssl_module +from murano.common.i18n import _ from murano.common.messaging import mqclient from murano.tests.unit import base +CONF = cfg.CONF + class MQClientTest(base.MuranoTestCase): @@ -42,6 +46,60 @@ class MQClientTest(base.MuranoTestCase): self.assertIsNone(self.ssl_client._channel) self.assertFalse(self.ssl_client._connected) + @mock.patch('murano.common.messaging.mqclient.kombu', autospec=True) + def test_client_initialization_with_ssl_version(self, mock_kombu): + ssl_versions = ( + ('tlsv1', getattr(ssl_module, 'PROTOCOL_TLSv1', None)), + ('tlsv1_1', getattr(ssl_module, 'PROTOCOL_TLSv1_1', None)), + ('tlsv1_2', getattr(ssl_module, 'PROTOCOL_TLSv1_2', None)), + ('sslv2', getattr(ssl_module, 'PROTOCOL_SSLv2', None)), + ('sslv23', getattr(ssl_module, 'PROTOCOL_SSLv23', None)), + ('sslv3', getattr(ssl_module, 'PROTOCOL_SSLv3', None))) + exception_count = 0 + + for ssl_name, ssl_version in ssl_versions: + ssl_kwargs = { + 'login': 'test_login', + 'password': 'test_password', + 'host': 'test_host', + 'port': 'test_port', + 'virtual_host': 'test_virtual_host', + 'ssl': True, + 'ssl_version': ssl_name, + 'ca_certs': ['cert1'], + 'insecure': False + } + + # If a ssl_version is not valid, a RuntimeError is thrown. + # According to the ssl_version docs in config.py, certain versions + # of TLS may be available depending on the system. So, just + # check that at least 1 ssl_version works. + if ssl_version is None: + e = self.assertRaises(RuntimeError, mqclient.MqClient, + **ssl_kwargs) + self.assertEqual(_('Invalid SSL version: %s') % ssl_name, + e.__str__()) + exception_count += 1 + continue + + self.ssl_client = mqclient.MqClient(**ssl_kwargs) + + mock_kombu.Connection.assert_called_once_with( + 'amqp://{0}:{1}@{2}:{3}/{4}'.format( + 'test_login', 'test_password', 'test_host', 'test_port', + 'test_virtual_host'), + ssl={'ca_certs': ['cert1'], + 'cert_reqs': ssl_module.CERT_REQUIRED, + 'ssl_version': ssl_version}) + self.assertEqual( + mock_kombu.Connection(), self.ssl_client._connection) + self.assertIsNone(self.ssl_client._channel) + self.assertFalse(self.ssl_client._connected) + mock_kombu.Connection.reset_mock() + + # Check that at least one ssl_version worked. + self.assertGreater(len(ssl_versions), exception_count) + @mock.patch('murano.common.messaging.mqclient.kombu') def test_alternate_client_initializations(self, mock_kombu): for ca_cert in ['cert1', None]: