Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mimic: mgr/dashboard: Add http support to dashboard #24734

Merged
merged 3 commits into from Oct 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 19 additions & 4 deletions doc/mgr/dashboard.rst
Expand Up @@ -101,7 +101,7 @@ Configuration
SSL/TLS Support
^^^^^^^^^^^^^^^

All HTTP connections to the dashboard are secured with SSL/TLS.
All HTTP connections to the dashboard are secured with SSL/TLS by default.

To get the dashboard up and running quickly, you can generate and install a
self-signed certificate using the following built-in command::
Expand Down Expand Up @@ -134,6 +134,20 @@ of the ``ceph-mgr`` instance, usually the hostname)::
$ ceph config-key set mgr/dashboard/$name/crt -i dashboard.crt
$ ceph config-key set mgr/dashboard/$name/key -i dashboard.key

SSL can also be disabled by setting this configuration value::

$ ceph config set mgr mgr/dashboard/ssl false

This might be useful if the dashboard will be running behind a proxy which does
not support SSL for its upstream servers or other situations where SSL is not
wanted or required.

.. warning::

Use caution when disabling SSL as usernames and passwords will be sent to the
dashboard unencrypted.


.. note::

You need to restart the Ceph manager processes manually after changing the SSL
Expand All @@ -150,9 +164,10 @@ Host name and port
Like most web applications, dashboard binds to a TCP/IP address and TCP port.

By default, the ``ceph-mgr`` daemon hosting the dashboard (i.e., the currently
active manager) will bind to TCP port 8443. If no specific address has been
configured, the web app will bind to ``::``, which corresponds to all available
IPv4 and IPv6 addresses.
active manager) will bind to TCP port 8443 or 8080 when SSL is disabled.

If no specific address has been configured, the web app will bind to ``::``,
which corresponds to all available IPv4 and IPv6 addresses.

These defaults can be changed via the configuration key facility on a
cluster-wide level (so they apply to all manager instances) as follows::
Expand Down
87 changes: 49 additions & 38 deletions src/pybind/mgr/dashboard/module.py
Expand Up @@ -6,6 +6,7 @@

import errno
from distutils.version import StrictVersion
from distutils.util import strtobool
import os
import socket
import tempfile
Expand Down Expand Up @@ -86,7 +87,7 @@ class ServerConfigException(Exception):
pass


class SSLCherryPyConfig(object):
class CherryPyConfig(object):
"""
Class for common server configuration done by both active and
standby module, especially setting up SSL.
Expand All @@ -112,7 +113,12 @@ def _configure(self):
:returns our URI
"""
server_addr = self.get_localized_config('server_addr', '::')
server_port = self.get_localized_config('server_port', '8443')
ssl = strtobool(self.get_localized_config('ssl', 'True'))
def_server_port = 8443
if not ssl:
def_server_port = 8080

