]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: allow cross origin when the url is set 49151/head
authorNizamudeen A <nia@redhat.com>
Fri, 25 Nov 2022 14:53:31 +0000 (20:23 +0530)
committerNizamudeen A <nia@redhat.com>
Wed, 30 Nov 2022 06:23:17 +0000 (11:53 +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>
(cherry picked from commit 1cf017bde492d3058d05b4375c1168cec9002afd)

src/pybind/mgr/dashboard/module.py

index 287e300a5e1486c56c9ef0d62912c9190ed8a21c..96f62178fda4d179a44b40c614a0323c93fc848c 100644 (file)
@@ -121,6 +121,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),
@@ -223,6 +224,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']
@@ -273,7 +335,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 []: