diff --git a/distributed/security.py b/distributed/security.py index 135d661d73a..a614239d049 100644 --- a/distributed/security.py +++ b/distributed/security.py @@ -1,18 +1,68 @@ +from __future__ import annotations + import datetime import os +import sys import tempfile +import warnings try: import ssl except ImportError: ssl = None # type: ignore +try: + from ssl import OPENSSL_VERSION_INFO as _OPENSSL_VERSION_INFO +except ImportError: + _OPENSSL_VERSION_INFO = (0, 0, 0, 0, 0) + import dask from dask.widgets import get_template __all__ = ("Security",) +if sys.version_info >= (3, 10) or _OPENSSL_VERSION_INFO >= (1, 1, 0, 7): + # The OP_NO_SSL* and OP_NO_TLS* become deprecated in favor of + # 'SSLContext.minimum_version' from Python 3.7 onwards, however + # this attribute is not available unless the ssl module is compiled + # with OpenSSL 1.1.0g or newer. + # https://docs.python.org/3.10/library/ssl.html#ssl.SSLContext.minimum_version + # https://docs.python.org/3.7/library/ssl.html#ssl.SSLContext.minimum_version + + # these _set_mimimun_version and _set_maximum_version depend on the validation + # already performed in `Security._set_tls_version_field`, + # and that they only apply to freshly created ssl.SSLContext instances in + # _get_tls_context + def _set_minimum_version(ctx: ssl.SSLContext, version: ssl.TLSVersion) -> None: + ctx.minimum_version = version + + def _set_maximum_version(ctx: ssl.SSLContext, version: ssl.TLSVersion) -> None: + ctx.maximum_version = version + +else: + + def _set_minimum_version(ctx: ssl.SSLContext, version: ssl.TLSVersion) -> None: + # if the ctx.maximum_version attribute is unsupported then we can infer + # that TLS 1.3 is not supported. + # _set_tls_version_field enforces that version is TLSVersion.TLSv1_2, + # or TLSVersion.TLSv1_3 + if version is not ssl.TLSVersion.TLSv1_2: + raise ValueError(f"Unsupported TLS/SSL version: {version!r}") + ctx.options |= ( + ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 + ) + + def _set_maximum_version(ctx: ssl.SSLContext, version: ssl.TLSVersion) -> None: + # if the ctx.maximum_version attribute is unsupported then we can infer + # that TLSv1_3 is not supported. + # _set_tls_version_field enforces that version is TLSVersion.TLSv1_2, + # TLSVersion.TLSv1_3, or None + # _get_tls_context enforces that version is not None + if version is not ssl.TLSVersion.TLSv1_2: + raise ValueError(f"Unsupported TLS/SSL version: {version!r}") + + class Security: """Security configuration for a Dask cluster. @@ -71,6 +121,12 @@ class Security: ) def __init__(self, require_encryption=None, **kwargs): + if _OPENSSL_VERSION_INFO < (1, 1, 1): + warnings.warn( + f"support for {ssl.OPENSSL_VERSION} is deprecated," + " and will be removed in a future release", + DeprecationWarning, + ) extra = set(kwargs).difference(self.__slots__) if extra: raise TypeError("Unknown parameters: %r" % sorted(extra)) @@ -251,10 +307,11 @@ def _get_tls_context(self, tls, purpose): else: ctx = ssl.create_default_context(purpose=purpose, cafile=ca) - if self.tls_min_version is not None: - ctx.minimum_version = self.tls_min_version + # the _set_tls_version_field method enforces that + # self.tls_min_version is TLSv1_2, or TLSv1_3 + _set_minimum_version(ctx, self.tls_min_version) if self.tls_max_version is not None: - ctx.maximum_version = self.tls_max_version + _set_maximum_version(ctx, self.tls_max_version) cert_in_memory = "\n" in cert key_in_memory = key is not None and "\n" in key diff --git a/distributed/tests/test_security.py b/distributed/tests/test_security.py index 49fa001f571..e4abfa46301 100644 --- a/distributed/tests/test_security.py +++ b/distributed/tests/test_security.py @@ -110,6 +110,14 @@ def test_min_max_version_config_errors(field): sec = Security() +def test_invalid_min_version_from_config_errors(): + with dask.config.set({"distributed.comm.tls.min-version": None}): + with pytest.raises( + ValueError, match=r"tls_min_version=.* is not supported, expected one of .*" + ): + Security(tls_min_version=ssl.TLSVersion.MINIMUM_SUPPORTED) + + def test_kwargs(): c = { "distributed.comm.tls.ca-file": "ca.pem",