Deprecate ssl_version option

This change defaults SSLContext to Python's ssl.create_default_context() and
marks ssl_version option as deprecated. The option value will be ignored and
warnign will be printed in stderr.

The ssl_version option was depending on old method of setting TLS min/max
version, which has not worked well anymore with modern Python versions.
This commit is contained in:
Tero Saarni 2023-05-11 17:45:57 +03:00
parent f859de498a
commit d8c3b1490e
6 changed files with 97 additions and 122 deletions

View File

@ -48,7 +48,9 @@ def format_settings(app):
def fmt_setting(s):
if callable(s.default):
if hasattr(s, "default_doc"):
val = s.default_doc
elif callable(s.default):
val = inspect.getsource(s.default)
val = "\n".join(" %s" % line for line in val.splitlines())
val = "\n\n.. code-block:: python\n\n" + val

View File

@ -32,7 +32,7 @@ Config File
**Default:** ``'./gunicorn.conf.py'``
The Gunicorn config file.
:ref:`The Gunicorn config file<configuration_file>`.
A string of the form ``PATH``, ``file:PATH``, or ``python:MODULE_NAME``.
@ -305,16 +305,6 @@ The log config file to use.
Gunicorn uses the standard Python logging module's Configuration
file format.
.. _logconfig-json:
logiconfig_json
~~~~~~~~~
* ``--log-config-json FILE``
* ``None``
The log config file written in JSON.
.. _logconfig-dict:
``logconfig_dict``
@ -324,9 +314,9 @@ The log config file written in JSON.
The log config dictionary to use, using the standard Python
logging module's dictionary configuration format. This option
takes precedence over the :ref:`logconfig` and :ref:`logConfigJson` options, which uses the
older file configuration format and JSON respectively.
takes precedence over the :ref:`logconfig` and :ref:`logConfigJson` options,
which uses the older file configuration format and JSON
respectively.
Format: https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig
@ -334,6 +324,21 @@ For more context you can look at the default configuration dictionary for loggin
.. versionadded:: 19.8
.. _logconfig-json:
``logconfig_json``
~~~~~~~~~~~~~~~~~~
**Command line:** ``--log-config-json FILE``
**Default:** ``None``
The log config to read config from a JSON file
Format: https://docs.python.org/3/library/logging.config.html#logging.config.jsonConfig
.. versionadded:: 20.0
.. _syslog-addr:
``syslog_addr``
@ -519,7 +524,10 @@ SSL certificate file
**Default:** ``<_SSLMethod.PROTOCOL_TLS: 2>``
SSL version to use.
SSL version to use (see stdlib ssl module's).
.. deprecated:: 20.2
The option is deprecated and it is currently ignored. Use :ref:`ssl-context` instead.
============= ============
--ssl-version Description
@ -542,6 +550,9 @@ TLS_SERVER Auto-negotiate the highest protocol version like TLS,
.. versionchanged:: 20.0
This setting now accepts string names based on ``ssl.PROTOCOL_``
constants.
.. versionchanged:: 20.0.1
The default value has been changed from ``ssl.PROTOCOL_SSLv23`` to
``ssl.PROTOCOL_TLS`` when Python >= 3.6 .
.. _cert-reqs:
@ -554,15 +565,13 @@ TLS_SERVER Auto-negotiate the highest protocol version like TLS,
Whether client certificate is required (see stdlib ssl module's)
**Options:**
`--cert-reqs=0` --- no client veirifcation
`--cert-reqs=1` --- ssl.CERT_OPTIONAL
`--cert-reqs=2` --- ssl.CERT_REQUIRED
=========== ===========================
--cert-reqs Description
=========== ===========================
`0` no client veirifcation
`1` ssl.CERT_OPTIONAL
`2` ssl.CERT_REQUIRED
=========== ===========================
.. _ca-certs:
@ -954,8 +963,7 @@ The callable needs to accept a single instance variable for the Arbiter.
Called when SSLContext is needed.
Allows fully customized SSL context to be used in place of the default
context.
Allows customizing SSL context.
The callable needs to accept an instance variable for the Config and
a factory function that returns default SSLContext which is initialized
@ -963,6 +971,18 @@ with certificates, private key, cert_reqs, and ciphers according to
config and can be further customized by the callable.
The callable needs to return SSLContext object.
Following example shows a configuration file that sets the minimum TLS version to 1.3:
.. code-block:: python
def ssl_context(conf, default_ssl_context_factory):
import ssl
context = default_ssl_context_factory()
context.minimum_version = ssl.TLSVersion.TLSv1_3
return context
.. versionadded:: 20.2
Server Mechanics
----------------
@ -1107,7 +1127,7 @@ If not set, the default temporary directory will be used.
**Command line:** ``-u USER`` or ``--user USER``
**Default:** ``501``
**Default:** ``os.geteuid()``
Switch worker processes to run as this user.
@ -1122,7 +1142,7 @@ change the worker process user.
**Command line:** ``-g GROUP`` or ``--group GROUP``
**Default:** ``20``
**Default:** ``os.getegid()``
Switch worker process to run as this group.
@ -1226,8 +1246,9 @@ variable. If it is not defined, the default is ``"127.0.0.1"``.
.. note::
The interplay between the request headers, the value of ``forwarded_allow_ips``, and the value of
``secure_scheme_headers`` is complex. Various scenarios are documented below to further elaborate. In each case, we
have a request from the remote address 134.213.44.18, and the default value of ``secure_scheme_headers``:
``secure_scheme_headers`` is complex. Various scenarios are documented below to further elaborate.
In each case, we have a request from the remote address 134.213.44.18, and the default value of
``secure_scheme_headers``:
.. code::
@ -1238,7 +1259,7 @@ variable. If it is not defined, the default is ``"127.0.0.1"``.
}
.. list-table::
.. list-table::
:header-rows: 1
:align: center
:widths: auto
@ -1247,35 +1268,35 @@ variable. If it is not defined, the default is ``"127.0.0.1"``.
- Secure Request Headers
- Result
- Explanation
* - .. code::
* - .. code::
["127.0.0.1"]
- .. code::
X-Forwarded-Proto: https
- .. code::
- .. code::
wsgi.url_scheme = "http"
- IP address was not allowed
* - .. code::
* - .. code::
"*"
- <none>
- .. code::
- .. code::
wsgi.url_scheme = "http"
- IP address allowed, but no secure headers provided
* - .. code::
* - .. code::
"*"
- .. code::
X-Forwarded-Proto: https
- .. code::
- .. code::
wsgi.url_scheme = "https"
- IP address allowed, one request header matched
* - .. code::
* - .. code::
["134.213.44.18"]
- .. code::

View File

@ -364,25 +364,9 @@ def validate_pos_int(val):
def validate_ssl_version(val):
ssl_versions = {}
for protocol in [p for p in dir(ssl) if p.startswith("PROTOCOL_")]:
ssl_versions[protocol[9:]] = getattr(ssl, protocol)
if val in ssl_versions:
# string matching PROTOCOL_...
return ssl_versions[val]
try:
intval = validate_pos_int(val)
if intval in ssl_versions.values():
# positive int matching a protocol int constant
return intval
except (ValueError, TypeError):
# negative integer or not an integer
# drop this in favour of the more descriptive ValueError below
pass
raise ValueError("Invalid ssl_version: %s. Valid options: %s"
% (val, ', '.join(ssl_versions)))
if val != SSLVersion.default:
sys.stderr.write("Warning: option `ssl_version` is deprecated and it is ignored. Use ssl_context instead.\n")
return val
def validate_string(val):
@ -736,7 +720,7 @@ class WorkerConnections(Setting):
desc = """\
The maximum number of simultaneous clients.
This setting only affects the Eventlet and Gevent worker types.
This setting only affects the ``gthread``, ``eventlet`` and ``gevent`` worker types.
"""
@ -1066,6 +1050,7 @@ class Chdir(Setting):
cli = ["--chdir"]
validator = validate_chdir
default = util.getcwd()
default_doc = "``'.'``"
desc = """\
Change directory to specified directory before loading apps.
"""
@ -1157,6 +1142,7 @@ class User(Setting):
meta = "USER"
validator = validate_user
default = os.geteuid()
default_doc = "``os.geteuid()``"
desc = """\
Switch worker processes to run as this user.
@ -1173,6 +1159,7 @@ class Group(Setting):
meta = "GROUP"
validator = validate_group
default = os.getegid()
default_doc = "``os.getegid()``"
desc = """\
Switch worker process to run as this group.
@ -2019,14 +2006,25 @@ class NewSSLContext(Setting):
desc = """\
Called when SSLContext is needed.
Allows fully customized SSL context to be used in place of the default
context.
Allows customizing SSL context.
The callable needs to accept an instance variable for the Config and
a factory function that returns default SSLContext which is initialized
with certificates, private key, cert_reqs, and ciphers according to
config and can be further customized by the callable.
The callable needs to return SSLContext object.
Following example shows a configuration file that sets the minimum TLS version to 1.3:
.. code-block:: python
def ssl_context(conf, default_ssl_context_factory):
import ssl
context = default_ssl_context_factory()
context.minimum_version = ssl.TLSVersion.TLSv1_3
return context
.. versionadded:: 20.2
"""
class ProxyProtocol(Setting):
@ -2105,17 +2103,12 @@ class SSLVersion(Setting):
else:
default = ssl.PROTOCOL_SSLv23
desc = """\
SSL version to use (see stdlib ssl module's)
.. versionchanged:: 20.0.1
The default value has been changed from ``ssl.PROTOCOL_SSLv23`` to
``ssl.PROTOCOL_TLS`` when Python >= 3.6 .
"""
default = ssl.PROTOCOL_SSLv23
desc = """\
SSL version to use.
SSL version to use (see stdlib ssl module's).
.. deprecated:: 20.2
The option is deprecated and it is currently ignored. Use :ref:`ssl-context` instead.
============= ============
--ssl-version Description
@ -2138,6 +2131,9 @@ class SSLVersion(Setting):
.. versionchanged:: 20.0
This setting now accepts string names based on ``ssl.PROTOCOL_``
constants.
.. versionchanged:: 20.0.1
The default value has been changed from ``ssl.PROTOCOL_SSLv23`` to
``ssl.PROTOCOL_TLS`` when Python >= 3.6 .
"""
@ -2149,13 +2145,14 @@ class CertReqs(Setting):
default = ssl.CERT_NONE
desc = """\
Whether client certificate is required (see stdlib ssl module's)
============== ===========================
`--cert-reqs=0` --- no client veirifcation
`--cert-reqs=1` --- ssl.CERT_OPTIONAL
`--cert-reqs=2` --- ssl.CERT_REQUIRED
============== ===========================
=========== ===========================
--cert-reqs Description
=========== ===========================
`0` no client veirifcation
`1` ssl.CERT_OPTIONAL
`2` ssl.CERT_REQUIRED
=========== ===========================
"""

View File

@ -213,13 +213,11 @@ def close_sockets(listeners, unlink=True):
def ssl_context(conf):
def default_ssl_context_factory():
context = ssl.SSLContext(conf.ssl_version)
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=conf.ca_certs)
context.load_cert_chain(certfile=conf.certfile, keyfile=conf.keyfile)
context.verify_mode = conf.cert_reqs
if conf.ciphers:
context.set_ciphers(conf.ciphers)
if conf.ca_certs:
context.load_verify_locations(cafile=conf.ca_certs)
return context
return conf.ssl_context(conf, default_ssl_context_factory)

