Created
February 23, 2017 21:14
-
-
Save wolever/30fa147c407f97dd837414a56e044cd4 to your computer and use it in GitHub Desktop.
Correctly monkeypatch gevent, PyOpenSSL, and urllib3 to all play well together and support SNI.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
The correct set of steps to monkeypatch: | |
- gevent into threading, socket, etc | |
- PyOpenSSL with gevent (via gevent_openssl) | |
- PyOpenSSL to be compatible with | |
- urllib3 and requests.packages.urllib3 to use PyOpenSSL, which | |
lets them connect to servers using SNI | |
""" | |
import logging | |
did_init = False | |
def _patch_urllib3(module): | |
from django.utils.module_loading import import_string | |
try: | |
inject_into_urllib3 = import_string(module + ".contrib.pyopenssl.inject_into_urllib3") | |
inject_into_urllib3() | |
except Exception as e: | |
import gevent | |
def log_error(): | |
# Give the rest of Django a chance to warm up and configure | |
# loggers, otherwise this message won't go anywhere. | |
gevent.sleep(1) | |
log = logging.getLogger("akindi.__init__") | |
log.error( | |
"Error injecting PyOpenSSL into %s: %s " | |
"(things will still work, but SSL certificates " | |
"with SNI will fail to validate)", | |
module, e, | |
) | |
gevent.spawn(log_error) | |
def _patch_openssl(): | |
""" Stock PyOpenSSL throws a TypeError if the domain name is unicode. """ | |
from gevent_openssl.SSL import _real_connection as Connection | |
old_set_tlsext_host_name = Connection.set_tlsext_host_name | |
def set_tlsext_host_name(self, name): | |
if isinstance(name, unicode): | |
name = name.encode("idna") | |
return old_set_tlsext_host_name(self, name) | |
set_tlsext_host_name.old = old_set_tlsext_host_name | |
Connection.set_tlsext_host_name = set_tlsext_host_name | |
def init_app(): | |
""" Initialize the app. This should be called as early as possible, either | |
from manage.py or app.wsgi. """ | |
global did_init | |
if did_init: | |
return | |
from gevent import monkey | |
monkey.patch_all(subprocess=True) | |
import gevent_openssl | |
gevent_openssl.monkey_patch() | |
from gevent_openssl.SSL import Connection | |
def Connection_sendall_fixed(self, buf, flags=0): | |
# There is a bug with gevent which causes `sendall` to crash when | |
# the buffer is large. Work around this by manually implementing | |
# `sendall`. See also: https://github.com/gevent/gevent/issues/736 | |
while buf: | |
sent = self.send(buf, flags) | |
buf = buf[sent:] | |
Connection.sendall = Connection_sendall_fixed | |
_patch_openssl() | |
_patch_urllib3("urllib3") | |
_patch_urllib3("requests.packages.urllib3") | |
did_init = True |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment