From 028c8e22e36bcaee30481cf05a9ddeb1b339c26d Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Fri, 25 Nov 2022 20:23:31 +0530 Subject: [PATCH] mgr/dashboard: allow cross origin when the url is set 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 (cherry picked from commit 1cf017bde492d3058d05b4375c1168cec9002afd) --- src/pybind/mgr/dashboard/module.py | 65 +++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/pybind/mgr/dashboard/module.py b/src/pybind/mgr/dashboard/module.py index 287e300a5e148..96f62178fda4d 100644 --- a/src/pybind/mgr/dashboard/module.py +++ b/src/pybind/mgr/dashboard/module.py @@ -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 []: -- 2.39.5