server_port = self.get_localized_config('server_port', def_server_port)
if server_addr is None:
raise ServerConfigException(
'no server_addr configured; '
Expand All @@ -126,49 +132,53 @@ def _configure(self):
cherrypy.tools.session_expire_at_browser_close = SessionExpireAtBrowserCloseTool()
cherrypy.tools.request_logging = RequestLoggingTool()

# SSL initialization
cert = self.get_store("crt")
if cert is not None:
self.cert_tmp = tempfile.NamedTemporaryFile()
self.cert_tmp.write(cert.encode('utf-8'))
self.cert_tmp.flush() # cert_tmp must not be gc'ed
cert_fname = self.cert_tmp.name
else:
cert_fname = self.get_localized_config('crt_file')

pkey = self.get_store("key")
if pkey is not None:
self.pkey_tmp = tempfile.NamedTemporaryFile()
self.pkey_tmp.write(pkey.encode('utf-8'))
self.pkey_tmp.flush() # pkey_tmp must not be gc'ed
pkey_fname = self.pkey_tmp.name
else:
pkey_fname = self.get_localized_config('key_file')

if not cert_fname or not pkey_fname:
raise ServerConfigException('no certificate configured')
if not os.path.isfile(cert_fname):
raise ServerConfigException('certificate %s does not exist' % cert_fname)
if not os.path.isfile(pkey_fname):
raise ServerConfigException('private key %s does not exist' % pkey_fname)

# Apply the 'global' CherryPy configuration.
config = {
'engine.autoreload.on': False,
'server.socket_host': server_addr,
'server.socket_port': int(server_port),
'server.ssl_module': 'builtin',
'server.ssl_certificate': cert_fname,
'server.ssl_private_key': pkey_fname,
'error_page.default': json_error_page,
'tools.request_logging.on': True
}

if ssl:
# SSL initialization
cert = self.get_store("crt")
if cert is not None:
self.cert_tmp = tempfile.NamedTemporaryFile()
self.cert_tmp.write(cert.encode('utf-8'))
self.cert_tmp.flush() # cert_tmp must not be gc'ed
cert_fname = self.cert_tmp.name
else:
cert_fname = self.get_localized_config('crt_file')

pkey = self.get_store("key")
if pkey is not None:
self.pkey_tmp = tempfile.NamedTemporaryFile()
self.pkey_tmp.write(pkey.encode('utf-8'))
self.pkey_tmp.flush() # pkey_tmp must not be gc'ed
pkey_fname = self.pkey_tmp.name
else:
pkey_fname = self.get_localized_config('key_file')

if not cert_fname or not pkey_fname:
raise ServerConfigException('no certificate configured')
if not os.path.isfile(cert_fname):
raise ServerConfigException('certificate %s does not exist' % cert_fname)
if not os.path.isfile(pkey_fname):
raise ServerConfigException('private key %s does not exist' % pkey_fname)

config['server.ssl_module'] = 'builtin'
config['server.ssl_certificate'] = cert_fname
config['server.ssl_private_key'] = pkey_fname

cherrypy.config.update(config)

self._url_prefix = prepare_url_prefix(self.get_config('url_prefix',
default=''))

uri = "https://{0}:{1}{2}/".format(
uri = "{0}://{1}:{2}{3}/".format(
'https' if ssl else 'http',
socket.getfqdn() if server_addr == "::" else server_addr,
server_port,
self.url_prefix
Expand Down Expand Up @@ -197,7 +207,7 @@ def await_configuration(self):
return uri


class Module(MgrModule, SSLCherryPyConfig):
class Module(MgrModule, CherryPyConfig):
"""
dashboard module entrypoint
"""
Expand Down Expand Up @@ -233,12 +243,13 @@ class Module(MgrModule, SSLCherryPyConfig):
{'name': 'username'},
{'name': 'key_file'},
{'name': 'crt_file'},
{'name': 'ssl'}
]
OPTIONS.extend(options_schema_list())

def __init__(self, *args, **kwargs):
super(Module, self).__init__(*args, **kwargs)
SSLCherryPyConfig.__init__(self)
CherryPyConfig.__init__(self)

mgr.init(self)

Expand Down Expand Up @@ -301,7 +312,7 @@ def serve(self):

def shutdown(self):
super(Module, self).shutdown()
SSLCherryPyConfig.shutdown(self)
CherryPyConfig.shutdown(self)
logger.info('Stopping engine...')
self.shutdown_event.set()

Expand Down Expand Up @@ -348,10 +359,10 @@ def notify(self, notify_type, notify_id):
NotificationQueue.new_notification(notify_type, notify_id)


class StandbyModule(MgrStandbyModule, SSLCherryPyConfig):
class StandbyModule(MgrStandbyModule, CherryPyConfig):
def __init__(self, *args, **kwargs):
super(StandbyModule, self).__init__(*args, **kwargs)
SSLCherryPyConfig.__init__(self)
CherryPyConfig.__init__(self)
self.shutdown_event = threading.Event()

# We can set the global mgr instance to ourselves even though
Expand Down Expand Up @@ -403,7 +414,7 @@ def index(self):
self.log.info("Engine stopped.")

def shutdown(self):
SSLCherryPyConfig.shutdown(self)
CherryPyConfig.shutdown(self)

self.log.info("Stopping engine...")
self.shutdown_event.set()
Expand Down