]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
SSL-enabled dashboard does not play nicely with a frontend HAproxy. To fix that issue... 30382/head
authorVolker Theile <vtheile@suse.com>
Tue, 16 Jul 2019 13:44:36 +0000 (15:44 +0200)
committerNathan Cutler <ncutler@suse.com>
Fri, 13 Sep 2019 21:11:53 +0000 (23:11 +0200)
1. Disable redirection on standby managers. A HTTP error (500) will be returned instead of a redirection.
   $ ceph config set mgr mgr/dashboard/standby_behaviour "error"

2. Configure the HTTP error status code.
   $ ceph config set mgr mgr/dashboard/standby_error_status_code 503

Signed-off-by: Volker Theile <vtheile@suse.com>
(cherry picked from commit 5bb986995af50b1049d8631d9cecddfc4cd941f2)

doc/mgr/dashboard.rst
qa/tasks/mgr/test_dashboard.py
src/pybind/mgr/dashboard/module.py

index bcec83414a7794b1b6162f3bf968fc3531edc687..8ba2111d2ac18315d65926d18047a0bcf8a61b5f 100644 (file)
@@ -772,13 +772,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
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -793,6 +786,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:
 
index 3b778520da872f93f15aea1f28151b3b56e358de..b0cf200d61be46ef312e3ca9751759e7d9aac703 100644 (file)
@@ -20,6 +20,14 @@ class TestDashboard(MgrTestCase):
         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 @@ class TestDashboard(MgrTestCase):
         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")
 
index 868ccdb557ec331f739abd6114d7a930604d6663..41e2070c246ddea11fd1c36ef9ed7c9e9e6bc4e0 100644 (file)
@@ -312,7 +312,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 []:
@@ -510,28 +514,32 @@ class StandbyModule(MgrStandbyModule, CherryPyConfig):
         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...")