Merge "Add SSL/TLS Support"

This commit is contained in:
Jenkins 2015-06-02 14:54:45 +00:00 committed by Gerrit Code Review
commit be9d533280
4 changed files with 144 additions and 3 deletions

View File

@ -43,6 +43,15 @@
# Deprecated group/name - [discoverd]/clean_up_period
#clean_up_period = 60
# SSL Enabled/Disabled (boolean value)
#use_ssl = false
# Path to SSL certificate (string value)
#ssl_cert_path =
# Path to SSL key (string value)
#ssl_key_path =
[firewall]

View File

@ -169,6 +169,15 @@ SERVICE_OPTS = [
help='Amount of time in seconds, after which repeat clean up '
'of timed out nodes and old nodes status information.',
deprecated_group='discoverd'),
cfg.BoolOpt('use_ssl',
default=False,
help='SSL Enabled/Disabled'),
cfg.StrOpt('ssl_cert_path',
default='',
help='Path to SSL certificate'),
cfg.StrOpt('ssl_key_path',
default='',
help='Path to SSL key'),
]

View File

@ -17,6 +17,7 @@ eventlet.monkey_patch()
import functools
import json
import logging
import ssl
import sys
import flask
@ -168,6 +169,37 @@ def init():
LOG.warning(_LW('Timeout is disabled in configuration'))
def create_ssl_context():
if not CONF.use_ssl:
return
MIN_VERSION = (2, 7, 9)
if sys.version_info < MIN_VERSION:
LOG.warning(_LW('Unable to use SSL in this version of Python: '
'%{current}, please ensure your version of Python is '
'greater than %{min} to enable this feature.'),
{'current': '.'.join(map(str, sys.version_info[:3])),
'min': '.'.join(map(str, MIN_VERSION))})
return
context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
if CONF.ssl_cert_path and CONF.ssl_key_path:
try:
context.load_cert_chain(CONF.ssl_cert_path, CONF.ssl_key_path)
except IOError as exc:
LOG.warning(_LW('Failed to load certificate or key from defined '
'locations: %{cert} and %{key}, will continue to '
'run with the default settings: %{exc}'),
{'cert': CONF.ssl_cert_path, 'key': CONF.ssl_key_path,
'exc': exc})
except ssl.SSLError as exc:
LOG.warning(_LW('There was a problem with the loaded certificate '
'and key, will continue to run with the default '
'settings: %s'), exc)
return context
def main(args=sys.argv[1:]): # pragma: no cover
CONF(args, project='ironic-inspector')
debug = CONF.debug
@ -180,10 +212,16 @@ def main(args=sys.argv[1:]): # pragma: no cover
logging.getLogger('ironicclient.common.http').setLevel(
logging.INFO if debug else logging.ERROR)
app_kwargs = {'debug': debug,
'host': CONF.listen_address,
'port': CONF.listen_port}
context = create_ssl_context()
if context:
app_kwargs['ssl_context'] = context
init()
try:
app.run(debug=debug,
host=CONF.listen_address,
port=CONF.listen_port)
app.run(**app_kwargs)
finally:
firewall.clean_up()

View File

@ -12,6 +12,8 @@
# limitations under the License.
import json
import ssl
import sys
import unittest
import eventlet
@ -242,3 +244,86 @@ class TestInit(test_base.BaseTest):
self.assertRaises(SystemExit, main.init)
mock_log.assert_called_once_with(mock.ANY, "'foo!'")
class TestCreateSSLContext(test_base.BaseTest):
def test_use_ssl_false(self):
CONF.set_override('use_ssl', False)
con = main.create_ssl_context()
self.assertIsNone(con)
@mock.patch.object(sys, 'version_info')
def test_old_python_returns_none(self, mock_version_info):
mock_version_info.__lt__.return_value = True
CONF.set_override('use_ssl', True)
con = main.create_ssl_context()
self.assertIsNone(con)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_use_ssl_true(self, mock_cdc):
CONF.set_override('use_ssl', True)
m_con = mock_cdc()
con = main.create_ssl_context()
self.assertEqual(m_con, con)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_only_key_path_provided(self, mock_cdc):
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_key_path', '/some/fake/path')
mock_context = mock_cdc()
con = main.create_ssl_context()
self.assertEqual(mock_context, con)
self.assertFalse(mock_context.load_cert_chain.called)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_only_cert_path_provided(self, mock_cdc):
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_cert_path', '/some/fake/path')
mock_context = mock_cdc()
con = main.create_ssl_context()
self.assertEqual(mock_context, con)
self.assertFalse(mock_context.load_cert_chain.called)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_both_paths_provided(self, mock_cdc):
key_path = '/some/fake/path/key'
cert_path = '/some/fake/path/cert'
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_key_path', key_path)
CONF.set_override('ssl_cert_path', cert_path)
mock_context = mock_cdc()
con = main.create_ssl_context()
self.assertEqual(mock_context, con)
mock_context.load_cert_chain.assert_called_once_with(cert_path,
key_path)
@unittest.skipIf(sys.version_info[:3] < (2, 7, 9),
'This feature is unsupported in this version of python '
'so the tests will be skipped')
@mock.patch.object(ssl, 'create_default_context', autospec=True)
def test_load_cert_chain_fails(self, mock_cdc):
CONF.set_override('use_ssl', True)
key_path = '/some/fake/path/key'
cert_path = '/some/fake/path/cert'
CONF.set_override('use_ssl', True)
CONF.set_override('ssl_key_path', key_path)
CONF.set_override('ssl_cert_path', cert_path)
mock_context = mock_cdc()
mock_context.load_cert_chain.side_effect = IOError('Boom!')
con = main.create_ssl_context()
self.assertEqual(mock_context, con)
mock_context.load_cert_chain.assert_called_once_with(cert_path,
key_path)