From 5fb61cb841068881f65e8fa2f750596cbaf2a48f Mon Sep 17 00:00:00 2001 From: Dariusz Suchojad Date: Thu, 26 Sep 2013 19:53:56 +0200 Subject: [PATCH] Added support for more options to ssl.wrap_socket --- gunicorn/config.py | 81 ++++++++++++++++++++++++++++++++--- gunicorn/workers/geventlet.py | 3 +- gunicorn/workers/ggevent.py | 3 +- gunicorn/workers/sync.py | 3 +- tests/test_007-ssl.py | 66 ++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 tests/test_007-ssl.py diff --git a/gunicorn/config.py b/gunicorn/config.py index d8ee150b..871d1dc2 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -12,6 +12,7 @@ except ImportError: # python 2.6 from . import argparse_compat as argparse import os import pwd +import ssl import sys import textwrap import types @@ -142,12 +143,17 @@ class Config(object): @property def ssl_options(self): opts = {} - if self.certfile: - opts['certfile'] = self.certfile - - if self.keyfile: - opts['keyfile'] = self.keyfile - + + for attr in('certfile', 'keyfile', 'cert_reqs', 'ssl_version', \ + 'ca_certs', 'suppress_ragged_eofs', 'do_handshake_on_connect', + 'ciphers'): + + # suppress_ragged_eofs/do_handshake_on_connect are booleans that can + # be False hence we use hasattr instead of getattr(self, attr, None). + if hasattr(self, attr): + value = getattr(self, attr) + opts[attr] = value + return opts @property @@ -1468,3 +1474,66 @@ class CertFile(Setting): desc = """\ SSL certificate file """ + +class SSLVersion(Setting): + name = "ssl_version" + section = "Ssl" + cli = ["--ssl-version"] + validator = validate_pos_int + default = ssl.PROTOCOL_TLSv1 + desc = """\ + SSL version to use (see stdlib ssl module's) + """ + +class CertReqs(Setting): + name = "cert_reqs" + section = "Ssl" + cli = ["--cert-reqs"] + validator = validate_pos_int + default = ssl.CERT_NONE + desc = """\ + Whether client certificate is required (see stdlib ssl module's) + """ + +class CACerts(Setting): + name = "ca_certs" + section = "Ssl" + cli = ["--ca-certs"] + meta = "FILE" + validator = validate_string + default = None + desc = """\ + CA certificates file + """ + +class SuppressRaggedEOFs(Setting): + name = "suppress_ragged_eofs" + section = "Ssl" + cli = ["--suppress-ragged-eofs"] + action = "store_true" + default = True + validator = validate_bool + desc = """\ + Suppress ragged EOFs (see stdlib ssl module's) + """ + +class DoHandshakeOnConnect(Setting): + name = "do_handshake_on_connect" + section = "Ssl" + cli = ["--do-handshake-on-connect"] + validator = validate_bool + action = "store_true" + default = False + desc = """\ + Whether to perform SSL handshake on socket connect (see stdlib ssl module's) + """ + +class Ciphers(Setting): + name = "ciphers" + section = "Ssl" + cli = ["--ciphers"] + validator = validate_string + default = 'TLSv1' + desc = """\ + Ciphers to use (see stdlib ssl module's) + """ diff --git a/gunicorn/workers/geventlet.py b/gunicorn/workers/geventlet.py index d3579adb..c842bb55 100644 --- a/gunicorn/workers/geventlet.py +++ b/gunicorn/workers/geventlet.py @@ -56,8 +56,7 @@ class EventletWorker(AsyncWorker): 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) + **self.cfg.ssl_options) super(EventletWorker, self).handle(listener, client, addr) diff --git a/gunicorn/workers/ggevent.py b/gunicorn/workers/ggevent.py index 0fed97f2..fde7c3ea 100644 --- a/gunicorn/workers/ggevent.py +++ b/gunicorn/workers/ggevent.py @@ -100,8 +100,7 @@ class GeventWorker(AsyncWorker): ssl_args = {} if self.cfg.is_ssl: - ssl_args = dict(server_side=True, - do_handshake_on_connect=False, **self.cfg.ssl_options) + ssl_args = dict(server_side=True, **self.cfg.ssl_options) for s in self.sockets: s.setblocking(1) diff --git a/gunicorn/workers/sync.py b/gunicorn/workers/sync.py index 06de9f6e..36cdf98e 100644 --- a/gunicorn/workers/sync.py +++ b/gunicorn/workers/sync.py @@ -85,8 +85,7 @@ class SyncWorker(base.Worker): try: if self.cfg.is_ssl: client = ssl.wrap_socket(client, server_side=True, - do_handshake_on_connect=False, - **self.cfg.ssl_options) + **self.cfg.ssl_options) parser = http.RequestParser(self.cfg, client) req = six.next(parser) diff --git a/tests/test_007-ssl.py b/tests/test_007-ssl.py new file mode 100644 index 00000000..54a001c1 --- /dev/null +++ b/tests/test_007-ssl.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 - + +# Copyright 2013 Dariusz Suchojad +# +# This file is part of gunicorn released under the MIT license. +# See the NOTICE for more information. + +# stdlib +import inspect +import ssl +from unittest import TestCase + +# gunicorn +from gunicorn.config import KeyFile, CertFile, SSLVersion, CACerts, \ + SuppressRaggedEOFs, DoHandshakeOnConnect, Ciphers, Setting, validate_bool, validate_string, \ + validate_pos_int + +class SSLTestCase(TestCase): + def test_settings_classes(self): + """ Tests all settings options and their defaults. + """ + self.assertTrue(issubclass(KeyFile, Setting)) + self.assertEquals(KeyFile.name, 'keyfile') + self.assertEquals(KeyFile.section, 'Ssl') + self.assertEquals(KeyFile.cli, ['--keyfile']) + self.assertEquals(KeyFile.meta, 'FILE') + self.assertEquals(KeyFile.default, None) + + self.assertTrue(issubclass(CertFile, Setting)) + self.assertEquals(CertFile.name, 'certfile') + self.assertEquals(CertFile.section, 'Ssl') + self.assertEquals(CertFile.cli, ['--certfile']) + self.assertEquals(CertFile.default, None) + + self.assertTrue(issubclass(SSLVersion, Setting)) + self.assertEquals(SSLVersion.name, 'ssl_version') + self.assertEquals(SSLVersion.section, 'Ssl') + self.assertEquals(SSLVersion.cli, ['--ssl-version']) + self.assertEquals(SSLVersion.default, ssl.PROTOCOL_TLSv1) + + self.assertTrue(issubclass(CACerts, Setting)) + self.assertEquals(CACerts.name, 'ca_certs') + self.assertEquals(CACerts.section, 'Ssl') + self.assertEquals(CACerts.cli, ['--ca-certs']) + self.assertEquals(CACerts.meta, 'FILE') + self.assertEquals(CACerts.default, None) + + self.assertTrue(issubclass(SuppressRaggedEOFs, Setting)) + self.assertEquals(SuppressRaggedEOFs.name, 'suppress_ragged_eofs') + self.assertEquals(SuppressRaggedEOFs.section, 'Ssl') + self.assertEquals(SuppressRaggedEOFs.cli, ['--suppress-ragged-eofs']) + self.assertEquals(SuppressRaggedEOFs.action, 'store_true') + self.assertEquals(SuppressRaggedEOFs.default, True) + + self.assertTrue(issubclass(DoHandshakeOnConnect, Setting)) + self.assertEquals(DoHandshakeOnConnect.name, 'do_handshake_on_connect') + self.assertEquals(DoHandshakeOnConnect.section, 'Ssl') + self.assertEquals(DoHandshakeOnConnect.cli, ['--do-handshake-on-connect']) + self.assertEquals(DoHandshakeOnConnect.action, 'store_true') + self.assertEquals(DoHandshakeOnConnect.default, False) + + self.assertTrue(issubclass(Ciphers, Setting)) + self.assertEquals(Ciphers.name, 'ciphers') + self.assertEquals(Ciphers.section, 'Ssl') + self.assertEquals(Ciphers.cli, ['--ciphers']) + self.assertEquals(Ciphers.default, 'TLSv1')