From 187b7cb01e5e34140f18d36c0ce41cba65a7d1da Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Mon, 13 Nov 2023 14:29:21 -0600 Subject: [PATCH 01/12] Updating comment to reflect changes in the previous commit --- cassandra/cluster.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index e0c09ca64f..45d98ad000 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -826,9 +826,7 @@ def default_retry_policy(self, policy): In addition to ``wrap_socket`` kwargs, clients may also specify ``'check_hostname': True`` to verify the cert hostname as outlined in RFC 2818 and RFC 6125. Note that this requires the certificate to be transferred, so - should almost always require the option ``'cert_reqs': ssl.CERT_REQUIRED``. Note also that this functionality was not built into - Python standard library until (2.7.9, 3.2). To enable this mechanism in earlier versions, patch ``ssl.match_hostname`` - with a custom or `back-ported function `_. + should almost always require the option ``'cert_reqs': ssl.CERT_REQUIRED``. """ ssl_context = None From e54ff8c07f848713655fe22392d79b03e24148a8 Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Tue, 14 Nov 2023 01:19:20 -0600 Subject: [PATCH 02/12] Revert "Updating comment to reflect changes in the previous commit" This reverts commit 187b7cb01e5e34140f18d36c0ce41cba65a7d1da. --- cassandra/cluster.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index 45d98ad000..e0c09ca64f 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -826,7 +826,9 @@ def default_retry_policy(self, policy): In addition to ``wrap_socket`` kwargs, clients may also specify ``'check_hostname': True`` to verify the cert hostname as outlined in RFC 2818 and RFC 6125. Note that this requires the certificate to be transferred, so - should almost always require the option ``'cert_reqs': ssl.CERT_REQUIRED``. + should almost always require the option ``'cert_reqs': ssl.CERT_REQUIRED``. Note also that this functionality was not built into + Python standard library until (2.7.9, 3.2). To enable this mechanism in earlier versions, patch ``ssl.match_hostname`` + with a custom or `back-ported function `_. """ ssl_context = None From daac2c8b4dbcf7b649fc1d4050fb130bc83b6866 Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Tue, 14 Nov 2023 15:53:38 -0600 Subject: [PATCH 03/12] Heart of the fix, or at least my initial attempt at it --- cassandra/__init__.py | 2 +- cassandra/cluster.py | 14 +++++--- cassandra/connection.py | 59 ++++++++++++++++++++++++--------- cassandra/io/eventletreactor.py | 15 ++++----- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/cassandra/__init__.py b/cassandra/__init__.py index 4398c86f69..c2c3515d49 100644 --- a/cassandra/__init__.py +++ b/cassandra/__init__.py @@ -742,5 +742,5 @@ class DependencyException(Exception): def __init__(self, msg, excs=[]): complete_msg = msg if excs: - complete_msg += ("The following exceptions were observed: \n" + '\n'.join(str(e) for e in excs)) + complete_msg += ("\nThe following exceptions were observed: \n - " + '\n - '.join(str(e) for e in excs)) Exception.__init__(self, complete_msg) \ No newline at end of file diff --git a/cassandra/cluster.py b/cassandra/cluster.py index e0c09ca64f..ccfcf078d6 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -165,10 +165,12 @@ def _connection_reduce_fn(val,import_fn): excs.append(exc) return (rv or import_result, excs) +log = logging.getLogger(__name__) + conn_fns = (_try_gevent_import, _try_eventlet_import, _try_libev_import, _try_asyncore_import) (conn_class, excs) = reduce(_connection_reduce_fn, conn_fns, (None,[])) -if excs: - raise DependencyException("Exception loading connection class dependencies", excs) +if not conn_class: + raise DependencyException("Unable to load a default connection class", excs) DefaultConnection = conn_class # Forces load of utf8 encoding module to avoid deadlock that occurs @@ -177,8 +179,6 @@ def _connection_reduce_fn(val,import_fn): # See http://bugs.python.org/issue10923 "".encode('utf8') -log = logging.getLogger(__name__) - DEFAULT_MIN_REQUESTS = 5 DEFAULT_MAX_REQUESTS = 100 @@ -829,6 +829,12 @@ def default_retry_policy(self, policy): should almost always require the option ``'cert_reqs': ssl.CERT_REQUIRED``. Note also that this functionality was not built into Python standard library until (2.7.9, 3.2). To enable this mechanism in earlier versions, patch ``ssl.match_hostname`` with a custom or `back-ported function `_. + + .. versionchanged:: 3.29.0 + + ``ssl.match_hostname`` has been deprecated since Python 3.7 (and removed in Python 3.12). This functionality is now implemented + via ``ssl.SSLContext.check_hostname``. All options specified above (including ``check_hostname``) should continue to behave in a + way that is consistent with prior implementations. """ ssl_context = None diff --git a/cassandra/connection.py b/cassandra/connection.py index 195c93c889..a87aa75d51 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -733,7 +733,6 @@ class Connection(object): _socket = None _socket_impl = socket - _ssl_impl = ssl _check_hostname = False _product_type = None @@ -777,15 +776,21 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, self._on_orphaned_stream_released = on_orphaned_stream_released if ssl_options: - self._check_hostname = bool(self.ssl_options.pop('check_hostname', False)) - if self._check_hostname: - if not getattr(ssl, 'match_hostname', None): - raise RuntimeError("ssl_options specify 'check_hostname', but ssl.match_hostname is not provided. " - "Patch or upgrade Python to use this option.") self.ssl_options.update(self.endpoint.ssl_options or {}) elif self.endpoint.ssl_options: self.ssl_options = self.endpoint.ssl_options + # PYTHON-1331 + # + # We always use SSLContext.wrap_socket() now but legacy configs may have other params that were passed to ssl.wrap_socket()... + # and either could have 'check_hostname'. Remove these params into a separate map and use them to build an SSLContext if + # we need to do so. + # + # Note the use of pop() here; we are very deliberately removing these params from ssl_options if they're present. After this + # operation ssl_options shoudl contain only args needed for the ssl_context.wrap_socket() call. + ssl_context_args = {k:self.ssl_options.pop(k, None) for k in ['check_hostname', 'keyfile', 'certfile', 'ca_certs', 'ciphers']} + if not self.ssl_context: + self.ssl_context = self._build_ssl_context_from_options(ssl_context_args) if protocol_version >= 3: self.max_request_id = min(self.max_in_flight - 1, (2 ** 15) - 1) @@ -852,6 +857,25 @@ def factory(cls, endpoint, timeout, *args, **kwargs): else: return conn + def _build_ssl_context_from_options(self, opts): + # Python >= 3.10 requires either PROTOCOL_TLS_CLIENT or PROTOCOL_TLS_SERVER so we'll get ahead of things by always + # being explicit + rv = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) + rv.check_hostname = bool(opts.pop('check_hostname', False)) + + certfile = opts.pop('certfile', None) + keyfile = opts.pop('keyfile', None) + if certfile: + rv.load_cert_chain(certfile, keyfile) + ca_certs = opts.pop('ca_certs', None) + if ca_certs: + rv.load_verify_locations(ca_certs) + ciphers = opts.pop('ciphers', None) + if ciphers: + rv.set_ciphers(ciphers) + + return rv + def _wrap_socket_from_context(self): ssl_options = self.ssl_options or {} # PYTHON-1186: set the server_hostname only if the SSLContext has @@ -860,13 +884,16 @@ def _wrap_socket_from_context(self): 'server_hostname' not in ssl_options): ssl_options = ssl_options.copy() ssl_options['server_hostname'] = self.endpoint.address - self._socket = self.ssl_context.wrap_socket(self._socket, **ssl_options) + return self.ssl_context.wrap_socket(self._socket, **ssl_options) def _initiate_connection(self, sockaddr): self._socket.connect(sockaddr) - def _match_hostname(self): - ssl.match_hostname(self._socket.getpeercert(), self.endpoint.address) + # PYTHON-1331 + # + # Allow implementations specific to an event loop to add additional behaviours + def _validate_hostname(self): + pass def _get_socket_addresses(self): address, port = self.endpoint.resolve() @@ -887,16 +914,18 @@ def _connect_socket(self): try: self._socket = self._socket_impl.socket(af, socktype, proto) if self.ssl_context: - self._wrap_socket_from_context() - elif self.ssl_options: - if not self._ssl_impl: - raise RuntimeError("This version of Python was not compiled with SSL support") - self._socket = self._ssl_impl.wrap_socket(self._socket, **self.ssl_options) + self._socket = self._wrap_socket_from_context() self._socket.settimeout(self.connect_timeout) self._initiate_connection(sockaddr) self._socket.settimeout(None) + + # PYTHON-1331 + # + # Most checking is done via the check_hostname param on the SSLContext. + # Subclasses can add additional behaviours via _validate_hostname() so + # run that here. if self._check_hostname: - self._match_hostname() + self._validate_hostname() sockerr = None break except socket.error as err: diff --git a/cassandra/io/eventletreactor.py b/cassandra/io/eventletreactor.py index 42874036d5..c51bfd7591 100644 --- a/cassandra/io/eventletreactor.py +++ b/cassandra/io/eventletreactor.py @@ -103,11 +103,12 @@ def __init__(self, *args, **kwargs): def _wrap_socket_from_context(self): _check_pyopenssl() - self._socket = SSL.Connection(self.ssl_context, self._socket) - self._socket.set_connect_state() + rv = SSL.Connection(self.ssl_context, self._socket) + rv.set_connect_state() if self.ssl_options and 'server_hostname' in self.ssl_options: # This is necessary for SNI - self._socket.set_tlsext_host_name(self.ssl_options['server_hostname'].encode('ascii')) + rv.set_tlsext_host_name(self.ssl_options['server_hostname'].encode('ascii')) + return rv def _initiate_connection(self, sockaddr): if self.uses_legacy_ssl_options: @@ -117,14 +118,12 @@ def _initiate_connection(self, sockaddr): if self.ssl_context or self.ssl_options: self._socket.do_handshake() - def _match_hostname(self): - if self.uses_legacy_ssl_options: - super(EventletConnection, self)._match_hostname() - else: + def _validate_hostname(self): + if not self.uses_legacy_ssl_options: cert_name = self._socket.get_peer_certificate().get_subject().commonName if cert_name != self.endpoint.address: raise Exception("Hostname verification failed! Certificate name '{}' " - "doesn't endpoint '{}'".format(cert_name, self.endpoint.address)) + "doesn't match endpoint '{}'".format(cert_name, self.endpoint.address)) def close(self): with self.lock: From f94494dad2c5816426af553cb7c6fc740c12f28e Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Tue, 14 Nov 2023 15:53:55 -0600 Subject: [PATCH 04/12] Move pynose into test-dependencies rather than directly imported in Jenkinsfile --- Jenkinsfile | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index d654558b8c..33d6de7468 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -196,7 +196,7 @@ def initializeEnvironment() { } sh label: 'Install unit test modules', script: '''#!/bin/bash -lex - pip install pynose nose-ignore-docstring nose-exclude service_identity + pip install nose-ignore-docstring nose-exclude service_identity ''' if (env.CYTHON_ENABLED == 'True') { diff --git a/test-requirements.txt b/test-requirements.txt index 7d3c021240..4ebb23df53 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,6 @@ -r requirements.txt scales -nose +pynose mock>1.1 ccm>=2.1.2 pytz From d66f4f9b8d9049d2b159187f20801b9fc4ae552a Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Fri, 17 Nov 2023 13:25:19 -0600 Subject: [PATCH 05/12] A couple minor fixes --- cassandra/cluster.py | 6 +++--- cassandra/connection.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cassandra/cluster.py b/cassandra/cluster.py index ccfcf078d6..d5f80290a9 100644 --- a/cassandra/cluster.py +++ b/cassandra/cluster.py @@ -811,9 +811,9 @@ def default_retry_policy(self, policy): Using ssl_options without ssl_context is deprecated and will be removed in the next major release. - An optional dict which will be used as kwargs for ``ssl.SSLContext.wrap_socket`` (or - ``ssl.wrap_socket()`` if used without ssl_context) when new sockets are created. - This should be used when client encryption is enabled in Cassandra. + An optional dict which will be used as kwargs for ``ssl.SSLContext.wrap_socket`` + when new sockets are created. This should be used when client encryption is enabled + in Cassandra. The following documentation only applies when ssl_options is used without ssl_context. diff --git a/cassandra/connection.py b/cassandra/connection.py index a87aa75d51..9fb69444ce 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -756,7 +756,7 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, self.endpoint = host if isinstance(host, EndPoint) else DefaultEndPoint(host, port) self.authenticator = authenticator - self.ssl_options = ssl_options.copy() if ssl_options else None + self.ssl_options = ssl_options.copy() if ssl_options else {} self.ssl_context = ssl_context self.sockopts = sockopts self.compression = compression From fca6eda9b01adcc0c18c01dfef86a2302db2435e Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Mon, 20 Nov 2023 16:09:13 -0600 Subject: [PATCH 06/12] Fixes based on local testing --- cassandra/connection.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 9fb69444ce..3d6c981502 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -787,8 +787,9 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, # we need to do so. # # Note the use of pop() here; we are very deliberately removing these params from ssl_options if they're present. After this - # operation ssl_options shoudl contain only args needed for the ssl_context.wrap_socket() call. - ssl_context_args = {k:self.ssl_options.pop(k, None) for k in ['check_hostname', 'keyfile', 'certfile', 'ca_certs', 'ciphers']} + # operation ssl_options should contain only args needed for the ssl_context.wrap_socket() call. + ssl_context_args = {k:self.ssl_options.pop(k, None) for k in \ + ['ssl_version', 'cert_reqs', 'check_hostname', 'keyfile', 'certfile', 'ca_certs', 'ciphers']} if not self.ssl_context: self.ssl_context = self._build_ssl_context_from_options(ssl_context_args) @@ -860,8 +861,11 @@ def factory(cls, endpoint, timeout, *args, **kwargs): def _build_ssl_context_from_options(self, opts): # Python >= 3.10 requires either PROTOCOL_TLS_CLIENT or PROTOCOL_TLS_SERVER so we'll get ahead of things by always # being explicit - rv = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) + ssl_version = opts.pop('ssl_version', ssl.PROTOCOL_TLS_CLIENT) + cert_reqs = opts.pop('cert_reqs', ssl.CERT_REQUIRED) + rv = ssl.SSLContext(protocol=ssl_version) rv.check_hostname = bool(opts.pop('check_hostname', False)) + rv.options = cert_reqs certfile = opts.pop('certfile', None) keyfile = opts.pop('keyfile', None) From 3c73dd920a70699c2c1ff836031863a2fc8d04b9 Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Tue, 21 Nov 2023 12:05:06 -0600 Subject: [PATCH 07/12] ssl_version passed to SSLContext must be an int --- cassandra/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 3d6c981502..d386b5f6c2 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -861,7 +861,7 @@ def factory(cls, endpoint, timeout, *args, **kwargs): def _build_ssl_context_from_options(self, opts): # Python >= 3.10 requires either PROTOCOL_TLS_CLIENT or PROTOCOL_TLS_SERVER so we'll get ahead of things by always # being explicit - ssl_version = opts.pop('ssl_version', ssl.PROTOCOL_TLS_CLIENT) + ssl_version = int(opts.pop('ssl_version', ssl.PROTOCOL_TLS_CLIENT)) cert_reqs = opts.pop('cert_reqs', ssl.CERT_REQUIRED) rv = ssl.SSLContext(protocol=ssl_version) rv.check_hostname = bool(opts.pop('check_hostname', False)) From 2eab3f36bdc71d9de66e4fc8f21e6249224c8c14 Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Tue, 28 Nov 2023 17:04:12 -0600 Subject: [PATCH 08/12] Fix to minor _build_ssl_context_from_options issue --- cassandra/connection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index d386b5f6c2..13589b8cd6 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -861,11 +861,11 @@ def factory(cls, endpoint, timeout, *args, **kwargs): def _build_ssl_context_from_options(self, opts): # Python >= 3.10 requires either PROTOCOL_TLS_CLIENT or PROTOCOL_TLS_SERVER so we'll get ahead of things by always # being explicit - ssl_version = int(opts.pop('ssl_version', ssl.PROTOCOL_TLS_CLIENT)) - cert_reqs = opts.pop('cert_reqs', ssl.CERT_REQUIRED) - rv = ssl.SSLContext(protocol=ssl_version) + ssl_version = opts.pop('ssl_version') or ssl.PROTOCOL_TLS_CLIENT + cert_reqs = opts.pop('cert_reqs') or ssl.CERT_REQUIRED + rv = ssl.SSLContext(protocol=int(ssl_version)) rv.check_hostname = bool(opts.pop('check_hostname', False)) - rv.options = cert_reqs + rv.options = int(cert_reqs) certfile = opts.pop('certfile', None) keyfile = opts.pop('keyfile', None) From 8de9b0363e8f372ee7e0c62034e26046d62d3a04 Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Thu, 30 Nov 2023 03:48:32 -0600 Subject: [PATCH 09/12] Hey, let's not use SSL when we don't mean to --- cassandra/connection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 13589b8cd6..24942e41f1 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -788,9 +788,9 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, # # Note the use of pop() here; we are very deliberately removing these params from ssl_options if they're present. After this # operation ssl_options should contain only args needed for the ssl_context.wrap_socket() call. - ssl_context_args = {k:self.ssl_options.pop(k, None) for k in \ - ['ssl_version', 'cert_reqs', 'check_hostname', 'keyfile', 'certfile', 'ca_certs', 'ciphers']} - if not self.ssl_context: + if not self.ssl_context and self.ssl_options: + ssl_context_names = ['ssl_version', 'cert_reqs', 'check_hostname', 'keyfile', 'certfile', 'ca_certs', 'ciphers'] + ssl_context_args = {k:self.ssl_options.pop(k, None) for k in ssl_context_names} self.ssl_context = self._build_ssl_context_from_options(ssl_context_args) if protocol_version >= 3: From 37823d1600c3da8698527f6a734a728718f2857b Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Fri, 1 Dec 2023 17:25:08 -0600 Subject: [PATCH 10/12] Don't install deps for nose plugins in order to avoid overwriting pynose with OG nose --- Jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 33d6de7468..ebad9f90a4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -196,7 +196,8 @@ def initializeEnvironment() { } sh label: 'Install unit test modules', script: '''#!/bin/bash -lex - pip install nose-ignore-docstring nose-exclude service_identity + pip install --no-deps nose-ignore-docstring nose-exclude + pip install service_identity ''' if (env.CYTHON_ENABLED == 'True') { From 96401f75dcc8d4edf7a1270aa703d28c0b29d3b2 Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Thu, 7 Dec 2023 05:23:19 -0600 Subject: [PATCH 11/12] Be more judicious about how we construct args for the SSLContext constructor and wrap_socket call --- cassandra/connection.py | 43 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index 24942e41f1..bfe38fc702 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -789,9 +789,7 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, # Note the use of pop() here; we are very deliberately removing these params from ssl_options if they're present. After this # operation ssl_options should contain only args needed for the ssl_context.wrap_socket() call. if not self.ssl_context and self.ssl_options: - ssl_context_names = ['ssl_version', 'cert_reqs', 'check_hostname', 'keyfile', 'certfile', 'ca_certs', 'ciphers'] - ssl_context_args = {k:self.ssl_options.pop(k, None) for k in ssl_context_names} - self.ssl_context = self._build_ssl_context_from_options(ssl_context_args) + self.ssl_context = self._build_ssl_context_from_options() if protocol_version >= 3: self.max_request_id = min(self.max_in_flight - 1, (2 ** 15) - 1) @@ -858,37 +856,48 @@ def factory(cls, endpoint, timeout, *args, **kwargs): else: return conn - def _build_ssl_context_from_options(self, opts): + def _build_ssl_context_from_options(self): + + # Extract a subset of names from self.ssl_options which apply to SSLContext creation + ssl_context_opt_names = ['ssl_version', 'cert_reqs', 'check_hostname', 'keyfile', 'certfile', 'ca_certs', 'ciphers'] + opts = {k:self.ssl_options.get(k, None) for k in ssl_context_opt_names if k in self.ssl_options} + # Python >= 3.10 requires either PROTOCOL_TLS_CLIENT or PROTOCOL_TLS_SERVER so we'll get ahead of things by always # being explicit - ssl_version = opts.pop('ssl_version') or ssl.PROTOCOL_TLS_CLIENT - cert_reqs = opts.pop('cert_reqs') or ssl.CERT_REQUIRED + ssl_version = opts.get('ssl_version', None) or ssl.PROTOCOL_TLS_CLIENT + cert_reqs = opts.get('cert_reqs', None) or ssl.CERT_REQUIRED rv = ssl.SSLContext(protocol=int(ssl_version)) - rv.check_hostname = bool(opts.pop('check_hostname', False)) + rv.check_hostname = bool(opts.get('check_hostname', False)) rv.options = int(cert_reqs) - certfile = opts.pop('certfile', None) - keyfile = opts.pop('keyfile', None) + certfile = opts.get('certfile', None) + keyfile = opts.get('keyfile', None) if certfile: rv.load_cert_chain(certfile, keyfile) - ca_certs = opts.pop('ca_certs', None) + ca_certs = opts.get('ca_certs', None) if ca_certs: rv.load_verify_locations(ca_certs) - ciphers = opts.pop('ciphers', None) + ciphers = opts.get('ciphers', None) if ciphers: rv.set_ciphers(ciphers) return rv def _wrap_socket_from_context(self): - ssl_options = self.ssl_options or {} + + # Extract a subset of names from self.ssl_options which apply to SSLContext.wrap_socket (or at least the parts + # of it that don't involve building an SSLContext under the covers) + wrap_socket_opt_names = ['server_side', 'do_handshake_on_connect', 'suppress_ragged_eofs', 'server_hostname'] + opts = {k:self.ssl_options.get(k, None) for k in wrap_socket_opt_names if k in self.ssl_options} + # PYTHON-1186: set the server_hostname only if the SSLContext has # check_hostname enabled and it is not already provided by the EndPoint ssl options - if (self.ssl_context.check_hostname and - 'server_hostname' not in ssl_options): - ssl_options = ssl_options.copy() - ssl_options['server_hostname'] = self.endpoint.address - return self.ssl_context.wrap_socket(self._socket, **ssl_options) + #opts['server_hostname'] = self.endpoint.address + if (self.ssl_context.check_hostname and 'server_hostname' not in opts): + server_hostname = self.endpoint.address + opts['server_hostname'] = server_hostname + + return self.ssl_context.wrap_socket(self._socket, **opts) def _initiate_connection(self, sockaddr): self._socket.connect(sockaddr) From cd94fe173b78f74e36ffb114de462b3cc2b3bf1c Mon Sep 17 00:00:00 2001 From: Bret McGuire Date: Thu, 7 Dec 2023 06:01:09 -0600 Subject: [PATCH 12/12] Install test-requirements in the Python 3.12 + DSE case so we can at least run as much of the test suite as possible. --- Jenkinsfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index ebad9f90a4..fdc5e74269 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -178,7 +178,10 @@ def initializeEnvironment() { // Determine if server version is Apache CassandraⓇ or DataStax Enterprise if (env.CASSANDRA_VERSION.split('-')[0] == 'dse') { if (env.PYTHON_VERSION =~ /3\.12\.\d+/) { - echo "Cannot install DSE dependencies for Python 3.12.x. See PYTHON-1368 for more detail." + echo "Cannot install DSE dependencies for Python 3.12.x; installing Apache CassandraⓇ requirements only. See PYTHON-1368 for more detail." + sh label: 'Install Apache CassandraⓇ requirements', script: '''#!/bin/bash -lex + pip install -r test-requirements.txt + ''' } else { sh label: 'Install DataStax Enterprise requirements', script: '''#!/bin/bash -lex