Skip to content

Commit d3c6d81

Browse files
committed
Merge branch 'master' into oss-next
2 parents 9a1c04d + 5f13adf commit d3c6d81

File tree

9 files changed

+90
-28
lines changed

9 files changed

+90
-28
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Unreleased
55
Features
66
--------
77
* Allow passing ssl context for Twisted (PYTHON-1161)
8+
* ssl context and cloud support for Eventlet (PYTHON-1162)
89
* Cloud Twisted support (PYTHON-1163)
910
* Add additional_write_policy and read_repair to system schema parsing (PYTHON-1048)
1011
* Remove *read_repair_chance* table options (PYTHON-1140)

build.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ build:
244244
"tests/integration/standard/test_metrics.py"
245245
"tests/integration/standard/test_query.py"
246246
"tests/integration/simulacron/test_endpoint.py"
247+
"tests/integration/long/test_ssl.py"
247248
)
248249
EVENT_LOOP_MANAGER=$EVENT_LOOP_MANAGER CCM_ARGS="$CCM_ARGS" DSE_VERSION=$DSE_VERSION CASSANDRA_VERSION=$CCM_CASSANDRA_VERSION MAPPED_CASSANDRA_VERSION=$MAPPED_CASSANDRA_VERSION VERIFY_CYTHON=$FORCE_CYTHON nosetests -s -v --logging-format="[%(levelname)s] %(asctime)s %(thread)d: %(message)s" --with-ignore-docstrings --with-xunit --xunit-file=standard_results.xml ${EVENT_LOOP_TESTS[@]} || true
249250
exit 0

cassandra/cluster.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,6 @@
3939
import weakref
4040
from weakref import WeakValueDictionary
4141

