]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: allow cross origin when the url is set 49060/head
authorNizamudeen A <nia@redhat.com>
Fri, 25 Nov 2022 14:53:31 +0000 (20:23 +0530)
committerNizamudeen A <nia@redhat.com>
Mon, 28 Nov 2022 17:14:03 +0000 (22:44 +0530)
Allow CORS when the cross_origin_url is set in the config opt.

you have to update the cross_origin_url setting with the url of the
requesting entity.

The request needs to have the header `Access-Control-Allow-Origin`
with the origin URL

The url can be set using this command
`ceph config set mgr mgr/dashboard/cross_origin_url
http://localhost:4200`

multiple urls can be set as
`ceph config set mgr mgr/dashboard/cross_origin_url
http://localhost:4200,http://localhost:4201`

If multiple url is provided in the configuration option, then whatever
url is there in the Access-Control-Allow-Origin request header will be
allowed for CORS

Once the URL is set you have to restart the dashboard module to restart
the cherrypy server with the new CORS policies

Fixes: https://tracker.ceph.com/issues/58086
Signed-off-by: Nizamudeen A <nia@redhat.com>
src/pybind/mgr/dashboard/module.py

index c82df154136ab32a8b0429fedccf415e3092e77f..d052e12e9f75c4a95e60d783a1fd3f26ea047eec 100644 (file)
@@ -119,6 +119,7 @@ class CherryPyConfig(object):
 
         # Initialize custom handlers.
         cherrypy.tools.authenticate = AuthManagerTool()
+        self.configure_cors()
         cherrypy.tools.plugin_hooks_filter_request = cherrypy.Tool(
             'before_handler',
             lambda: PLUGIN_MANAGER.hook.filter_request_before_handler(request=cherrypy.request),
@@ -221,6 +222,67 @@ class CherryPyConfig(object):
                 self.log.info("Configured CherryPy, starting engine...")  # type: ignore
                 return uri
 
+    def configure_cors(self):
+        """
+        Allow CORS requests if the cross_origin_url option is set.
+        """
+        cross_origin_url = mgr.get_localized_module_option('cross_origin_url', '')
+        if cross_origin_url:
+            cherrypy.tools.CORS = cherrypy.Tool('before_handler', self.cors_tool)
+            config = {
+                'tools.CORS.on': True,
+            }
+            self.update_cherrypy_config(config)
+
+    def cors_tool(self):
+        '''
+        Handle both simple and complex CORS requests
+
+        Add CORS headers to each response. If the request is a CORS preflight
+        request swap out the default handler with a simple, single-purpose handler
+        that verifies the request and provides a valid CORS response.
+        '''
+        req_head = cherrypy.request.headers
+        resp_head = cherrypy.response.headers
+
+        # Always set response headers necessary for 'simple' CORS.
+        req_header_origin_url = req_head.get('Access-Control-Allow-Origin')
+        cross_origin_urls = mgr.get_localized_module_option('cross_origin_url', '')
+        cross_origin_url_list = [url.strip() for url in cross_origin_urls.split(',')]
+        if req_header_origin_url in cross_origin_url_list:
+            resp_head['Access-Control-Allow-Origin'] = req_header_origin_url
+        resp_head['Access-Control-Expose-Headers'] = 'GET, POST'
+        resp_head['Access-Control-Allow-Credentials'] = 'true'
+
+        # Non-simple CORS preflight request; short-circuit the normal handler.
+        if cherrypy.request.method == 'OPTIONS':
+            ac_method = req_head.get('Access-Control-Request-Method', None)
+
+            allowed_methods = ['GET', 'POST']
+            allowed_headers = [
+                'Content-Type',
+                'Authorization',
+                'Accept',
+                'Access-Control-Allow-Origin'
+            ]
+
+            if ac_method and ac_method in allowed_methods:
+                resp_head['Access-Control-Allow-Methods'] = ', '.join(allowed_methods)
+                resp_head['Access-Control-Allow-Headers'] = ', '.join(allowed_headers)
+
+                resp_head['Connection'] = 'keep-alive'
+                resp_head['Access-Control-Max-Age'] = '3600'
+
+            # CORS requests should short-circuit the other tools.
+            cherrypy.response.body = ''.encode('utf8')
+            cherrypy.response.status = 200
+            cherrypy.serving.request.handler = None
+
+            # Needed to avoid the auth_tool check.
+            if cherrypy.request.config.get('tools.sessions.on', False):
+                cherrypy.session['token'] = True
+            return True
+
 
 if TYPE_CHECKING:
     SslConfigKey = Literal['crt', 'key']
@@ -271,7 +333,8 @@ class Module(MgrModule, CherryPyConfig):
                enum_allowed=['redirect', 'error']),
         Option(name='standby_error_status_code', type='int', default=500,
                min=400, max=599),
-        Option(name='redirect_resolve_ip_addr', type='bool', default=False)
+        Option(name='redirect_resolve_ip_addr', type='bool', default=False),
+        Option(name='cross_origin_url', type='str', default=''),
     ]
     MODULE_OPTIONS.extend(options_schema_list())
     for options in PLUGIN_MANAGER.hook.get_options() or []: