From cdca314298392c2838eb39796d7d24e6bfebaa18 Mon Sep 17 00:00:00 2001 From: Stephen Holsapple Date: Mon, 14 Apr 2014 14:43:12 -0700 Subject: [PATCH 1/2] Remove incompatible SSL option in Python 2.6 An exposed SSL option, `ciphers`, was added in Python 2.7 and breaks older versions of Python with an unexpected kwargs error. I'm not sure if this is the best way to fix this, and I'm happy to consider other approachs. Some things I'm a bit up in the air about are: i) This is only broken for Python 2.6 and older whose ssl module doesn't expose this. Do the other types of workers not use ssl module, and thus might be able to make use of the `cipher` kwarg? ii) Should we silently or explicity fail? I couldn't find a nice way to do this because the configuration setting has a default value and resulted in stringy code to special case the one setting`. What I would really like to do would add a "python_version" validator that could intelligently handle this, but I went off into the weeds figuring out how the SettingsMeta class works. :) --- gunicorn/config.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/gunicorn/config.py b/gunicorn/config.py index f272bcc9..bb6f2b61 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -140,19 +140,24 @@ class Config(object): def is_ssl(self): return self.certfile or self.keyfile - @property - def ssl_options(self): - opts = {} - - for attr in('certfile', 'keyfile', 'cert_reqs', 'ssl_version', \ - 'ca_certs', 'suppress_ragged_eofs', 'do_handshake_on_connect', - 'ciphers'): - + def _load_attrs(self, attrs, version=sys.version_info): + for attr in attrs: # 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 + if hasattr(self, attr) and version >= sys.version_info: + yield (attr, getattr(self, attr)) + + @property + def ssl_options(self): + + opts = {} + + opts.update(self._load_attrs(('certfile', 'keyfile', 'cert_reqs', 'ssl_version', + 'ca_certs', 'suppress_ragged_eofs', 'do_handshake_on_connect'))) + + # The `ciphers` kwarg was only available in Python 2.7, so don't make + # it available for us in older versions on Python. + opts.update(self._load_attrs(('ciphers'), version=(2, 7))) return opts @@ -1560,4 +1565,6 @@ class Ciphers(Setting): default = 'TLSv1' desc = """\ Ciphers to use (see stdlib ssl module's) + + Note, this value is only available in Python 2.7+ and is ignored in older versions of Python. """ From c3538283518a800bda7683479a92d1a1e62418ee Mon Sep 17 00:00:00 2001 From: Stephen Holsapple Date: Tue, 22 Apr 2014 10:26:23 -0700 Subject: [PATCH 2/2] Define `Ciphers` config conditionally We only expose the `Ciphers` config parameter in versions of Python that support it (i.e., >= Python 2.7). --- gunicorn/config.py | 40 +++++++++++++--------------------------- tests/test_007-ssl.py | 18 ++++++++++++------ 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/gunicorn/config.py b/gunicorn/config.py index bb6f2b61..cfafe081 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -140,25 +140,12 @@ class Config(object): def is_ssl(self): return self.certfile or self.keyfile - def _load_attrs(self, attrs, version=sys.version_info): - for attr in attrs: - # 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) and version >= sys.version_info: - yield (attr, getattr(self, attr)) - @property def ssl_options(self): - opts = {} - - opts.update(self._load_attrs(('certfile', 'keyfile', 'cert_reqs', 'ssl_version', - 'ca_certs', 'suppress_ragged_eofs', 'do_handshake_on_connect'))) - - # The `ciphers` kwarg was only available in Python 2.7, so don't make - # it available for us in older versions on Python. - opts.update(self._load_attrs(('ciphers'), version=(2, 7))) - + for name, value in self.settings.items(): + if value.section == 'Ssl': + opts[name] = value.get() return opts @property @@ -1557,14 +1544,13 @@ class DoHandshakeOnConnect(Setting): 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) - - Note, this value is only available in Python 2.7+ and is ignored in older versions of Python. - """ +if sys.version_info >= (2, 7): + 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/tests/test_007-ssl.py b/tests/test_007-ssl.py index 54a001c1..b61788d8 100644 --- a/tests/test_007-ssl.py +++ b/tests/test_007-ssl.py @@ -8,13 +8,17 @@ # stdlib import inspect import ssl +import sys from unittest import TestCase # gunicorn from gunicorn.config import KeyFile, CertFile, SSLVersion, CACerts, \ - SuppressRaggedEOFs, DoHandshakeOnConnect, Ciphers, Setting, validate_bool, validate_string, \ + SuppressRaggedEOFs, DoHandshakeOnConnect, Setting, validate_bool, validate_string, \ validate_pos_int +if sys.version_info >= (2, 7): + from gunicorn.config import Ciphers + class SSLTestCase(TestCase): def test_settings_classes(self): """ Tests all settings options and their defaults. @@ -59,8 +63,10 @@ class SSLTestCase(TestCase): 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') + + if sys.version_info >= (2, 7): + 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')