View File

@ -453,41 +453,6 @@ def test_umask_config(options, expected):
assert app.cfg.umask == expected
@pytest.mark.parametrize("options, expected", [
(["--ssl-version", "SSLv23"], 2),
(["--ssl-version", "TLSv1"], 3),
(["--ssl-version", "2"], 2),
(["--ssl-version", "3"], 3),
])
def test_ssl_version_named_constants_python3(options, expected):
_test_ssl_version(options, expected)
@pytest.mark.skipif(sys.version_info < (3, 6),
reason="requires python3.6+")
@pytest.mark.parametrize("options, expected", [
(["--ssl-version", "TLS"], 2),
(["--ssl-version", "TLSv1_1"], 4),
(["--ssl-version", "TLSv1_2"], 5),
(["--ssl-version", "TLS_SERVER"], 17),
])
def test_ssl_version_named_constants_python36(options, expected):
_test_ssl_version(options, expected)
@pytest.mark.parametrize("ssl_version", [
"FOO",
"-99",
"99991234"
])
def test_ssl_version_bad(ssl_version):
c = config.Config()
with pytest.raises(ValueError) as exc:
c.set("ssl_version", ssl_version)
assert 'Valid options' in str(exc.value)
assert "TLSv" in str(exc.value)
def _test_ssl_version(options, expected):
cmdline = ["prog_name"]
cmdline.extend(options)

View File

@ -32,14 +32,6 @@ def test_certfile():
assert CertFile.default is None
def test_ssl_version():
assert issubclass(SSLVersion, Setting)
assert SSLVersion.name == 'ssl_version'
assert SSLVersion.section == 'SSL'
assert SSLVersion.cli == ['--ssl-version']
assert SSLVersion.default == ssl.PROTOCOL_SSLv23
def test_cacerts():
assert issubclass(CACerts, Setting)
assert CACerts.name == 'ca_certs'