Adds TLS/SSL Version Support to Murano Agent.

Already implemented for Murano, but need Murano Agent
support as well.

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].

This patch adds ssl_version support to Murano Agent.

[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: I410a38d3c8294ce63c15ae98f627fa5c69a3d118
Partially-Implements: blueprint add-tls-support
Depends-On: I71c36c455cde658f402a19c59d7966cee8544cf1
This commit is contained in:
Felipe Monteiro 2017-02-16 10:49:34 -05:00
parent e34c93fc17
commit 20213e0061
4 changed files with 83 additions and 2 deletions

View File

@ -118,6 +118,7 @@ class MuranoAgent(service.Service):
'port': rabbitmq.port,
'virtual_host': rabbitmq.virtual_host,
'ssl': rabbitmq.ssl,
'ssl_version': rabbitmq.ssl_version,
'ca_certs': rabbitmq.ca_certs.strip() or None,
'insecure': rabbitmq.insecure
}

View File

@ -56,6 +56,12 @@ rabbit_opts = [
help='Boolean flag to enable SSL communication through the '
'RabbitMQ broker between murano-engine and guest agents.',
default=False),
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',
help='SSL cert file (valid only if SSL enabled).',
default=''),

View File

@ -19,13 +19,14 @@ import ssl as ssl_module
import anyjson
import eventlet
import kombu
from oslo_service import sslutils
from muranoagent.common.messaging import subscription
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 +36,19 @@ 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)
# Time interval after which RabbitMQ will disconnect client if no
# heartbeats were received. Usually client sends 2 heartbeats during
# this interval. Using random to make it less lucky that many agents

View File

@ -14,10 +14,14 @@
import fixtures
import mock
import ssl as ssl_module
from oslo_service import sslutils
from muranoagent import app
from muranoagent import bunch
from muranoagent.common import config as cfg
from muranoagent.common.messaging import mqclient
from muranoagent import exceptions as exc
from muranoagent.tests.unit import base
from muranoagent.tests.unit import execution_plan as ep
@ -32,7 +36,8 @@ class TestApp(base.MuranoAgentTestCase, fixtures.FunctionFixture):
super(TestApp, self).setUp()
mock_path.return_value = True
self.agent = app.MuranoAgent()
CONF.set_override('storage', 'cache')
CONF.set_override('storage', 'cache', enforce_type=True)
self.addCleanup(CONF.clear_override, 'storage')
def test_verify_execution_plan_downloable(self):
template = self.useFixture(ep.ExPlanDownloable()).execution_plan
@ -80,3 +85,63 @@ class TestApp(base.MuranoAgentTestCase, fixtures.FunctionFixture):
template = self.useFixture(ep.ExPlanBerkWrongVersion()).execution_plan
self.assertRaises(exc.IncorrectFormat,
self.agent._verify_plan, template)
@mock.patch.object(mqclient, 'random', autospec=True)
@mock.patch.object(mqclient, 'kombu', autospec=True)
def test_rmq_client_initialization_with_ssl_version(self, mock_kombu,
mock_random):
expected_heartbeat = 20 # 20 = 20 + 20 * 0, due to mocked value below.
mock_random.random.return_value = 0
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'),
heartbeat=expected_heartbeat,
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)