X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=ceph%2Fsrc%2Fpybind%2Fmgr%2Fdashboard%2Fmodule.py;h=3b3519e7bf0f446d8288590def6904307aa62284;hb=39ae355f72b1d71f2212a99f2bd9f6c1e0d35528;hp=946c853f880d66330d91fa3940be94553d16f363;hpb=e04241aa9b639588fa6c864845287d2824cb6b55;p=ceph.git diff --git a/ceph/src/pybind/mgr/dashboard/module.py b/ceph/src/pybind/mgr/dashboard/module.py index 946c853f8..3b3519e7b 100644 --- a/ceph/src/pybind/mgr/dashboard/module.py +++ b/ceph/src/pybind/mgr/dashboard/module.py @@ -6,12 +6,14 @@ import collections import errno import logging import os +import socket import ssl import sys import tempfile import threading import time from typing import TYPE_CHECKING, Optional +from urllib.parse import urlparse if TYPE_CHECKING: if sys.version_info >= (3, 8): @@ -117,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), @@ -219,6 +222,70 @@ 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_cross_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_cross_origin_url in cross_origin_url_list: + resp_head['Access-Control-Allow-Origin'] = req_header_cross_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': + req_header_origin_url = req_head.get('Origin') + if req_header_origin_url in cross_origin_url_list: + resp_head['Access-Control-Allow-Origin'] = req_header_origin_url + 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'] @@ -268,7 +335,9 @@ class Module(MgrModule, CherryPyConfig): 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) + min=400, max=599), + 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 []: @@ -525,6 +594,13 @@ class StandbyModule(MgrStandbyModule, CherryPyConfig): return None if active_uri: + if module.get_module_option('redirect_resolve_ip_addr'): + p_result = urlparse(active_uri) + hostname = str(p_result.hostname) + fqdn_netloc = p_result.netloc.replace( + hostname, socket.getfqdn(hostname)) + active_uri = p_result._replace(netloc=fqdn_netloc).geturl() + module.log.info("Redirecting to active '%s'", active_uri) raise cherrypy.HTTPRedirect(active_uri) else: