handle ssl connections.

gunicorn is now supporting ssl connections:

    $ gunicorn -w3 --certfile=server.crt --keyfile=server.key test:app

works with all supported workers
This commit is contained in:
benoitc 2012-12-14 10:14:51 +01:00
parent b7b51adf13
commit 1198cc2723
9 changed files with 134 additions and 5 deletions

21
examples/server.crt Normal file
View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDdDCCAlwCCQC3MfdcOMwt6DANBgkqhkiG9w0BAQUFADB8MQswCQYDVQQGEwJG
UjERMA8GA1UECBMIUGljYXJkaWUxDjAMBgNVBAcTBUNyZWlsMREwDwYDVQQKEwhn
dW5pY29ybjEVMBMGA1UEAxMMZ3VuaWNvcm4ub3JnMSAwHgYJKoZIhvcNAQkBFhF1
c2VyQGd1bmljb3JuLm9yZzAeFw0xMjEyMTQwODI2MDJaFw0xMzEyMTQwODI2MDJa
MHwxCzAJBgNVBAYTAkZSMREwDwYDVQQIEwhQaWNhcmRpZTEOMAwGA1UEBxMFQ3Jl
aWwxETAPBgNVBAoTCGd1bmljb3JuMRUwEwYDVQQDEwxndW5pY29ybi5vcmcxIDAe
BgkqhkiG9w0BCQEWEXVzZXJAZ3VuaWNvcm4ub3JnMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAy9RQSiGpB+HyjMpRCEfV9M/4g7gXq/qRizxDspJujoBz
SW0d4FqMHaSRX2QOA+euhtlOYTgsvWZcyv5cvDfL1CtrNWSVBrlo7wIy5tg60Z3A
JnWT/Zxj4WIqkPwdglB1sRBsI1Fn0o6nJu4HekZedXDK6fua4lOPfsQG84EhRQKS
Mz2o7Nesk8/UMjb+5WoRmG7mxrpe0/OYlnydqzqwHUQ+I5CHl1kOhePo9ZBTFMA5
Ece8kGQs37rFCEy92xCYHDgp+CjjyYbeBskF3o0/a88K2bt8J7uXkn4h14HjtFHq
fYnqn60cwyIx3T/uMUh6EmhKQezaw60xyIivmjH8tQIDAQABMA0GCSqGSIb3DQEB
BQUAA4IBAQAKu7kzTAqONFI1qC6mnwAixSd7ml6RtyQRiIWjg4FyTJmS2NMlqUSI
CiV1g1+pv2cy9amld8hoO17ISYFZqMoRxJgD5GuN4y1lUefFe95GHI9loubIJZlR
5KlZEvCiaAQoGvYiacf4BNkljyrwgPVM5e71dGon7jyghmV6yUaUL6+1J8BU/KYg
jz8RtMtptqkwKPKQVfuDcr/eoH6uZwPRbyfqSui8SuMz3Df6Dnx1hOtlQRJC6eNo
U9L3jkmCsbbMNBAz6iQjyFHFa9iqzwL7nvqZTryjmI5Dpn+BnT7Q5cduK+N5vt4+
RjNVrz/l6+nR68B5GO96zUTV3/KrEmFr
-----END CERTIFICATE-----

27
examples/server.key Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAy9RQSiGpB+HyjMpRCEfV9M/4g7gXq/qRizxDspJujoBzSW0d
4FqMHaSRX2QOA+euhtlOYTgsvWZcyv5cvDfL1CtrNWSVBrlo7wIy5tg60Z3AJnWT
/Zxj4WIqkPwdglB1sRBsI1Fn0o6nJu4HekZedXDK6fua4lOPfsQG84EhRQKSMz2o
7Nesk8/UMjb+5WoRmG7mxrpe0/OYlnydqzqwHUQ+I5CHl1kOhePo9ZBTFMA5Ece8
kGQs37rFCEy92xCYHDgp+CjjyYbeBskF3o0/a88K2bt8J7uXkn4h14HjtFHqfYnq
n60cwyIx3T/uMUh6EmhKQezaw60xyIivmjH8tQIDAQABAoIBAQDFzhTc3C2daLhp
yS06S/xmyCz0JwNR8qir5qAL++8ue5ll+G61+yle2wX4/LBdOck1NE3MKye/5kbG
+HImdj9od3pjJmk5TVV4HToorE7ofZ6rtA8aX1rOruWALiq0/EA6xSUsYSPQQoAU
V4sKLqAceIly6Kk2WsE21CWqyfXvcQOtfBYmFwmPCImWecJLypeheEpz1U2EYl65
u6b0NsXeODrLXEAEFjdb6UBJtzRtTJ/OnbDvghu9xMjlT0Pj+inoAv/ePZB8bmvH
XGhZo7dzgsDZ+eys7XnbeggUOhFImzCjO1f3pIVXWThGDgKIrpc9Evac2Q3AjTFY
NV9HBV9BAoGBAOyWq7HDgeCEu54orPAmdkO4j/HFX+262BTQoVCg4OX3Iv9A/lH4
lpVGrFlK0qJF9jb7mjDmXP2LW0fwzyHe42DGFbZkKdfiMBuE+qoPeAV9s+SjE4H3
l3tHoAOFUt2wITcHK4EYjoLMAgrbRNiv9t/gqiMm1oIb3fkUbpOoGG3LAoGBANyN
kLop3JfN1Kzto7gJq/tLS21joexTU+s4EJ+a4Q8KH1d47DLI+4119Ot+NWwi9X3S
sbOKZOjfrGw9+HPI64i7hD9HPSK58IUbsfVR9vPlPei45inRfi6s7+EUzKifOKZU
o1ecpOSPYQHZtDToGcQCTS0IFwMXHgrkP380We9/AoGBAI1wljyz8RVUxQWMs7bu
h4187TFRGkR5i20GPSqCw3E4CkgnhuNihkO/+JF5VeuFf+jnCgtp7PX3Nh8QLATH
x4+3XIup3goeQzxwh5rbnJlLyRxLEgKFDp6490SjlCLMhU7sjmmjUK+JXz82TzZs
HF9DZPOW6G7oUg/y0xibSd95AoGAXpDEcU3pq50xh0QNYqei+gh6uthxYScJYF2V
oxmBTjWE4riSbeQHF8xvy1k+BrOmluB0GQtJ4R+minK3yM1pUCM2vPsKl40qN6h8
UTdnr4OnW9WLunp8o/66i8OjTNmYLJk1wCcF/IoNigGSZuztv0FNXfWOCGEtHHZp
U11bAnkCgYEAoU0sdFL3IfmxnNQ9CDmgXdJM0SpUm4ECd2jM/fRdgLelL+WislCF
gHjZw+3mplIzqQ9DMwRkjbaIxP0H92OopOBIqmShWUuzWw/Dj0L8PGe/7skcwsGD
/VLEkGzrxJwP4kokUu1kvLOqHM429JXsb8wO16iMQAB93yUZ+X8PGfQ=
-----END RSA PRIVATE KEY-----

View File

@ -115,6 +115,21 @@ class Config(object):
logger_class.install() logger_class.install()
return logger_class return logger_class
@property
def is_ssl(self):
return self.certfile or self.keyfile
@property
def ssl_options(self):
opts = {}
if self.certfile:
opts['certfile'] = self.certfile
if self.keyfile:
opts['keyfile'] = self.keyfile
return opts
class SettingMeta(type): class SettingMeta(type):
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
@ -1066,3 +1081,25 @@ class ProxyAllowFrom(Setting):
desc = """\ desc = """\
Front-end's IPs from which allowed accept proxy requests (comma separate). Front-end's IPs from which allowed accept proxy requests (comma separate).
""" """
class KeyFile(Setting):
name = "keyfile"
section = "Ssl"
cli = ["--keyfile"]
meta = "FILE"
validator = validate_string
default = None
desc = """\
SSL key file
"""
class CertFile(Setting):
name = "certfile"
section = "Ssl"
cli = ["--certfile"]
meta = "FILE"
validator = validate_string
default = None
desc = """\
SSL certificate file
"""

View File

@ -117,6 +117,14 @@ def create_sockets(conf, log):
laddr = conf.address laddr = conf.address
listeners = [] listeners = []
# check ssl config early to raise the error on startup
# only the certfile is needed since it can contains the keyfile
if conf.certfile and not os.path.exists(conf.certfile):
raise ValueError('certfile "%s" does not exist' % conf.certfile)
if conf.keyfile and not os.path.exists(conf.keyfile):
raise ValueError('certfile "%s" does not exist' % conf.keyfile)
# sockets are already bound # sockets are already bound
if 'GUNICORN_FD' in os.environ: if 'GUNICORN_FD' in os.environ:
fds = os.environ.pop('GUNICORN_FD').split(',') fds = os.environ.pop('GUNICORN_FD').split(',')

View File

