12
12
13
13
import ssl
14
14
import cassandra .cluster
15
+ import re
16
+
17
+
18
+ # This function normalizes the SSL cipher suite name, which we need because:
19
+ # - python's library, which tests' driver uses,
20
+ # - C's library, which scylla server uses,
21
+ # namings conventions are different, and we cannot simply compare them for equality.
22
+ def normalize_cipher (cipher_name : str ) -> str :
23
+ if cipher_name .startswith ("TLS_" ):
24
+ cipher_name = cipher_name [len ("TLS_" ):] # Remove leading "TLS_" if present.
25
+ cipher_name = cipher_name .replace ("_WITH_" , "-" )
26
+ cipher_name = cipher_name .replace ("_" , "-" )
27
+ # Remove hyphen between letters and digits: e.g. convert "AES-256" to "AES256"
28
+ cipher_name = re .sub (r'([A-Z]+)-(\d+)' , r'\1\2' , cipher_name )
29
+ return cipher_name
30
+
15
31
16
32
# Test that TLS 1.2 is supported (because this is what "cqlsh --ssl" uses
17
33
# by default), and that other TLS version are either supported - or if
@@ -27,8 +43,8 @@ def test_tls_versions(cql):
27
43
# TLS v1.2 must be supported, because this is the default version that
28
44
# "cqlsh --ssl" uses. If this fact changes in the future, we may need
29
45
# to reconsider this test.
30
- try_connect (cql .cluster , ssl .TLSVersion .TLSv1_2 )
31
- print (f"{ ssl .TLSVersion .TLSv1_2 } supported" )
46
+ with try_connect (cql .cluster , ssl .TLSVersion .TLSv1_2 ):
47
+ print (f"{ ssl .TLSVersion .TLSv1_2 } supported" )
32
48
33
49
# All other protocol versions should either work (if Scylla is configured
34
50
# to allow them) or fail with the expected error message.
@@ -37,15 +53,31 @@ def test_tls_versions(cql):
37
53
ssl .TLSVersion .TLSv1 ,
38
54
ssl .TLSVersion .SSLv3 ]:
39
55
try :
40
- try_connect (cql .cluster , ssl_version )
41
- print (f"{ ssl_version } supported" )
56
+ with try_connect (cql .cluster , ssl_version ) as session :
57
+ print (f"{ ssl_version } supported" )
58
+ table_result = session .execute (f"SELECT * FROM system.clients" )
59
+ for row in table_result :
60
+ assert row .ssl_enabled == True # ' == True` is redundant, but makes it clear
61
+ assert row .hostname == '127.0.0.1' # is that always the case?
62
+ # Even if we request to connect with a specific SSL version,
63
+ # the connection may end up negotiating a different version,
64
+ # thus we can't assert for a specific match.
65
+ assert row .ssl_protocol in ["TLS1" , "TLS1.0" , "TLS1.1" , "TLS1.2" , "TLS1.3" , "SSL3" ]
66
+ # The session object returned by the Cassandra driver's connect() method
67
+ # does not expose the low‑level TLS session details, such as the negotiated cipher suite,
68
+ # so we need to, again, assert that it matches one of the expected values.
69
+ expected_ciphers = [normalize_cipher (cipher ['name' ]) for cipher in ssl .create_default_context ().get_ciphers ()]
70
+ actual_cipher = normalize_cipher (row .ssl_cipher_suite )
71
+ assert actual_cipher in expected_ciphers
42
72
except cassandra .cluster .NoHostAvailable as e :
43
73
# For the various TLS versions, we get the new TLS alert
44
74
# "protocol version". But for SSL, we get the older
45
75
# "no protocols available" error.
46
76
assert 'protocol version' in str (e ) or 'no protocols available' in str (e )
47
77
print (f"{ ssl_version } not supported" )
48
78
79
+
80
+ @contextmanager
49
81
def try_connect (orig_cluster , ssl_version ):
50
82
ssl_context = ssl .SSLContext (ssl .PROTOCOL_TLS )
51
83
ssl_context .minimum_version = ssl_version
@@ -63,8 +95,11 @@ def try_connect(orig_cluster, ssl_version):
63
95
# so let's increase them to 60 seconds. See issue #11289.
64
96
connect_timeout = 60 ,
65
97
control_connection_timeout = 60 )
66
- cluster .connect ()
67
- cluster .shutdown ()
98
+ try :
99
+ session = cluster .connect ()
100
+ yield session
101
+ finally :
102
+ cluster .shutdown ()
68
103
69
104
# Test that if we try to connect to an SSL port with *unencrypted* CQL,
70
105
# it doesn't work.
0 commit comments