diff options
Diffstat (limited to '')
-rw-r--r-- | src/pybind/mgr/dashboard/cherrypy_backports.py | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/cherrypy_backports.py b/src/pybind/mgr/dashboard/cherrypy_backports.py new file mode 100644 index 000000000..8871004fe --- /dev/null +++ b/src/pybind/mgr/dashboard/cherrypy_backports.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- +""" +Copyright © 2004-2019, CherryPy Team (team@cherrypy.org) + +All rights reserved. + +* * * + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of CherryPy nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from pkg_resources import parse_version + +# The SSL code in CherryPy 3.5.0 is buggy. It was fixed long ago, +# but 3.5.0 is still shipping in major linux distributions +# (Fedora 27, Ubuntu Xenial), so we must monkey patch it to get SSL working. + + +def patch_http_connection_init(v): + # It was fixed in 3.7.0. Exact lower bound version is probably earlier, + # but 3.5.0 is what this monkey patch is tested on. + if parse_version("3.5.0") <= v < parse_version("3.7.0"): + from cherrypy.wsgiserver.wsgiserver2 import CP_fileobject, HTTPConnection + + def fixed_init(hc_self, server, sock, makefile=CP_fileobject): + hc_self.server = server + hc_self.socket = sock + hc_self.rfile = makefile(sock, "rb", hc_self.rbufsize) + hc_self.wfile = makefile(sock, "wb", hc_self.wbufsize) + hc_self.requests_seen = 0 + + HTTPConnection.__init__ = fixed_init + + +# When the CherryPy server in 3.2.2 (and later) starts it attempts to verify +# that the ports its listening on are in fact bound. When using the any address +# "::" it tries both ipv4 and ipv6, and in some environments (e.g. kubernetes) +# ipv6 isn't yet configured / supported and CherryPy throws an uncaught +# exception. +def skip_wait_for_occupied_port(v): + # the issue was fixed in 3.2.3. it's present in 3.2.2 (current version on + # centos:7) and back to at least 3.0.0. + if parse_version("3.1.2") <= v < parse_version("3.2.3"): + # https://github.com/cherrypy/cherrypy/issues/1100 + from cherrypy.process import servers + servers.wait_for_occupied_port = lambda host, port: None + + +# cherrypy.wsgiserver was extracted wsgiserver into cheroot in cherrypy v9.0.0 +def patch_builtin_ssl_wrap(v, new_wrap): + if v < parse_version("9.0.0"): + from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter as builtin_ssl + else: + from cheroot.ssl.builtin import BuiltinSSLAdapter as builtin_ssl # type: ignore + builtin_ssl.wrap = new_wrap(builtin_ssl.wrap) + + +def accept_exceptions_from_builtin_ssl(v): + # the fix was included by cheroot v5.2.0, which was included by cherrypy + # 10.2.0. + if v < parse_version("10.2.0"): + # see https://github.com/cherrypy/cheroot/pull/4 + import ssl + + def accept_ssl_errors(func): + def wrapper(self, sock): + try: + return func(self, sock) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_SSL: + # Check if it's one of the known errors + # Errors that are caught by PyOpenSSL, but thrown by + # built-in ssl + _block_errors = ('unknown protocol', 'unknown ca', 'unknown_ca', + 'unknown error', + 'https proxy request', 'inappropriate fallback', + 'wrong version number', + 'no shared cipher', 'certificate unknown', + 'ccs received early', + 'certificate verify failed', # client cert w/o trusted CA + 'version too low', # caused by SSL3 connections + 'unsupported protocol', # caused by TLS1 connections + 'sslv3 alert bad certificate') + for error_text in _block_errors: + if error_text in e.args[1].lower(): + # Accepted error, let's pass + return None, {} + raise + return wrapper + patch_builtin_ssl_wrap(v, accept_ssl_errors) + + +def accept_socket_error_0(v): + # see https://github.com/cherrypy/cherrypy/issues/1618 + try: + import cheroot + cheroot_version = parse_version(cheroot.__version__) + except ImportError: + pass + + if v < parse_version("9.0.0") or cheroot_version < parse_version("6.5.5"): + generic_socket_error = OSError + + def accept_socket_error_0(func): + def wrapper(self, sock): + try: + return func(self, sock) + except generic_socket_error as e: + """It is unclear why exactly this happens. + + It's reproducible only with openssl>1.0 and stdlib ``ssl`` wrapper. + In CherryPy it's triggered by Checker plugin, which connects + to the app listening to the socket port in TLS mode via plain + HTTP during startup (from the same process). + + Ref: https://github.com/cherrypy/cherrypy/issues/1618 + """ + import ssl + is_error0 = e.args == (0, 'Error') + IS_ABOVE_OPENSSL10 = ssl.OPENSSL_VERSION_INFO >= (1, 1) + del ssl + if is_error0 and IS_ABOVE_OPENSSL10: + return None, {} + raise + return wrapper + patch_builtin_ssl_wrap(v, accept_socket_error_0) + + +def patch_request_unique_id(v): + """ + Older versions of cherrypy don't include request.unique_id field (a lazily + calculated UUID4). + + Monkey-patching is preferred over alternatives as inheritance, as it'd break + type checks (cherrypy/lib/cgtools.py: `isinstance(obj, _cprequest.Request)`) + """ + if v < parse_version('11.1.0'): + import uuid + from functools import update_wrapper + + from cherrypy._cprequest import Request + + class LazyUUID4(object): + def __str__(self): + """Return UUID4 and keep it for future calls.""" + return str(self.uuid4) + + @property + def uuid4(self): + """Provide unique id on per-request basis using UUID4. + It's evaluated lazily on render. + """ + try: + self._uuid4 # type: ignore + except AttributeError: + # evaluate on first access + self._uuid4 = uuid.uuid4() + + return self._uuid4 + + old_init = Request.__init__ + + def init_with_unique_id(self, *args, **kwargs): + old_init(self, *args, **kwargs) + self.unique_id = LazyUUID4() + + Request.__init__ = update_wrapper(init_with_unique_id, old_init) + + +def patch_cherrypy(v): + ver = parse_version(v) + patch_http_connection_init(ver) + skip_wait_for_occupied_port(ver) + accept_exceptions_from_builtin_ssl(ver) + accept_socket_error_0(ver) + patch_request_unique_id(ver) |