@ -3,11 +3,10 @@
# This file is part of gunicorn released under the MIT license. # This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information. # See the NOTICE for more information.
from __future__ import with_statement
from datetime import datetime from datetime import datetime
import errno import errno
import socket import socket
import ssl
import gunicorn.http as http import gunicorn.http as http
import gunicorn.http.wsgi as wsgi import gunicorn.http.wsgi as wsgi
@ -47,10 +46,19 @@ class AsyncWorker(base.Worker):
self.log.debug("Ignored premature client disconnection. %s", e) self.log.debug("Ignored premature client disconnection. %s", e)
except StopIteration as e: except StopIteration as e:
self.log.debug("Closing connection. %s", e) self.log.debug("Closing connection. %s", e)
except ssl.SSLError:
raise # pass to next try-except level
except socket.error: except socket.error:
raise # pass to next try-except level raise # pass to next try-except level
except Exception as e: except Exception as e:
self.handle_error(req, client, addr, e) self.handle_error(req, client, addr, e)
except ssl.SSLError as e:
if e.args[0] == ssl.SSL_ERROR_EOF:
self.log.debug("ssl connection closed")
client.close()
else:
self.log.debug("Error processing SSL request.")
self.handle_error(req, client, addr, e)
except socket.error as e: except socket.error as e:
if e.args[0] not in (errno.EPIPE, errno.ECONNRESET): if e.args[0] not in (errno.EPIPE, errno.ECONNRESET):
self.log.exception("Socket error processing request.") self.log.exception("Socket error processing request.")

View File

@ -31,6 +31,14 @@ class EventletWorker(AsyncWorker):
def timeout_ctx(self): def timeout_ctx(self):
return eventlet.Timeout(self.cfg.keepalive or None, False) return eventlet.Timeout(self.cfg.keepalive or None, False)
def handle(self, listener, client, addr):
if self.cfg.is_ssl:
client = eventlet.wrap_ssl(client, server_side=True,
do_handshake_on_connect=False,
**self.cfg.ssl_options)
super(EventletWorker, self).handle(listener, client, addr)
def run(self): def run(self):
acceptors = [] acceptors = []
for sock in self.sockets: for sock in self.sockets:

View File

@ -55,16 +55,22 @@ class GeventWorker(AsyncWorker):
def run(self): def run(self):
servers = [] servers = []
ssl_args = {}
if self.cfg.is_ssl:
ssl_args = dict(server_side=True,
do_handshake_on_connect=False, **self.cfg.ssl_options)
for s in self.sockets: for s in self.sockets:
s.setblocking(1) s.setblocking(1)
pool = Pool(self.worker_connections) pool = Pool(self.worker_connections)
if self.server_class is not None: if self.server_class is not None:
server = self.server_class( server = self.server_class(
s, application=self.wsgi, spawn=pool, log=self.log, s, application=self.wsgi, spawn=pool, log=self.log,
handler_class=self.wsgi_handler) handler_class=self.wsgi_handler, **ssl_args)
else: else:
hfun = partial(self.handle, s) hfun = partial(self.handle, s)
server = StreamServer(s, handle=hfun, spawn=pool) server = StreamServer(s, handle=hfun, spawn=pool, **ssl_args)
server.start() server.start()
servers.append(server) servers.append(server)

View File

@ -75,7 +75,8 @@ class TornadoWorker(Worker):
httpserver.HTTPConnection.finish = finish httpserver.HTTPConnection.finish = finish
sys.modules["tornado.httpserver"] = httpserver sys.modules["tornado.httpserver"] = httpserver
server = tornado.httpserver.HTTPServer(app, io_loop=self.ioloop) server = tornado.httpserver.HTTPServer(app, io_loop=self.ioloop,
ssl_options=self.cfg.ssl_options)
for s in self.sockets: for s in self.sockets:
s.setblocking(0) s.setblocking(0)
if hasattr(server, "add_socket"): # tornado > 2.0 if hasattr(server, "add_socket"): # tornado > 2.0

View File

@ -9,6 +9,7 @@ import errno
import os import os
import select import select
import socket import socket
import ssl
import gunicorn.http as http import gunicorn.http as http
import gunicorn.http.wsgi as wsgi import gunicorn.http.wsgi as wsgi
@ -75,6 +76,11 @@ class SyncWorker(base.Worker):
def handle(self, listener, client, addr): def handle(self, listener, client, addr):
req = None req = None
try: try:
if self.cfg.is_ssl:
client = ssl.wrap_socket(client, server_side=True,
do_handshake_on_connect=False,
**self.cfg.ssl_options)
parser = http.RequestParser(self.cfg, client) parser = http.RequestParser(self.cfg, client)
req = six.next(parser) req = six.next(parser)
self.handle_request(listener, req, client, addr) self.handle_request(listener, req, client, addr)
@ -82,6 +88,13 @@ class SyncWorker(base.Worker):
self.log.debug("Ignored premature client disconnection. %s", e) self.log.debug("Ignored premature client disconnection. %s", e)
except StopIteration as e: except StopIteration as e:
self.log.debug("Closing connection. %s", e) self.log.debug("Closing connection. %s", e)
except ssl.SSLError as e:
if e.args[0] == ssl.SSL_ERROR_EOF:
self.log.debug("ssl connection closed")
client.close()
else:
self.log.debug("Error processing SSL request.")
self.handle_error(req, client, addr, e)
except socket.error as e: except socket.error as e:
if e.args[0] != errno.EPIPE: if e.args[0] != errno.EPIPE:
self.log.exception("Error processing request.") self.log.exception("Error processing request.")