42-
try:
43-
from cassandra.io.twistedreactor import TwistedConnection
44-
except ImportError:
45-
TwistedConnection = None
46-
47-
try:
48-
from weakref import WeakSet
49-
except ImportError:
50-
from cassandra.util import WeakSet # NOQA
51-
5242
from cassandra import (ConsistencyLevel, AuthenticationFailed,
5343
OperationTimedOut, UnsupportedOperation,
5444
SchemaTargetType, DriverException, ProtocolVersion,
@@ -99,6 +89,21 @@
9989
from cassandra.datastax.graph.query import _request_timeout_key
10090
from cassandra.datastax import cloud as dscloud
10191

92+
try:
93+
from cassandra.io.twistedreactor import TwistedConnection
94+
except ImportError:
95+
TwistedConnection = None
96+
97+
try:
98+
from cassandra.io.eventletreactor import EventletConnection
99+
except ImportError:
100+
EventletConnection = None
101+
102+
try:
103+
from weakref import WeakSet
104+
except ImportError:
105+
from cassandra.util import WeakSet # NOQA
106+
102107
if six.PY3:
103108
long = int
104109

@@ -1104,10 +1109,9 @@ def __init__(self,
11041109
raise ValueError("contact_points, endpoint_factory, ssl_context, and ssl_options "
11051110
"cannot be specified with a cloud configuration")
11061111

1107-
cloud_config = dscloud.get_cloud_config(
1108-
cloud,
1109-
create_pyopenssl_context=self.connection_class is TwistedConnection
1110-
)
1112+
uses_twisted = TwistedConnection and issubclass(self.connection_class, TwistedConnection)
1113+
uses_eventlet = EventletConnection and issubclass(self.connection_class, EventletConnection)
1114+
cloud_config = dscloud.get_cloud_config(cloud, create_pyopenssl_context=uses_twisted or uses_eventlet)
11111115

11121116
ssl_context = cloud_config.ssl_context
11131117
ssl_options = {'check_hostname': True}

cassandra/connection.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,15 @@ def factory(cls, endpoint, timeout, *args, **kwargs):
772772
else:
773773
return conn
774774

775+
def _wrap_socket_from_context(self):
776+
self._socket = self.ssl_context.wrap_socket(self._socket, **(self.ssl_options or {}))
777+
778+
def _initiate_connection(self, sockaddr):
779+
self._socket.connect(sockaddr)
780+
781+
def _match_hostname(self):
782+
ssl.match_hostname(self._socket.getpeercert(), self.endpoint.address)
783+
775784
def _get_socket_addresses(self):
776785
address, port = self.endpoint.resolve()
777786

@@ -791,17 +800,16 @@ def _connect_socket(self):
791800
try:
792801
self._socket = self._socket_impl.socket(af, socktype, proto)
793802
if self.ssl_context:
794-
self._socket = self.ssl_context.wrap_socket(self._socket,
795-
**(self.ssl_options or {}))
803+
self._wrap_socket_from_context()
796804
elif self.ssl_options:
797805
if not self._ssl_impl:
798806
raise RuntimeError("This version of Python was not compiled with SSL support")
799807
self._socket = self._ssl_impl.wrap_socket(self._socket, **self.ssl_options)
800808
self._socket.settimeout(self.connect_timeout)
801-
self._socket.connect(sockaddr)
809+
self._initiate_connection(sockaddr)
802810
self._socket.settimeout(None)
803811
if self._check_hostname:
804-
ssl.match_hostname(self._socket.getpeercert(), self.endpoint.address)
812+
self._match_hostname()
805813
sockerr = None
806814
break
807815
except socket.error as err:

cassandra/datastax/cloud/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
import os
1616
import logging
1717
import json
18+
import sys
1819
import tempfile
1920
import shutil
21+
import six
2022
from six.moves.urllib.request import urlopen
2123

2224
_HAS_SSL = True
@@ -177,8 +179,12 @@ def _ssl_context_from_cert(ca_cert_location, cert_location, key_location):
177179
def _pyopenssl_context_from_cert(ca_cert_location, cert_location, key_location):
178180
try:
179181
from OpenSSL import SSL
180-
except ImportError:
181-
return None
182+
except ImportError as e:
183+
six.reraise(
184+
ImportError,
185+
ImportError("PyOpenSSL must be installed to connect to Apollo with the Eventlet or Twisted event loops"),
186+
sys.exc_info()[2]
187+
)
182188
ssl_context = SSL.Context(SSL.TLSv1_METHOD)
183189
ssl_context.set_verify(SSL.VERIFY_PEER, callback=lambda _1, _2, _3, _4, ok: ok)
184190
ssl_context.use_certificate_file(cert_location)

cassandra/io/eventletreactor.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
# Originally derived from MagnetoDB source:
1717
# https://github.com/stackforge/magnetodb/blob/2015.1.0b1/magnetodb/common/cassandra/io/eventletreactor.py
18-
1918
import eventlet
2019
from eventlet.green import socket
2120
from eventlet.queue import Queue
@@ -27,11 +26,25 @@
2726
from six.moves import xrange
2827

2928
from cassandra.connection import Connection, ConnectionShutdown, Timer, TimerManager
29+
try:
30+
from eventlet.green.OpenSSL import SSL
31+
_PYOPENSSL = True
32+
except ImportError as e:
33+
_PYOPENSSL = False
34+
no_pyopenssl_error = e
3035

3136

3237
log = logging.getLogger(__name__)
3338

3439

40+
def _check_pyopenssl():
41+
if not _PYOPENSSL:
42+
raise ImportError(
43+
"{}, pyOpenSSL must be installed to enable "
44+
"SSL support with the Eventlet event loop".format(str(no_pyopenssl_error))
45+
)
46+
47+
3548
class EventletConnection(Connection):
3649
"""
3750
An implementation of :class:`.Connection` that utilizes ``eventlet``.
@@ -81,6 +94,7 @@ def service_timeouts(cls):
8194

8295
def __init__(self, *args, **kwargs):
8396
Connection.__init__(self, *args, **kwargs)
97+
self.uses_legacy_ssl_options = self.ssl_options and not self.ssl_context
8498
self._write_queue = Queue()
8599

86100
self._connect_socket()
@@ -89,6 +103,31 @@ def __init__(self, *args, **kwargs):
89103
self._write_watcher = eventlet.spawn(lambda: self.handle_write())
90104
self._send_options_message()
91105

106+
def _wrap_socket_from_context(self):
107+
_check_pyopenssl()
108+
self._socket = SSL.Connection(self.ssl_context, self._socket)
109+
self._socket.set_connect_state()
110+
if self.ssl_options and 'server_hostname' in self.ssl_options:
111+
# This is necessary for SNI
112+
self._socket.set_tlsext_host_name(self.ssl_options['server_hostname'].encode('ascii'))
113+
114+
def _initiate_connection(self, sockaddr):
115+
if self.uses_legacy_ssl_options:
116+
super(EventletConnection, self)._initiate_connection(sockaddr)
117+
else:
118+
self._socket.connect(sockaddr)
119+
if self.ssl_context or self.ssl_options:
120+
self._socket.do_handshake()
121+
122+
def _match_hostname(self):
123+
if self.uses_legacy_ssl_options:
124+
super(EventletConnection, self)._match_hostname()
125+
else:
126+
cert_name = self._socket.get_peer_certificate().get_subject().commonName
127+
if cert_name != self.endpoint.address:
128+
raise Exception("Hostname verification failed! Certificate name '{}' "
129+
"doesn't endpoint '{}'".format(cert_name, self.endpoint.address))
130+
92131
def close(self):
93132
with self.lock:
94133
if self.is_closed:

docs/cloud.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,4 @@ Limitations
3434

3535
Event loops
3636
^^^^^^^^^^^
37-
Evenlet isn't supported yet. Eventlet still uses the old way to configure
38-
SSL (ssl_options), which is not compatible with the secure connect bundle provided by Apollo.
37+
Evenlet isn't yet supported for python 3.7+ due to an `issue in Eventlet <https://github.com/eventlet/eventlet/issues/526>`_.

docs/security.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ It might be also useful to learn about the different levels of identity verifica
7878

7979
* `Using SSL in DSE drivers <https://docs.datastax.com/en/dse/6.7/dse-dev/datastax_enterprise/appDevGuide/sslDrivers.html>`_
8080

81-
SSL with Twisted
82-
^^^^^^^^^^^^^^^^
83-
Twisted uses an alternative SSL implementation called pyOpenSSL, so if your `Cluster`'s connection class is
84-
:class:`~cassandra.io.twistedreactor.TwistedConnection`, you must pass a
81+
SSL with Twisted or Eventlet
82+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
83+
Twisted and Eventlet both use an alternative SSL implementation called pyOpenSSL, so if your `Cluster`'s connection class is
84+
:class:`~cassandra.io.twistedreactor.TwistedConnection` or :class:`~cassandra.io.eventletreactor.EventletConnection`, you must pass a
8585
`pyOpenSSL context <https://www.pyopenssl.org/en/stable/api/ssl.html#context-objects>`_ instead.
8686
An example is provided in these docs, and more details can be found in the
8787
`documentation <https://www.pyopenssl.org/en/stable/api/ssl.html#context-objects>`_.
@@ -270,6 +270,10 @@ for more details about ``SSLContext`` configuration.
270270
)
271271
session = cluster.connect()
272272
273+
274+
Connecting using Eventlet would look similar except instead of importing and using ``TwistedConnection``, you would
275+
import and use ``EventletConnection``, including the appropriate monkey-patching.
276+
273277
Versions 3.16.0 and lower
274278
^^^^^^^^^^^^^^^^^^^^^^^^^
275279

tests/integration/long/test_ssl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
DRIVER_CERTFILE = os.path.abspath("tests/integration/long/ssl/driver.pem")
5252
DRIVER_CERTFILE_BAD = os.path.abspath("tests/integration/long/ssl/python_driver_bad.pem")
5353

54-
USES_PYOPENSSL = "twisted" in EVENT_LOOP_MANAGER
54+
USES_PYOPENSSL = "twisted" in EVENT_LOOP_MANAGER or "eventlet" in EVENT_LOOP_MANAGER
5555
if "twisted" in EVENT_LOOP_MANAGER:
5656
import OpenSSL
5757
ssl_version = OpenSSL.SSL.TLSv1_METHOD

0 commit comments

Comments
 (0)