diff --git a/doc/mgr/dashboard.rst b/doc/mgr/dashboard.rst
index 8c2ba4a53401b..456a7941d5e86 100644
--- a/doc/mgr/dashboard.rst
+++ b/doc/mgr/dashboard.rst
@@ -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
- `_ 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 `_ for
- details.
-
Configuring a URL Prefix
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -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 `_
+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 `_.
+
+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 : check-ssl check verify none
+ server y : check-ssl check verify none
+ server z : check-ssl check verify none
.. _dashboard-auditing:
diff --git a/qa/tasks/mgr/test_dashboard.py b/qa/tasks/mgr/test_dashboard.py
index 3b778520da872..b0cf200d61be4 100644
--- a/qa/tasks/mgr/test_dashboard.py
+++ b/qa/tasks/mgr/test_dashboard.py
@@ -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")
@@ -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")
diff --git a/src/pybind/mgr/dashboard/module.py b/src/pybind/mgr/dashboard/module.py
index 2486a164dc705..b080d17b0acd2 100644
--- a/src/pybind/mgr/dashboard/module.py
+++ b/src/pybind/mgr/dashboard/module.py
@@ -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 []:
@@ -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 = """
+
+
+
+ Ceph
+
+
+
+ No active ceph-mgr instance is currently running
+ the dashboard. A failover may be in progress.
+ Retrying in {delay} seconds...
+
+
+ """
+ return template.format(delay=5)
else:
- template = """
-
-
-
- Ceph
-
-
-
- No active ceph-mgr instance is currently running
- the dashboard. A failover may be in progress.
- Retrying in {delay} seconds...
-
-
- """
- 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...")