Skip to content

Commit

Permalink
Merge pull request #29088 from votdev/issue_24662
Browse files Browse the repository at this point in the history
mgr/dashboard: Allow disabling redirection on standby Dashboards

Reviewed-by: Ernesto Puerta <epuertat@redhat.com>
  • Loading branch information
rjfd committed Aug 2, 2019
2 parents 647d804 + 5bb9869 commit 1fb9c64
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 29 deletions.
72 changes: 65 additions & 7 deletions doc/mgr/dashboard.rst
Expand Up @@ -779,13 +779,6 @@ to allow direct connections to the manager nodes, you could set up a proxy that
automatically forwards incoming requests to the currently active ceph-mgr
instance.

.. note::
Note that putting the dashboard behind a load-balancing proxy like `HAProxy
<https://www.haproxy.org/>`_ currently has some limitations, particularly if
you require the traffic between the proxy and the dashboard to be encrypted
via SSL/TLS. See `BUG#24662 <https://tracker.ceph.com/issues/24662>`_ for
details.

Configuring a URL Prefix
^^^^^^^^^^^^^^^^^^^^^^^^

Expand All @@ -800,6 +793,71 @@ to use hyperlinks that include your prefix, you can set the

so you can access the dashboard at ``http://$IP:$PORT/$PREFIX/``.

Disable the redirection
^^^^^^^^^^^^^^^^^^^^^^^

If the dashboard is behind a load-balancing proxy like `HAProxy <https://www.haproxy.org/>`_
you might want to disable the redirection behaviour to prevent situations that
internal (unresolvable) URL's are published to the frontend client. Use the
following command to get the dashboard to respond with a HTTP error (500 by default)
instead of redirecting to the active dashboard::

$ ceph config set mgr mgr/dashboard/standby_behaviour "error"

To reset the setting to the default redirection behaviour, use the following command::

$ ceph config set mgr mgr/dashboard/standby_behaviour "redirect"

Configure the error status code
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

When the redirection behaviour is disabled, then you want to customize the HTTP status
code of standby dashboards. To do so you need to run the command::

$ ceph config set mgr mgr/dashboard/standby_error_status_code 503

HAProxy example configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Below you will find an example configuration for SSL/TLS pass through using
`HAProxy <https://www.haproxy.org/>`_.

Please note that the configuration works under the following conditions.
If the dashboard fails over, the front-end client might receive a HTTP redirect
(303) response and will be redirected to an unresolvable host. This happens when
the failover occurs during two HAProxy health checks. In this situation the
previously active dashboard node will now respond with a 303 which points to
the new active node. To prevent that situation you should consider to disable
the redirection behaviour on standby nodes.

::

defaults
log global
option log-health-checks
timeout connect 5s
timeout client 50s
timeout server 450s

frontend dashboard_front
mode http
bind *:80
option httplog
redirect scheme https code 301 if !{ ssl_fc }

frontend dashboard_front_ssl
mode tcp
bind *:443
option tcplog
default_backend dashboard_back_ssl

backend dashboard_back_ssl
mode tcp
option httpchk GET /
http-check expect status 200
server x <HOST>:<PORT> check-ssl check verify none
server y <HOST>:<PORT> check-ssl check verify none
server z <HOST>:<PORT> check-ssl check verify none

.. _dashboard-auditing:

Expand Down
44 changes: 44 additions & 0 deletions qa/tasks/mgr/test_dashboard.py
Expand Up @@ -20,6 +20,14 @@ def setUp(self):
self.mgr_cluster.mon_manager.raw_cluster_cmd("dashboard",
"create-self-signed-cert")

def tearDown(self):
self.mgr_cluster.mon_manager.raw_cluster_cmd("config", "set", "mgr",
"mgr/dashboard/standby_behaviour",
"redirect")
self.mgr_cluster.mon_manager.raw_cluster_cmd("config", "set", "mgr",
"mgr/dashboard/standby_error_status_code",
"500")

def test_standby(self):
original_active_id = self.mgr_cluster.get_active_id()
original_uri = self._get_uri("dashboard")
Expand All @@ -46,6 +54,42 @@ def test_standby(self):
self.assertEqual(r.status_code, 303)
self.assertEqual(r.headers['Location'], failed_over_uri)

