If listen_tls is true, enable TLS on wsgi server
This change enables operators to set [DEFAULT]listen_tls to true configure IPA to be host its WSGI server over TLS using existing SSL support in oslo.service. In addition to configuring this in IPA, a deployer will need to also set [ssl]cert_file, [ssl]key_file, and optionally [ssl]ca_file in their ipa config, in addition to embedding those files into the IPA ramdisk in order for this to be functional. In order to make this change work, we also need to monkey patch socket library early, or else oslo.service will end up passing an unpatched socket to the eventlet wsgi server, which causes deadlocks. Change-Id: Ib7decae410915f3c27b045ee08538c94d455b030
This commit is contained in:
parent
7d0ad36ebd
commit
1d11f0b7dd
@ -12,8 +12,16 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
import eventlet
|
||||||
|
|
||||||
# NOTE(TheJulia): Eventlet, when monkey patching occurs, replaces the base
|
# NOTE(TheJulia): Eventlet, when monkey patching occurs, replaces the base
|
||||||
# dns resolver methods. This can lead to compatability issues,
|
# dns resolver methods. This can lead to compatability issues,
|
||||||
# and un-expected exceptions being raised during the process
|
# and un-expected exceptions being raised during the process
|
||||||
# of monkey patching. Such as one if there are no resolvers.
|
# of monkey patching. Such as one if there are no resolvers.
|
||||||
os.environ['EVENTLET_NO_GREENDNS'] = "yes"
|
os.environ['EVENTLET_NO_GREENDNS'] = "yes"
|
||||||
|
|
||||||
|
# NOTE(JayF) Without monkey_patching socket, API requests will hang with TLS
|
||||||
|
# enabled. Enabling more than just socket for monkey patching causes failures
|
||||||
|
# in image streaming. In an ideal world, we track down all those errors and
|
||||||
|
# monkey patch everything as suggested in eventlet documentation.
|
||||||
|
eventlet.monkey_patch(all=False, socket=True)
|
||||||
|
@ -130,7 +130,8 @@ class Application(object):
|
|||||||
"""Start the API service in the background."""
|
"""Start the API service in the background."""
|
||||||
self.service = wsgi.Server(self._conf, 'ironic-python-agent', app=self,
|
self.service = wsgi.Server(self._conf, 'ironic-python-agent', app=self,
|
||||||
host=self.agent.listen_address.hostname,
|
host=self.agent.listen_address.hostname,
|
||||||
port=self.agent.listen_address.port)
|
port=self.agent.listen_address.port,
|
||||||
|
use_ssl=self._conf.listen_tls)
|
||||||
self.service.start()
|
self.service.start()
|
||||||
LOG.info('Started API service on port %s',
|
LOG.info('Started API service on port %s',
|
||||||
self.agent.listen_address.port)
|
self.agent.listen_address.port)
|
||||||
|
@ -54,6 +54,18 @@ cli_opts = [
|
|||||||
help='The port to listen on. '
|
help='The port to listen on. '
|
||||||
'Can be supplied as "ipa-listen-port" kernel parameter.'),
|
'Can be supplied as "ipa-listen-port" kernel parameter.'),
|
||||||
|
|
||||||
|
# This is intentionally not settable via kernel command line, as it
|
||||||
|
# requires configuration parameters from oslo_service which are not
|
||||||
|
# configurable over the command line and require files-on-disk.
|
||||||
|
# Operators who want to use this support should configure it statically
|
||||||
|
# as part of a ramdisk build.
|
||||||
|
cfg.BoolOpt('listen_tls',
|
||||||
|
default=False,
|
||||||
|
help='When true, IPA will host API behind TLS. You will also '
|
||||||
|
'need to configure [ssl] group options for cert_file, '
|
||||||
|
'key_file, and, if desired, ca_file to validate client '
|
||||||
|
'certificates.'),
|
||||||
|
|
||||||
cfg.StrOpt('advertise_host',
|
cfg.StrOpt('advertise_host',
|
||||||
default=APARAMS.get('ipa-advertise-host', None),
|
default=APARAMS.get('ipa-advertise-host', None),
|
||||||
help='The host to tell Ironic to reply and send '
|
help='The host to tell Ironic to reply and send '
|
||||||
|
@ -208,7 +208,51 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
|
wsgi_server.start.assert_called_once_with()
|
||||||
|
mock_wait.assert_called_once_with(mock.ANY)
|
||||||
|
self.assertEqual([mock.call('list_hardware_info'),
|
||||||
|
mock.call('wait_for_disks')],
|
||||||
|
mock_dispatch.call_args_list)
|
||||||
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'ironic_python_agent.hardware_managers.cna._detect_cna_card',
|
||||||
|
mock.Mock())
|
||||||
|
@mock.patch.object(hardware, 'dispatch_to_managers', autospec=True)
|
||||||
|
@mock.patch.object(agent.IronicPythonAgent,
|
||||||
|
'_wait_for_interface', autospec=True)
|
||||||
|
@mock.patch('oslo_service.wsgi.Server', autospec=True)
|
||||||
|
@mock.patch.object(hardware, 'get_managers', autospec=True)
|
||||||
|
def test_run_with_ssl(self, mock_get_managers, mock_wsgi,
|
||||||
|
mock_wait, mock_dispatch):
|
||||||
|
CONF.set_override('inspection_callback_url', '')
|
||||||
|
CONF.set_override('listen_tls', True)
|
||||||
|
|
||||||
|
wsgi_server = mock_wsgi.return_value
|
||||||
|
|
||||||
|
def set_serve_api():
|
||||||
|
self.agent.serve_api = False
|
||||||
|
|
||||||
|
wsgi_server.start.side_effect = set_serve_api
|
||||||
|
self.agent.heartbeater = mock.Mock()
|
||||||
|
self.agent.api_client.lookup_node = mock.Mock()
|
||||||
|
self.agent.api_client.lookup_node.return_value = {
|
||||||
|
'node': {
|
||||||
|
'uuid': 'deadbeef-dabb-ad00-b105-f00d00bab10c'
|
||||||
|
},
|
||||||
|
'config': {
|
||||||
|
'heartbeat_timeout': 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.agent.run()
|
||||||
|
|
||||||
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
|
app=self.agent.api,
|
||||||
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=True)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
mock_wait.assert_called_once_with(mock.ANY)
|
mock_wait.assert_called_once_with(mock.ANY)
|
||||||
self.assertEqual([mock.call('list_hardware_info'),
|
self.assertEqual([mock.call('list_hardware_info'),
|
||||||
@ -262,7 +306,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
mock_wait.assert_called_once_with(mock.ANY)
|
mock_wait.assert_called_once_with(mock.ANY)
|
||||||
self.assertEqual([mock.call('list_hardware_info'),
|
self.assertEqual([mock.call('list_hardware_info'),
|
||||||
@ -320,7 +365,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
mock_wait.assert_called_once_with(mock.ANY)
|
mock_wait.assert_called_once_with(mock.ANY)
|
||||||
self.assertEqual([mock.call('list_hardware_info'),
|
self.assertEqual([mock.call('list_hardware_info'),
|
||||||
@ -365,7 +411,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
mock_wait.assert_called_once_with(mock.ANY)
|
mock_wait.assert_called_once_with(mock.ANY)
|
||||||
self.assertEqual([mock.call('list_hardware_info'),
|
self.assertEqual([mock.call('list_hardware_info'),
|
||||||
@ -412,7 +459,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host='2001:db8:dead:beef::cafe',
|
host='2001:db8:dead:beef::cafe',
|
||||||
port=9998)
|
port=9998,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
mock_wait.assert_called_once_with(mock.ANY)
|
mock_wait.assert_called_once_with(mock.ANY)
|
||||||
self.assertEqual([mock.call('list_hardware_info'),
|
self.assertEqual([mock.call('list_hardware_info'),
|
||||||
@ -455,7 +503,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
mock_dispatch.call_args_list)
|
mock_dispatch.call_args_list)
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
self.agent.heartbeater.start.assert_called_once_with()
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
|
|
||||||
@ -494,7 +543,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
|
|
||||||
mock_inspector.assert_called_once_with()
|
mock_inspector.assert_called_once_with()
|
||||||
@ -557,7 +607,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
|
|
||||||
mock_inspector.assert_called_once_with()
|
mock_inspector.assert_called_once_with()
|
||||||
@ -613,7 +664,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
|
|
||||||
self.assertFalse(mock_inspector.called)
|
self.assertFalse(mock_inspector.called)
|
||||||
@ -674,7 +726,8 @@ class TestBaseAgent(ironic_agent_base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
|
|
||||||
self.agent.heartbeater.start.assert_called_once_with()
|
self.agent.heartbeater.start.assert_called_once_with()
|
||||||
@ -827,7 +880,8 @@ class TestAgentStandalone(ironic_agent_base.IronicAgentTest):
|
|||||||
self.assertTrue(mock_get_managers.called)
|
self.assertTrue(mock_get_managers.called)
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server_request.start.assert_called_once_with()
|
wsgi_server_request.start.assert_called_once_with()
|
||||||
|
|
||||||
self.assertFalse(self.agent.heartbeater.called)
|
self.assertFalse(self.agent.heartbeater.called)
|
||||||
@ -1051,7 +1105,8 @@ class TestBaseAgentVMediaToken(ironic_agent_base.IronicAgentTest):
|
|||||||
|
|
||||||
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
mock_wsgi.assert_called_once_with(CONF, 'ironic-python-agent',
|
||||||
app=self.agent.api,
|
app=self.agent.api,
|
||||||
host=mock.ANY, port=9999)
|
host=mock.ANY, port=9999,
|
||||||
|
use_ssl=False)
|
||||||
wsgi_server.start.assert_called_once_with()
|
wsgi_server.start.assert_called_once_with()
|
||||||
mock_wait.assert_called_once_with(mock.ANY)
|
mock_wait.assert_called_once_with(mock.ANY)
|
||||||
self.assertEqual([mock.call('list_hardware_info'),
|
self.assertEqual([mock.call('list_hardware_info'),
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Enables support in IPA for hosting the API server over TLS. Using this
|
||||||
|
support requires setting ``[DEFAULT]listen_tls`` to True, and then setting
|
||||||
|
``[ssl]cert_file``, ``[ssl]key_file``, and optionally ``[ssl]ca_file`` to
|
||||||
|
files embedded in the ramdisk IPA runs inside.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user