]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/cherrypy_backports.py
import quincy beta 17.1.0
[ceph.git] / ceph / src / pybind / mgr / dashboard / cherrypy_backports.py
CommitLineData
92f5a8d4
TL
1# -*- coding: utf-8 -*-
2"""
3Copyright © 2004-2019, CherryPy Team (team@cherrypy.org)
4
5All rights reserved.
6
7* * *
8
9Redistribution and use in source and binary forms, with or without
10modification, are permitted provided that the following conditions are met:
11
12* Redistributions of source code must retain the above copyright notice, this
13 list of conditions and the following disclaimer.
14
15* Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
18
19* Neither the name of CherryPy nor the names of its
20 contributors may be used to endorse or promote products derived from
21 this software without specific prior written permission.
22
23THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33"""
34
35from distutils.version import StrictVersion
36
37# The SSL code in CherryPy 3.5.0 is buggy. It was fixed long ago,
38# but 3.5.0 is still shipping in major linux distributions
39# (Fedora 27, Ubuntu Xenial), so we must monkey patch it to get SSL working.
40
41
42def patch_http_connection_init(v):
43 # It was fixed in 3.7.0. Exact lower bound version is probably earlier,
44 # but 3.5.0 is what this monkey patch is tested on.
45 if StrictVersion("3.5.0") <= v < StrictVersion("3.7.0"):
f67539c2 46 from cherrypy.wsgiserver.wsgiserver2 import CP_fileobject, HTTPConnection
92f5a8d4
TL
47
48 def fixed_init(hc_self, server, sock, makefile=CP_fileobject):
49 hc_self.server = server
50 hc_self.socket = sock
51 hc_self.rfile = makefile(sock, "rb", hc_self.rbufsize)
52 hc_self.wfile = makefile(sock, "wb", hc_self.wbufsize)
53 hc_self.requests_seen = 0
54
55 HTTPConnection.__init__ = fixed_init
56
57
58# When the CherryPy server in 3.2.2 (and later) starts it attempts to verify
59# that the ports its listening on are in fact bound. When using the any address
60# "::" it tries both ipv4 and ipv6, and in some environments (e.g. kubernetes)
61# ipv6 isn't yet configured / supported and CherryPy throws an uncaught
62# exception.
63def skip_wait_for_occupied_port(v):
64 # the issue was fixed in 3.2.3. it's present in 3.2.2 (current version on
65 # centos:7) and back to at least 3.0.0.
66 if StrictVersion("3.1.2") <= v < StrictVersion("3.2.3"):
67 # https://github.com/cherrypy/cherrypy/issues/1100
68 from cherrypy.process import servers
69 servers.wait_for_occupied_port = lambda host, port: None
70
71
72# cherrypy.wsgiserver was extracted wsgiserver into cheroot in cherrypy v9.0.0
73def patch_builtin_ssl_wrap(v, new_wrap):
74 if v < StrictVersion("9.0.0"):
75 from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter as builtin_ssl
76 else:
20effc67 77 from cheroot.ssl.builtin import BuiltinSSLAdapter as builtin_ssl # type: ignore
92f5a8d4
TL
78 builtin_ssl.wrap = new_wrap(builtin_ssl.wrap)
79
80
81def accept_exceptions_from_builtin_ssl(v):
82 # the fix was included by cheroot v5.2.0, which was included by cherrypy
83 # 10.2.0.
84 if v < StrictVersion("10.2.0"):
85 # see https://github.com/cherrypy/cheroot/pull/4
86 import ssl
87
88 def accept_ssl_errors(func):
89 def wrapper(self, sock):
90 try:
91 return func(self, sock)
92 except ssl.SSLError as e:
93 if e.errno == ssl.SSL_ERROR_SSL:
94 # Check if it's one of the known errors
95 # Errors that are caught by PyOpenSSL, but thrown by
96 # built-in ssl
adb31ebb
TL
97 _block_errors = ('unknown protocol', 'unknown ca', 'unknown_ca',
98 'unknown error',
99 'https proxy request', 'inappropriate fallback',
92f5a8d4
TL
100 'wrong version number',
101 'no shared cipher', 'certificate unknown',
adb31ebb
TL
102 'ccs received early',
103 'certificate verify failed', # client cert w/o trusted CA
104 'version too low', # caused by SSL3 connections
105 'unsupported protocol', # caused by TLS1 connections
106 'sslv3 alert bad certificate')
92f5a8d4
TL
107 for error_text in _block_errors:
108 if error_text in e.args[1].lower():
109 # Accepted error, let's pass
110 return None, {}
111 raise
112 return wrapper
113 patch_builtin_ssl_wrap(v, accept_ssl_errors)
114
115
116def accept_socket_error_0(v):
117 # see https://github.com/cherrypy/cherrypy/issues/1618
118 try:
119 import cheroot
120 cheroot_version = cheroot.__version__
121 except ImportError:
122 pass
123
124 if v < StrictVersion("9.0.0") or cheroot_version < StrictVersion("6.5.5"):
f67539c2 125 generic_socket_error = OSError
92f5a8d4
TL
126
127 def accept_socket_error_0(func):
128 def wrapper(self, sock):
129 try:
130 return func(self, sock)
131 except generic_socket_error as e:
132 """It is unclear why exactly this happens.
133
134 It's reproducible only with openssl>1.0 and stdlib ``ssl`` wrapper.
135 In CherryPy it's triggered by Checker plugin, which connects
136 to the app listening to the socket port in TLS mode via plain
137 HTTP during startup (from the same process).
138
139 Ref: https://github.com/cherrypy/cherrypy/issues/1618
140 """
141 import ssl
142 is_error0 = e.args == (0, 'Error')
143 IS_ABOVE_OPENSSL10 = ssl.OPENSSL_VERSION_INFO >= (1, 1)
144 del ssl
145 if is_error0 and IS_ABOVE_OPENSSL10:
146 return None, {}
147 raise
148 return wrapper
149 patch_builtin_ssl_wrap(v, accept_socket_error_0)
150
151
152def patch_request_unique_id(v):
153 """
154 Older versions of cherrypy don't include request.unique_id field (a lazily
155 calculated UUID4).
156
157 Monkey-patching is preferred over alternatives as inheritance, as it'd break
158 type checks (cherrypy/lib/cgtools.py: `isinstance(obj, _cprequest.Request)`)
159 """
160 if v < StrictVersion('11.1.0'):
161 import uuid
162 from functools import update_wrapper
f67539c2 163
92f5a8d4
TL
164 from cherrypy._cprequest import Request
165
166 class LazyUUID4(object):
167 def __str__(self):
168 """Return UUID4 and keep it for future calls."""
169 return str(self.uuid4)
170
171 @property
172 def uuid4(self):
173 """Provide unique id on per-request basis using UUID4.
174 It's evaluated lazily on render.
175 """
176 try:
9f95a23c 177 self._uuid4 # type: ignore
92f5a8d4
TL
178 except AttributeError:
179 # evaluate on first access
180 self._uuid4 = uuid.uuid4()
181
182 return self._uuid4
183
184 old_init = Request.__init__
185
186 def init_with_unique_id(self, *args, **kwargs):
187 old_init(self, *args, **kwargs)
188 self.unique_id = LazyUUID4()
189
190 Request.__init__ = update_wrapper(init_with_unique_id, old_init)
191
192
193def patch_cherrypy(v):
194 patch_http_connection_init(v)
195 skip_wait_for_occupied_port(v)
196 accept_exceptions_from_builtin_ssl(v)
197 accept_socket_error_0(v)
198 patch_request_unique_id(v)