def test_standby_disable_redirect(self):
self.mgr_cluster.mon_manager.raw_cluster_cmd("config", "set", "mgr",
"mgr/dashboard/standby_behaviour",
"error")

original_active_id = self.mgr_cluster.get_active_id()
original_uri = self._get_uri("dashboard")
log.info("Originally running manager '{}' at {}".format(
original_active_id, original_uri))

# Force a failover and wait until the previously active manager
# is listed as standby.
self.mgr_cluster.mgr_fail(original_active_id)
self.wait_until_true(
lambda: original_active_id in self.mgr_cluster.get_standby_ids(),
timeout=30)

failed_active_id = self.mgr_cluster.get_active_id()
failed_over_uri = self._get_uri("dashboard")
log.info("After failover running manager '{}' at {}".format(
failed_active_id, failed_over_uri))

self.assertNotEqual(original_uri, failed_over_uri)

# Redirection should be disabled now, instead a 500 must be returned.
r = requests.get(original_uri, allow_redirects=False, verify=False)
self.assertEqual(r.status_code, 500)

self.mgr_cluster.mon_manager.raw_cluster_cmd("config", "set", "mgr",
"mgr/dashboard/standby_error_status_code",
"503")

# The customized HTTP status code (503) must be returned.
r = requests.get(original_uri, allow_redirects=False, verify=False)
self.assertEqual(r.status_code, 503)

def test_urls(self):
base_uri = self._get_uri("dashboard")

Expand Down
52 changes: 30 additions & 22 deletions src/pybind/mgr/dashboard/module.py
Expand Up @@ -272,7 +272,11 @@ class Module(MgrModule, CherryPyConfig):
Option(name='username', type='str', default=''),
Option(name='key_file', type='str', default=''),
Option(name='crt_file', type='str', default=''),
Option(name='ssl', type='bool', default=True)
Option(name='ssl', type='bool', default=True),
Option(name='standby_behaviour', type='str', default='redirect',
enum_allowed=['redirect', 'error']),
Option(name='standby_error_status_code', type='int', default=500,
min=400, max=599)
]
MODULE_OPTIONS.extend(options_schema_list())
for options in PLUGIN_MANAGER.hook.get_options() or []:
Expand Down Expand Up @@ -470,28 +474,32 @@ def serve(self):
class Root(object):
@cherrypy.expose
def index(self):
active_uri = module.get_active_uri()
if active_uri:
module.log.info("Redirecting to active '%s'", active_uri)
raise cherrypy.HTTPRedirect(active_uri)
if module.get_module_option('standby_behaviour', 'redirect') == 'redirect':
active_uri = module.get_active_uri()
if active_uri:
module.log.info("Redirecting to active '%s'", active_uri)
raise cherrypy.HTTPRedirect(active_uri)
else:
template = """
<html>
<!-- Note: this is only displayed when the standby
does not know an active URI to redirect to, otherwise
a simple redirect is returned instead -->
<head>
<title>Ceph</title>
<meta http-equiv="refresh" content="{delay}">
</head>
<body>
No active ceph-mgr instance is currently running
the dashboard. A failover may be in progress.
Retrying in {delay} seconds...
</body>
</html>
"""
return template.format(delay=5)
else:
template = """
<html>
<!-- Note: this is only displayed when the standby
does not know an active URI to redirect to, otherwise
a simple redirect is returned instead -->
<head>
<title>Ceph</title>
<meta http-equiv="refresh" content="{delay}">
</head>
<body>
No active ceph-mgr instance is currently running
the dashboard. A failover may be in progress.
Retrying in {delay} seconds...
</body>
</html>
"""
return template.format(delay=5)
status = module.get_module_option('standby_error_status_code', 500)
raise cherrypy.HTTPError(status, message="Keep on looking")

cherrypy.tree.mount(Root(), "{}/".format(self.url_prefix), {})
self.log.info("Starting engine...")
Expand Down

0 comments on commit 1fb9c64

Please sign in to comment.