From 8c42514320ff0a61d809e34dac53eda54cb3da68 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Mon, 19 May 2025 16:44:28 +0200 Subject: [PATCH 1/9] PYTHON-1419 Connection failure to SNI endpoint when first host is unavailable --- cassandra/connection.py | 16 ++++++++++++---- tests/unit/test_endpoints.py | 12 ++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/cassandra/connection.py b/cassandra/connection.py index bfe38fc702..0a27e3a5d5 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -245,9 +245,9 @@ def create(self, row): class SniEndPoint(EndPoint): """SNI Proxy EndPoint implementation.""" - def __init__(self, proxy_address, server_name, port=9042): + def __init__(self, proxy_address, server_name, port=9042, init_index=0): self._proxy_address = proxy_address - self._index = 0 + self._index = init_index self._resolved_address = None # resolved address self._port = port self._server_name = server_name @@ -305,16 +305,24 @@ class SniEndPointFactory(EndPointFactory): def __init__(self, proxy_address, port): self._proxy_address = proxy_address self._port = port + # Initial lookup index to prevent all SNI endpoints to be resolved + # into the same starting IP address (which might not be available currently). + # If SNI resolves to 3 IPs, first endpoint will connect to first + # IP address, and subsequent resolutions to next IPs in round-robin + # fusion. + self._init_index = -1 def create(self, row): host_id = row.get("host_id") if host_id is None: raise ValueError("No host_id to create the SniEndPoint") - return SniEndPoint(self._proxy_address, str(host_id), self._port) + self._init_index += 1 + return SniEndPoint(self._proxy_address, str(host_id), self._port, self._init_index) def create_from_sni(self, sni): - return SniEndPoint(self._proxy_address, sni, self._port) + self._init_index += 1 + return SniEndPoint(self._proxy_address, sni, self._port, self._init_index) @total_ordering diff --git a/tests/unit/test_endpoints.py b/tests/unit/test_endpoints.py index b0841962ca..4352afb9a5 100644 --- a/tests/unit/test_endpoints.py +++ b/tests/unit/test_endpoints.py @@ -65,3 +65,15 @@ def test_endpoint_resolve(self): for i in range(10): (address, _) = endpoint.resolve() self.assertEqual(address, next(it)) + + def test_sni_resolution_start_index(self): + factory = SniEndPointFactory("proxy.datastax.com", 9999) + initial_index = factory._init_index + + endpoint1 = factory.create_from_sni('sni1') + self.assertEqual(factory._init_index, initial_index + 1) + self.assertEqual(endpoint1._index, factory._init_index) + + endpoint2 = factory.create_from_sni('sni2') + self.assertEqual(factory._init_index, initial_index + 2) + self.assertEqual(endpoint2._index, factory._init_index) From 9698893c96179ea58d1d5f6f50aa09b234219130 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Tue, 1 Oct 2024 12:11:26 +0200 Subject: [PATCH 2/9] PYTHON-1331 Recreate expired SSL certificates for integration tests --- cassandra/connection.py | 4 +- tests/integration/long/ssl/127.0.0.1.keystore | Bin 4317 -> 4541 bytes tests/integration/long/ssl/ca-cert | 21 ++++++ tests/integration/long/ssl/ca-key | 28 ++++++++ .../integration/long/ssl/cassandra.truststore | Bin 1074 -> 930 bytes tests/integration/long/ssl/client.crt | 21 ++++++ tests/integration/long/ssl/client.crt_signed | 19 ------ tests/integration/long/ssl/client.key | 56 ++++++++-------- .../integration/long/ssl/client_encrypted.key | 60 +++++++++--------- .../long/ssl/generate_certificates.sh | 31 +++++++++ tests/integration/long/ssl/rootCa.crt | 19 ------ tests/integration/long/test_ssl.py | 6 +- 12 files changed, 165 insertions(+), 100 deletions(-) create mode 100644 tests/integration/long/ssl/ca-cert create mode 100644 tests/integration/long/ssl/ca-key create mode 100644 tests/integration/long/ssl/client.crt delete mode 100644 tests/integration/long/ssl/client.crt_signed create mode 100755 tests/integration/long/ssl/generate_certificates.sh delete mode 100644 tests/integration/long/ssl/rootCa.crt diff --git a/cassandra/connection.py b/cassandra/connection.py index 0a27e3a5d5..b89717003c 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -787,15 +787,13 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, self.ssl_options.update(self.endpoint.ssl_options or {}) elif self.endpoint.ssl_options: self.ssl_options = self.endpoint.ssl_options + self._check_hostname = self.ssl_options.get('check_hostname', False) # 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 should contain only args needed for the ssl_context.wrap_socket() call. if not self.ssl_context and self.ssl_options: self.ssl_context = self._build_ssl_context_from_options() diff --git a/tests/integration/long/ssl/127.0.0.1.keystore b/tests/integration/long/ssl/127.0.0.1.keystore index 98193ab54e271fc4d241442371204f3a63457262..aefae73ca2501c16b115b6cce66089668891d453 100644 GIT binary patch delta 4475 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 diff --git a/tests/integration/long/ssl/ca-cert b/tests/integration/long/ssl/ca-cert new file mode 100644 index 0000000000..ab0bb28a6e --- /dev/null +++ b/tests/integration/long/ssl/ca-cert @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXzCCAkegAwIBAgIUNaZrKLGgSDvEMiIZE401OeWIYXQwDQYJKoZIhvcNAQEL +BQAwPzEQMA4GA1UEAwwHcm9vdC1jYTEQMA4GA1UECwwHZHJpdmVyczEMMAoGA1UE +CgwDb3NzMQswCQYDVQQGEwJVUzAeFw0yNDA5MjQwODUwMThaFw0zNDA5MjIwODUw +MThaMD8xEDAOBgNVBAMMB3Jvb3QtY2ExEDAOBgNVBAsMB2RyaXZlcnMxDDAKBgNV +BAoMA29zczELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCK9qGc3CboY44t8K28q3GEVGsJieT5b3qNpsI1HBmJ7L6u0z2+qNCq6YS8 +zT4Dyf/E0lIluh1hfnHF0ZuPOc9tODZPuqGJrdSHDCgoh0pGgSG5Nne4YT/RLwtG +/F1DXVFBZRMvxqo+A5Td7R2jk/iAy0pIQNghxYOYyaq8bGV/CbkEgS3OUto3yA0F +UPyJLuBKlvw5/1gNOyWy2HRUHIrwMBSuFZ5cgjewWH8Q9WoFcaHvT5gh0+Rzffn9 +TEfuwsFDS8e9QMc6MmicCZ5y7xk3/J1ZRbk9ovh/AA7dhS9Q4LFmFr9e5MH7Yafu +LWk+12gRItC/W/r95PQF03dSPaQdAgMBAAGjUzBRMB0GA1UdDgQWBBRnQujD5pLP +J5ZalKZ0Ij3Zi0uJTjAfBgNVHSMEGDAWgBRnQujD5pLPJ5ZalKZ0Ij3Zi0uJTjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQByyImDmYFnn/D3gLCy +F6ZrOV2xywDk36rfSfrpRK29E++3PBMvl/e5UrDQaI5dsoNjYiAO4J3xZqA0DQan +/6Pf1x/SL04nZnMuY73UFBovtk2RzkFJFPv11+m8muWiS2aiL1IEd83tpGXGaVXY +cmj+iqCupQGdZf9Qz3RhXi1Ye7m7joszYWazFCyAg2FtkwXeWBZcmRQFv3V3R6lt +cyZKLFjKCa8hyeEjYoTC53Fd9ibTdIEWtSWSvgGTDuKD1AjFvr92iYHaw3xsv1WF +8QXU6SjDaJfs7Crzm0B+5eQTjIp7Dwt5FfB5RSnnewiMqaMpI9HKvgA/Ru0iEb/8 +ANcF +-----END CERTIFICATE----- diff --git a/tests/integration/long/ssl/ca-key b/tests/integration/long/ssl/ca-key new file mode 100644 index 0000000000..4e804f18bd --- /dev/null +++ b/tests/integration/long/ssl/ca-key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCK9qGc3CboY44t +8K28q3GEVGsJieT5b3qNpsI1HBmJ7L6u0z2+qNCq6YS8zT4Dyf/E0lIluh1hfnHF +0ZuPOc9tODZPuqGJrdSHDCgoh0pGgSG5Nne4YT/RLwtG/F1DXVFBZRMvxqo+A5Td +7R2jk/iAy0pIQNghxYOYyaq8bGV/CbkEgS3OUto3yA0FUPyJLuBKlvw5/1gNOyWy +2HRUHIrwMBSuFZ5cgjewWH8Q9WoFcaHvT5gh0+Rzffn9TEfuwsFDS8e9QMc6Mmic +CZ5y7xk3/J1ZRbk9ovh/AA7dhS9Q4LFmFr9e5MH7YafuLWk+12gRItC/W/r95PQF +03dSPaQdAgMBAAECggEAAbNuG/o9Ma1SRrFXobkO0nn+thGthKNpgnAEQtvSsN5T +ISxYaPaDrgEzYjo4OZn7MEtgvQck/UCryio8IgnTm6Mgqw4o6a3/2B1SeoMuv2PX +kqmeLTASNLsY2L1rCNGMwTpS/KE3tpBFqLJny/eaMZK2GIyj+JnZzVYelGAr8oea +fA6v7O7DKgZ4ozMe8UNzBdmCUCcCPVJK42XvcwS2c+/bIJi86Wj/1I/r3LGONFJ5 +8iaiC4GTMqyLNIEFoo+bFeLfV3SDgXX5/J2uvyJziKDrx8N+1qnn5bEfVL8ViE8W +65Fa4Ht1A2IPSqkbw/fTzlfAAYCRfgdRGz/UYyRysQKBgQDAYkT3cQlkqxZ2DS14 +laK36EOu8moB2qwzN2kepZim1C/IXQKp5jwotNDIwrJsWorfEXyA22m9BBVnk2J+ +OIKPH3BH1RzPV7YFSHVXSrq7yA157OO7+CaB8dXGrvdu8fkIFyJNxFreSXjSn01S +RWjjrstJLKmWD44HmCT12/Z/yQKBgQC46jLbRB8kjpC4UU7RwEEfZ/QKh1xnzdxg +heqR2oEUyHLY6T0GZIltAGV7frCzIqBAxzGm8rWrvmx+Sv2whuN+T0X3DXjskELM +++wjJy8ZRroVpD/4AhIQqasZXSyyydRjDGFkn83d5Ski8oR33FxYZT/a/+yIKiHM +LLRrWB5ztQKBgQCZMqfw01bDj2pHf57iE2aMRK0BN5ErANN3xXw0J3I0B2w1hbuF +SA5H7BUGieRDXKaRk/8tLYw6NHJHFJquIJn3FvX2fcJ/aj1MX7LxXFTvDBOPMBD5 +slYXzFiL6vCmrJG+2405mE80DBXmw2xzQ0qPZLYFA0fYc3KKoaFtF0hn4QKBgQCn +wRf4IbnbEVcrT+Agm7i4xDb6Ykirh2/ZRURDo6Yc86h1LkuFhCnEcGqgeZPWP6CA +g/WAjonP0AZfIKs7vXOfAE3pzhgZDNr9WcKlNYQd+zMQNR0vYrl+0l39ubC2VjHO +1cl5XxyFpMMICFmy34ALVXdzt1+fPBHDR/85rwyZTQKBgFC9VXukiHiF0JVRHJwh +WFi16M4wAh7juPQjskAXK6USkuUZMSkONpQqFwVVKbxp4f+F3VKtDRWUYtiuZDgW +AosCimrs16KxTV1pjgJCE/C2b4ANAApStxZxzdN3qnwS5myNYEgU9cSNwfmKSoes +XOMwluTpn+FdmDye+Lw7nmoM +-----END PRIVATE KEY----- diff --git a/tests/integration/long/ssl/cassandra.truststore b/tests/integration/long/ssl/cassandra.truststore index b31e34b8aaad16a24f56d19d3f30f097727816a1..3ac9ab05e621f59d11b97fe63a4bedd30b1e60df 100644 GIT binary patch literal 930 zcmezO_TO6u1_mY|W(3o0$%#ez`6WQ^BxTDdYZzD~^h^yb85o$84Vswa4VswT7cet1 zGBJsmF3Z-~xWL2uh>?<{aIdN5(~iUv170>xtu~Lg@4SqR+^h@+_J#rmd~D32EX+LY zAX9ad6Ja9UJnSh&nPsU(#fCfvTp%@EJk0sU#fIDloFE>XFjHu-ft)z6k%@sN5L=iU z7+OS$^BRM=Mo=#07Bn#`Av>Cpm4Ug5k)Oe!iIIz`iII_^>)XOPchp`a_vwCEyJvM_ zOGq|n=aZlLRlUm&naW6ZzS+0#vhBVV7goJ&*>l#8`Q-m2mx5Gx$tKnn9=$lb-|~E} zg_-}Zg`I1!wDV|aw0pTVD(*BZ-;rp4QJ>rGPpornpku1A{;^ed%v0{Zm0djfN5g3^ z4~H9yN1JDyT(u`BwVrb)OQY_&pj+lAcv%Dfbm~3un)b)?e*~|!>ZThdAu?Sb3`Ew6 z&WmX>-w;tR@HLCIaN&FZ8H$&m6xaUz>*N0J&_QSK<9i*BTN!1{;hb0WUef%}+(_4* zwu^q$Gw|JQ)em^EF->fL+>?X96PLf!&9u9oA*gg=fAp`vPrk5TE)TL@BFn_g$iTQb z*dWkA78onCd@N!tBI!;q4nLc8UVU2Blw~DKwl}-IJN*pgLDI@B5(Z)o*cI@D6bLgi z{%2t|U+>ECw-ZMcxp7rUuBIZ1)@iYLVpZyXo~SzUK~8{rIu&2=o!sj&~mMDhuK3pYAXVJQ%tSPR+L(kP;%# z6t;Rl6-YihrCU@K@;O|f0k1<;11d<5AS&W@(wV3mhbsHa#t?Z*RCh*Q>Jz) z#xH#;*w`l3MzW#$ZjUd6nq>-r=((f|?BR|xrVc2JZRiZF-d4s8g9W6FpS9$^m{eXI z(gAnnIYUrjebAHu$O5@^ojmp3VU$dP2%KwC@P{vpcs^uk?0xB}SMxsI&X&c%)dV+} zFQVi~UiiO~Z_3w;YuJDU|68Ro?{#4nrXkPiF1c2C?LF%Tz26%IyxlO!&jTC@m%0Va zx>a)%T*cX43*P<7AeEUw8-;W#8gJ$cc#zwNxg9P7 z;}y$!0_YIgCNb^ce=ODLa-RMOkChZJzF{8;YyMu@SApoDMFMTSw=RNWG7+m#&nStR zxI7$^9-|-&S|a$>4#(SZhAaUWz82z=RO*DBHEX+Ji;3fFQMTO$E0%M;JlI%l+}4uiP2)e2T2T?31=m;s+WC3I{`wczLYN_Xr0+D_v z_b|-Z&ae=)+)w<5V+BZ2v0Z>a25Q+(B;f0ExINz1I6MA1p}Q%>MRZ6CMrjX#W|@7; zWDf0+>9Tj{{=_hg+e$w4?)+PHDO?6C6pj_f)&!Q0s{etpihnU9RL6T diff --git a/tests/integration/long/ssl/client.crt b/tests/integration/long/ssl/client.crt new file mode 100644 index 0000000000..4dfa834665 --- /dev/null +++ b/tests/integration/long/ssl/client.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYTCCAkmgAwIBAgIUGtLp1v/4I4YFhA1t7Y4kb2LPkXYwDQYJKoZIhvcNAQEL +BQAwPzEQMA4GA1UEAwwHcm9vdC1jYTEQMA4GA1UECwwHZHJpdmVyczEMMAoGA1UE +CgwDb3NzMQswCQYDVQQGEwJVUzAeFw0yNDA5MjQwODUwMzlaFw0zNDA5MjIwODUw +MzlaMEExCzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNvc3MxEDAOBgNVBAsTB2RyaXZl +cnMxEjAQBgNVBAMTCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALj5zQWQP+Q1Zsrvnf0lFOmIMUwG3CnkKYIH+3w6UCBTimnqYUdWmDxX +rx2EMgOCkpWAQ+IdnPNygJdSdBiiv4Io6mCCtHYXQOLdvofQF+O7e7FTlWLAzaJF +3Sk7wHJGk2Xwm5uKr/EXtr7vT1a0WzNmPmdDMR5CBx/urnSob4v4wgWaXeJEQVGR +Q2Oe2cps2dl0kieq0iFdaaHxlaHFbODhBm7EmRPHmjPbEmTBkVlAXwvP9TWzf0K+ +XvguJ0ePadsG84PslheY7Vw0Ul6j2neshZR0aO/pjVDNRzTCtG2fwhJG+D7zwgcU +kxbDgmeVWJIgo4Z3C+jxfn2yKkO/i2UCAwEAAaNTMFEwDwYDVR0RBAgwBocEfwAA +ATAdBgNVHQ4EFgQU+jUkfI5lW5C5KYPWlNpa0RESilwwHwYDVR0jBBgwFoAUZ0Lo +w+aSzyeWWpSmdCI92YtLiU4wDQYJKoZIhvcNAQELBQADggEBAIKE5Xk52FbSz3h5 +ecl8GvdJlYrABzIXns41IV4ThJM5ki4Y2WVOk+t2dm74p61XHkCLaO+OltHuGNAO +dzuFnkEAEp6bILJQZ+bsSCn5mBwj5b6lup0n8Jdf01Gr6wmUemf4joiBMKz3J0JL +JVg56l5Wsz9MGIKra49z735rOE+VR+WgcZM95xHwXqN++jI4+c7GVuG4ShhHqpfV +mBS6bJ+pwxa3bClNYg+e9PWvEzzN6m6jg4Mgnxgz8Moj4BiNelxr+7QQCg8f6Ide +DNhwU/irKXukd0/HMzNvS9z6SsgK3V51txl0lah77T5Wjo5u310XbcU7/uAgqc35 +OcCwg7Q= +-----END CERTIFICATE----- diff --git a/tests/integration/long/ssl/client.crt_signed b/tests/integration/long/ssl/client.crt_signed deleted file mode 100644 index db3d903f19..0000000000 --- a/tests/integration/long/ssl/client.crt_signed +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDDjCCAfYCFAG4WryLorTXxNtrkEJ56zUg/XdDMA0GCSqGSIb3DQEBCwUAMEIx -CzAJBgNVBAYTAlVTMREwDwYDVQQKDAhkYXRhc3RheDEPMA0GA1UECwwGZmllbGRz -MQ8wDQYDVQQDDAZyb290Q2EwHhcNMjEwMzE3MTcwNTE4WhcNMjIwMzE3MTcwNTE4 -WjBFMQswCQYDVQQGEwJVUzERMA8GA1UECgwIZGF0YXN0YXgxDzANBgNVBAsMBmZp -ZWxkczESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAnrpE3g8pbQn2tVVidX2Ww1rh/6YIH6EGW9hXMO/F506ReMruv+Al -ilc7B2sPpGRDKXupy23IcpfMIe9+Lm74/yu7pW51rJ/r2jMqg+tViFa/GQxSQLKd -AxDAvwJaAM41kro0DKmcm4RwfYAltupwc6pC7AfBtT08PBuDK7WfaNnFbhGAWkHv -MbULNWAKbPWqITHbUEvLgS/uPj+/W4SHk5GaYk0Y2mU3aWypeDOBqEfKTi2W0ix1 -O7SpOHyfA0hvXS9IilF/HWURvr9u13mnvJNe8W+uqWqlQMdyFsbPCIhbVwVwGYQp -yoyBrgz6y5SPwSyugAb2F8Yk3UpvqH30yQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB -AQB5XV+3NS5UpwpTXTYsadLL8XcdGsfITMs4MSv0N3oir++TUzTc3cOd2T6YVdEc -ypw5CKTYnFTK9oF2PZXeV+aLIjdvK4AukQurB8EdXq4Hu7y1b61OaGRqiKTVsIne -LwxCXpc42jqMFt4mMXpmU/hSCjRSvoumTcL1aHUzaPlSIasD2JDyLurO64gxQypi -wbD9gliPJ60pdhY0m9NfF5F2PdqBuJXrhF1VuxYx1/cfo/c1A4UK2slhsZCDls7/ -HbM8ri5Z74M1EtCGFcTNYvm0xlfF5arisGQSKhTw+06LnpUlQi5a8NRNBLeAmem/ -cuICJJbnSzjmq9skkp8i/ejH ------END CERTIFICATE----- diff --git a/tests/integration/long/ssl/client.key b/tests/integration/long/ssl/client.key index d6b8811a94..0d1e08e8bd 100644 --- a/tests/integration/long/ssl/client.key +++ b/tests/integration/long/ssl/client.key @@ -1,28 +1,32 @@ +Bag Attributes + friendlyName: 127.0.0.1 + localKeyID: 54 69 6D 65 20 31 37 32 37 31 36 37 38 32 34 36 37 31 +Key Attributes: -----BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCeukTeDyltCfa1 -VWJ1fZbDWuH/pggfoQZb2Fcw78XnTpF4yu6/4CWKVzsHaw+kZEMpe6nLbchyl8wh -734ubvj/K7ulbnWsn+vaMyqD61WIVr8ZDFJAsp0DEMC/AloAzjWSujQMqZybhHB9 -gCW26nBzqkLsB8G1PTw8G4MrtZ9o2cVuEYBaQe8xtQs1YAps9aohMdtQS8uBL+4+ -P79bhIeTkZpiTRjaZTdpbKl4M4GoR8pOLZbSLHU7tKk4fJ8DSG9dL0iKUX8dZRG+ -v27Xeae8k17xb66paqVAx3IWxs8IiFtXBXAZhCnKjIGuDPrLlI/BLK6ABvYXxiTd -Sm+offTJAgMBAAECggEAN+VysRx3wy1aEvuRo7xpZjxQD/5BKBpFqfxioBogAFfb -xMT6FNnzfmc/o1ohdQvV1vr0jW4Iw8oPGfhD4Eg2KW4WM6jVicf7f6i7FR+/zDZ4 -L3L2WFBOGLFCn0FNvrDfjt9Byx/DxcR69Mc3ANZIaYMQ9Bu7LH73AlfR9oeMLpjL -+6g1qz2yz8Sm2CMCGXTyXtvUCgn2ld6nz8KlZ8FTUG9C9mAabuvV91Ko6rmTxuiv -YKvHSPnIjXRjuC+Ozjf1rYTOJ5LVMNNhlbIKBG/Nx5QzL7bA3XDtMD1BEI9pdHR+ -5HwA0tV2Ex67tBCJwlBAhYLxuPjfOj1R5KV8wriE3QKBgQDNvqOaGYiXwp9Rajoo -ltlOBPfnjshd9tPdc6tTUQR34vSbkHrg0HVJhvIP5LRbyx/M/8ACQxFkDRE4U7fJ -xVGDs8Pi0FqcqFTnm/AYQ5eZbJkPp9qe71aDOPanncrVNEGFeW26LaeLGbTLrOMM -6mTmsfGig0MKgml35IMrP+oPuwKBgQDFf56DdaFe08xSK9pDWuKxUuBIagGExQkQ -r9eYasBc336CXh3FWtpSlxl73dqtISh/HbKbv+OZfkVdbmkcTVGlWm/N/XvLqpPK -86kbKW6PY8FxIY/RxiZANf/JJ5gzPp6VQMJeSy+oepeWj11mTLcT02plvIMM0Jmg -Z5B9Hw37SwKBgDR/59lDmLI47FRnCc4fp/WbmPKSYZhwimFgyZ/p9XzuAcLMXD6P -ks4fTBc4IbmmnEfAHuu013QzTWiVHDm1SvaTYXG3/tcosPmkteBLJxz0NB5lk4io -w+eaGn5s6jv7KJj5gkFWswDwn0y1of5CtVqUn3b7jZjZ7DW2rq3TklNPAoGAIzaW -56+AfyzaQEhrWRkKVD2HmcG01Zxf+mav1RArjiOXJd1sB3UkehdQxuIOjFHeK5P6 -9YQoK4T1DyyRdydeCFJwntS0TuLyCPyaySoA+XX61pX6U5e12DsIiTATFgfzNH9g -aHmVXL/G6WRUbdn9xn4qeUs8Pnuu+IeenoB7+LMCgYBBnig9nTp81U+SGsNl2D3J -WUz4z+XzEfKU1nq2s4KNjIPB2T1ne+1x3Uso2hagtEHeuEbZoRY4dtCahAvYwrPM -8wtDFQXWmvFyN3X0Js65GZ++knuseQ1tdlbc/4C+k4u26tVe2GcwhKTjn08++L2E -UB3pLXbssswH271OjD+QkQ== +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC4+c0FkD/kNWbK +7539JRTpiDFMBtwp5CmCB/t8OlAgU4pp6mFHVpg8V68dhDIDgpKVgEPiHZzzcoCX +UnQYor+CKOpggrR2F0Di3b6H0Bfju3uxU5ViwM2iRd0pO8ByRpNl8Jubiq/xF7a+ +709WtFszZj5nQzEeQgcf7q50qG+L+MIFml3iREFRkUNjntnKbNnZdJInqtIhXWmh +8ZWhxWzg4QZuxJkTx5oz2xJkwZFZQF8Lz/U1s39Cvl74LidHj2nbBvOD7JYXmO1c +NFJeo9p3rIWUdGjv6Y1QzUc0wrRtn8ISRvg+88IHFJMWw4JnlViSIKOGdwvo8X59 +sipDv4tlAgMBAAECggEAHZfxgiNa5XLZuDvvxdFJ8DbW1DgAvz7+mQwX4v8dVJ6o +9VsHJzemcXkBzjIZIlCgjQSRV7qvIo++HPeXFV3sT7GmFbyzjHUZ73HUirvzJn8X +Qf6CVuNLwtt0j6U8m8vIxzVgX9knXuYRWajFw7RlJusDrtekIxgjNaulA4rzFax3 +hoJa8JYUizjZnTe2hhZSdG7JzbBV5n9Wei2rPTMXEI1llyCBb/MfhTBrCIYeF9PO +IYCAi/0i2en5uVTgQlwejGp5/xj1KWnbD1S5FWZgj88AXwHfVvEFxheEXxYXhLav +XGlrGxb1x/uFn651c3rWxMdfZc9T9QITSWuD7EFF4QKBgQD25n5/OtcQYGUoVH4g +o+wdiWva5FgzAlcaA3ciNW5Dtx/8obrkO3zJEDP3p4tnTRJEkWjuZaHMTCsq+K9U +egHgrTCQMpMV1xydkdUPVaBD7QXLr528VvNOiHdruxt7cRxVGbGzbwCj8dDwzLhe +W8tcmz02XTzfk6Vz+l73AS6IKQKBgQC/ywxOTx0tZPeK24d4rE4ufK9GYH8LQ1M+ +9HFh5VZZPyGM8zKQk4YJzQChwpRSMEToqY7x/51QDa02/mHNkntS6fw48TnBCt41 +JfYRfhOhVDCyFKOJ+vuM6RHlkZHFTxUvtZdnneuG/9HXY4HY64dSrKLqXGjWZ9ou +zqcVrHQA3QKBgAq+lRqsUNehmkVbB/IbsBbI+Cyaa0ws+eVj6TdP4/CGc5nm3982 +x4NodRp97A8ex4C8Yzicq6HcXrSMBfVDKfnBD6/2w3fb2J7yzbbRHxxVoD7w8YhU +sFnmjmvdxKBml7kMWTNZzUlVKKaSAiP5EqyBBPTssc14+2ZEqwVMw92hAoGADgtR +UF6stUlCczGWHvkHFJJex1mDlBCPBPojX1bK1ugvjcG1Py7+TrNrS20TLV2JfjwE +UqY0H8uQlolUIhiK3UxzArxvTTp9gQjRlwBTcanXkwK94vm09+GNRPE+6mLbG05B +0v2WZKFQ/WO0+2xr0VsA5wZzStf5+xl41LZ3HCUCgYAUyrj2/elSKdaXzNCVsLTU +PmOpQUiBUTt2YJ06UiZL0V+ompEl15MhDssMJcsJSfxEYmgExNvWJEWwJQy9LNoy +YZHj8PycoQOGYtbPwstleTmdKh0MfgKO3dmSSfueQur1p9/kjy+OYB4yiKcaPw0z +aaEu6ksnOjRTK5ZBhDhK0Q== -----END PRIVATE KEY----- diff --git a/tests/integration/long/ssl/client_encrypted.key b/tests/integration/long/ssl/client_encrypted.key index 49f475d7fe..645fd714b0 100644 --- a/tests/integration/long/ssl/client_encrypted.key +++ b/tests/integration/long/ssl/client_encrypted.key @@ -1,30 +1,30 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-256-CBC,7288A409E846EBE2DE421B77598DAF98 - -ahiUSf+k9POIEUJb5BGbQ6knk4+FtTz+e+6fouqVc4Lq+RXR9f0pFBi9eDEkFNiN -AcUjLkxh+3TmihTZJprqXSbQ3jacwbnwDOFgtZE3PxoA1heHxADaKCNr+Ph0lC/T -3cIzsoIZ6slk+3n6ERieZRdmvoMH1SY8nXKT5+bLMR4RIjw1y7h26MRhjQS+lXaX -Asd5EOGROCIgefeEBGHAbrlg0FoHy7slqVBxuZphTHKtyK/VK4fRLt6doUzBu5GJ -T2jdrqJCWr5PRn3bAqMemJWxDhZLX4DyNDQPn8riZ8jMbwPOVUSnF8B8re1tNkQ0 -CsH77sYIIjmPdizCdvj91+jH6o7MRCZPvky+PHG/9G5WsPiw5W1i/nrPemT1XJyy -oPRc/fMFfbHmW3HCGqgv2/6Wg+17un/a6UyzXsbNdhDZLCVqtAQ7PSv83z5oUazT -djzFHgxSqRknUY0lOUvP8Rni67MG+Rcksj9HgszhLoC0be64IX0Ey5oc5+pBYrf9 -FVEPsuyyu4aDSRYYATC2E1V/EQRwcvpKEZNFTbqMpQhjrWtlBM/GgQnQBeQdLAGX -yefDSzkH31y5gcdgHLElriWwbHHbcaAmf3e15W94YHgTytJBsQ9A19SmtmgUmo4h -jaFoUooM5mFA8hc/snSe2PdkEefkzS72g8qxa//61LTJAAkVk43dYjoqQ34wq6WR -OB4nn/W2xlfv/ClZJTWf8YvQTrQptJY5VQq/TTEcrXy67Uc0wRHXZK2rTjKeyRj9 -65SkyyXhMopWEl2vX25ReITVfdJ0FgjqI/ugYSf25iOfJtsk+jgrtrswZ+8F2eMq -iAQ+0JSiYmlot2Pn1QCalLjtTz8zeMfXPyo5fbKNMdp52U1cPYld90kUGHZfjqju -GmY/aHa6N8lZGxj8SC/JM36GawaGKe4S/F5BetYJOpaEzkpowqlTC8Syv529rm46 -vvgf+EJL8gRvdtnIEe/qtzbtel299VhaBpuOcApfTDSxRHZmvkCpdHo9I3KgOZB9 -Cqu9Bz+FiJmTk8rGQwmI8EYj38jneEoqA+fN7tUkzxCGacg+x6ke4nOcJzgBhd94 -8DvGclrcAwBY1mlNYRceFJKFXhwLZTKBojZlS8Q9863EAH3DOBLeP85V3YvBD/MK -O+kzPoxN/jPVNho7y4gL7skcqe/IXePzPxBcZrHJjoU7mGVDcVcouRj16XSezMbB -5Pft0/gGiItRJ2+v9DlPjzDfjTuRdS78muaZ4nNqX6B+JmyPJtkb2CdiHz6B21RO -3hjGrffM1nhmYBegyjTVc88IxzYg0T8CZLq1FYxuTZmwyahA520IpwsbfwXxLVMU -5rmou5dj1pVlvoP3l+ivPqugeY3k7UjZ33m5H9p009JR40dybr1S2RbI8Gqhe953 -0bedA4DWvPakODXgYu43al92uR/tyjazeB5t7Iu8uB5Xcm3/Mqoofe9xtdQSCWa0 -jKKvXzSpL1MM2C0bRyYHIkVR65K7Zmi/BzvTaPECo1+Uv+EwqRZRyBzUZKPP8LMq -jTCOBmYaK8+0dTRk8MEzrPW2ihVVJYVMmFyTZKW0iK7kOMKZRkhDCaNSUlPEty7j ------END RSA PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQGwgW+7olu2AXiupx +NxswrAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEIbwGGQKe8+6Kb+s +ngNKUFcEggTQPKEHVA1qFFXANwtAMXezfPAK9JlLHdAJNiSMy8RWvR+TLOt5xJ2b +FSP8c8ME425YXVyvV8XlQ4P9czr7UODuU7/aU0PL4gCrOWSPA+azWA3mJJEjx/QK +CY1MgrG++09TP1n7yv9kij7a3/3gOxEe9IkM+uLq8tjZi9Xhsvi68jmJUTXRD4UB +9moMtUH16LG866HeUwFk3P9ASoBoRDzKiq81FoU7iITNw+Hes5+2Tcs9ENfhPd43 +5Y9WrFFnArwuR7BZVqt72Bwme5iQfn4X7yczG4iyx7dk8DXCfvslWL1nWBoSRoVS +H1Xj9pNABgLbjO25/NI1ycTmX/f2dTq5QE5MuIAQJ5gfYjLQhYczswE/G7QqjyLA +5AMu+nz/B9oBLeRcjL2e5363bGD2/70lQdL2MvLxyTaPyYo9cOmzDSZfzYjzx1ro +y1wDlreKKT5zrPfQbZ1LTjmaWdLbI2t8UUy6X1H+E0qY5IsTIm9VfNSQJcmgtJSP +nAbdDvZlD2NGbpjDsjbmX1xwKG2z4JNyP0BS2PXd3STvBCCO6rUKovuyk7MlS3Kn +HU8F4spe0YAMuYZNG72XZuG1AhXGhGG0rCVnkaakyXH5kgUA76cmj5ONU5fX4B0Y +g/6+V/BelK5hVYUq9vUZEzUcY/IrWPoDe27nGmrFVaCTHymjrp+KUixiUJOkGP25 +z7URMsVPElkcPhNnfb9Wf1EAei//ETd5U7aVaxYSau6nijI+LhPWxBZNKjGQytEd +tFqc29GmIlIk22zZGj0OwMz6hm/OqQxAq9jHn34ZukqXzFlQ6/rmFKIQIVcA3HQL +NT7TgMCJqNB3pub2RhHS5iY8GatUT8OeXklGF7GLQV3xvEEMxm9+KmIe4F47I8P5 +V0soBKNDlZaiiKNE9WHld4zinbwZ/DNlpuuzeQeAPTii57CgSoDXyt+rST30lftp +OwCQ62j+h3sGTR2OexmILVIXBcrko/B3/MXQ4wmXKBasrEPlfuSBpm5QQ7eviM8r +55hkWlXFYA0ND+IlLnUB1MMcsGhvfrzbI1RlzL1CN0Vt3UyPZvrgJJKHfEQRUXcz +SWiZz1PaJNBNVYOfvAzWru1tv9ZVH7RMOQnoVOXoJBNHBAUA6f93W8x+dFuaaqRn +9v/snIAT5gNoNVllMWHeK1QPfEYJ90cDiUaxi8EiETuVpf/vGYSgbOV7VpTIhCq0 +buoWwN1/hEar+JhseK6b3qWKki9SHhwk3zN8y3+wt7lAA8eMhIY2dnz8rG2qiCRs +Co8qBYGgsYzqAGqutFuepMF8lGmVUw6g5MOEf2goIjdQ6PgcWHAFT//O5RrQEE86 +I4lRU0wn/kZfgPWOxMoghVTLZOLH14/pooMZwph+zLr6y3qp5QBlcPhZZETTo4B+ +iLEEoTPspJ/RsbI9OCoxTpQ/VrRKbHNUGOeI4HULEq04y0cZ+Vaaknktw2/xhUkk +78Mpj14fYmgp57jfAj8Xq8LkBPdW/FWMG+zfElu4U8Kz/Fgk2WSmj54idOu/zZUe +Y97ARqyP0upUL4PlE8glAFxbpWcwjKivoc9p2xb/gfomObeLzvxPXYzWXKqYc8dV +ZbgiJwDLOpIdBy+46sAkHXbhXLQ4+FpVEL4QohcPuPnuQoRNTjoz5wU= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/integration/long/ssl/generate_certificates.sh b/tests/integration/long/ssl/generate_certificates.sh new file mode 100755 index 0000000000..75029530c8 --- /dev/null +++ b/tests/integration/long/ssl/generate_certificates.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# create new CA key and certificate +openssl req -new -newkey rsa:2048 -days 3650 -x509 -subj "/CN=root-ca/OU=drivers/O=oss/C=US" -keyout ca-key -out ca-cert -nodes + +# create keystore and key-pair for DSE server +keytool -genkey -keyalg RSA -keystore 127.0.0.1.keystore -validity 3650 -storepass cassandra -keypass cassandra -dname "CN=127.0.0.1,OU=drivers,O=oss,C=US" -ext "SAN=IP:127.0.0.1" -alias 127.0.0.1 -storetype pkcs12 + +# export DSE server key from keystore +openssl pkcs12 -in 127.0.0.1.keystore -nodes -nocerts -out client.key -legacy -passin pass:cassandra + +# create encrypted client key +openssl rsa -aes256 -in client.key -passout pass:cassandra -out client_encrypted.key + +# create CSR +keytool -keystore 127.0.0.1.keystore -alias 127.0.0.1 -certreq -file client.csr -storepass cassandra -ext san=ip:127.0.0.1 + +# sign CSR with CA key +openssl x509 -req -CA ca-cert -CAkey ca-key -in client.csr -out client.crt -days 3650 -copy_extensions copyall -passin pass:cassandra + +# import CA certificate to DSE node keystore +keytool -keystore 127.0.0.1.keystore -alias CARoot -import -file ca-cert -storepass cassandra -noprompt + +# import signed certificate to DSE node keystore +keytool -keystore 127.0.0.1.keystore -alias 127.0.0.1 -import -file client.crt -storepass cassandra -noprompt + +# import CA certificate to DSE node truststore +keytool -keystore cassandra.truststore -alias CARoot -import -file ca-cert -storepass cassandra -noprompt + +# cleanup +rm client.csr \ No newline at end of file diff --git a/tests/integration/long/ssl/rootCa.crt b/tests/integration/long/ssl/rootCa.crt deleted file mode 100644 index a0a0ec73cf..0000000000 --- a/tests/integration/long/ssl/rootCa.crt +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDCzCCAfMCFCoTNYhIQpOXMBnAq8Bw72qfKwGLMA0GCSqGSIb3DQEBCwUAMEIx -CzAJBgNVBAYTAlVTMREwDwYDVQQKDAhkYXRhc3RheDEPMA0GA1UECwwGZmllbGRz -MQ8wDQYDVQQDDAZyb290Q2EwHhcNMjEwMzE3MTcwNTE2WhcNMzEwMzE1MTcwNTE2 -WjBCMQswCQYDVQQGEwJVUzERMA8GA1UECgwIZGF0YXN0YXgxDzANBgNVBAsMBmZp -ZWxkczEPMA0GA1UEAwwGcm9vdENhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEApFoQtNu0+XQuMBPle4WAJYIMR74HL15uk9ToKBqMEXL7ah3r23xTTeGr -NyUXicM6Owiup7DK27F4vni+MYKAn7L4uZ99mW0ATYNXBDLFB+wwy1JBk4Dw5+eZ -q9lz1TGK7uBvTOXCllOA2qxRqtMTl2aPy5OuciWQe794abwFqs5+1l9GEuzJGsp1 -P9L4yljbmijC8RmvDFAeUZoKRdKXw2G5kUOHqK9Aej5gLxIK920PezpgLxm0V/PD -ZAlwlsW0vT79RgZCF/vtKcKSLtFTHgPBNPPbkZmOdE7s/6KoAkORBV/9CIsKeTC3 -Y/YeYQ2+G0gxiq1RcMavPw8f58POTQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA1 -MXBlk6u2oVBM+4SyYc2nsaHyerM+omUEysAUNFJq6S6i0pu32ULcusDfrnrIQoyR -xPJ/GSYqZkIDX0s9LvPVD6A6bnugR+Z6VfEniLkG1+TkFC+JMCblgJyaF/EbuayU -3iJX+uj7ikTySjMSDvXxOHik2i0aOh90B/351+sFnSPQrFDQ0XqxeG8s0d7EiLTV -wWJmsYglSeTo1vF3ilVRwjmHO9sX6cmQhRvRNmiQrdWaM3gLS5F6yoQ2UQQ3YdFp -quhYuNwy0Ip6ZpORHYtzkCKSanz/oUh17QWvi7aaJyqD5G5hWZgn3R4RCutoOHRS -TEJ+xzhY768rpsrrNUou ------END CERTIFICATE----- diff --git a/tests/integration/long/test_ssl.py b/tests/integration/long/test_ssl.py index 0e39cb21ad..f60adc6db4 100644 --- a/tests/integration/long/test_ssl.py +++ b/tests/integration/long/test_ssl.py @@ -42,10 +42,10 @@ SERVER_TRUSTSTORE_PATH = os.path.abspath("tests/integration/long/ssl/cassandra.truststore") # Client specific keys/certs -CLIENT_CA_CERTS = os.path.abspath("tests/integration/long/ssl/rootCa.crt") +CLIENT_CA_CERTS = os.path.abspath("tests/integration/long/ssl/ca-cert") DRIVER_KEYFILE = os.path.abspath("tests/integration/long/ssl/client.key") DRIVER_KEYFILE_ENCRYPTED = os.path.abspath("tests/integration/long/ssl/client_encrypted.key") -DRIVER_CERTFILE = os.path.abspath("tests/integration/long/ssl/client.crt_signed") +DRIVER_CERTFILE = os.path.abspath("tests/integration/long/ssl/client.crt") DRIVER_CERTFILE_BAD = os.path.abspath("tests/integration/long/ssl/client_bad.key") USES_PYOPENSSL = "twisted" in EVENT_LOOP_MANAGER or "eventlet" in EVENT_LOOP_MANAGER @@ -486,7 +486,7 @@ def test_cannot_connect_ssl_context_with_invalid_hostname(self): password="cassandra", ) ssl_context.verify_mode = ssl.CERT_REQUIRED - ssl_options["check_hostname"] = True + ssl_context.check_hostname = True with self.assertRaises(Exception): validate_ssl_options(ssl_context=ssl_context, ssl_options=ssl_options, hostname="localhost") From 98a592ac92f5f32c1c41884f28dff95406f970d6 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Wed, 21 May 2025 15:20:26 +0200 Subject: [PATCH 3/9] PYTHON-1331 Integration test --- cassandra/connection.py | 7 +- tests/integration/long/test_sni_connection.py | 94 +++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 tests/integration/long/test_sni_connection.py diff --git a/cassandra/connection.py b/cassandra/connection.py index b89717003c..0dc0a7c4bc 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -267,8 +267,7 @@ def ssl_options(self): def resolve(self): try: - resolved_addresses = socket.getaddrinfo(self._proxy_address, self._port, - socket.AF_UNSPEC, socket.SOCK_STREAM) + resolved_addresses = self._resolve_proxy_addresses() except socket.gaierror: log.debug('Could not resolve sni proxy hostname "%s" ' 'with port %d' % (self._proxy_address, self._port)) @@ -280,6 +279,10 @@ def resolve(self): return self._resolved_address, self._port + def _resolve_proxy_addresses(self): + return socket.getaddrinfo(self._proxy_address, self._port, + socket.AF_UNSPEC, socket.SOCK_STREAM) + def __eq__(self, other): return (isinstance(other, SniEndPoint) and self.address == other.address and self.port == other.port and diff --git a/tests/integration/long/test_sni_connection.py b/tests/integration/long/test_sni_connection.py new file mode 100644 index 0000000000..fad0da6645 --- /dev/null +++ b/tests/integration/long/test_sni_connection.py @@ -0,0 +1,94 @@ +# Copyright DataStax, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import socket +import ssl +import unittest +from unittest.mock import patch + +from cassandra import ProtocolVersion +from cassandra.cluster import Cluster, ControlConnection, ProfileManager +from cassandra.connection import SniEndPoint +from cassandra.datastax.cloud import CloudConfig +from cassandra.policies import ConstantReconnectionPolicy +from tests.integration import ( + get_cluster, remove_cluster, CASSANDRA_IP +) +from tests.integration.long.test_ssl import setup_cluster_ssl, CLIENT_CA_CERTS, DRIVER_CERTFILE, DRIVER_KEYFILE + + +class SniConnectionTests(unittest.TestCase): + + @classmethod + def setUpClass(cls): + setup_cluster_ssl(client_auth=True) + + @classmethod + def tearDownClass(cls): + ccm_cluster = get_cluster() + ccm_cluster.stop() + remove_cluster() + + def _mocked_cloud_config(self, cloud_config, create_pyopenssl_context): + config = CloudConfig.from_dict({}) + config.sni_host = 'proxy.datastax.com' + config.sni_port = 9042 + config.host_ids = ['8c4b6ed7-f505-4226-b7a4-41f322520c1f', '2e25021d-8d72-41a7-a247-3da85c5d92d2'] + + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS) + ssl_context.load_verify_locations(CLIENT_CA_CERTS) + ssl_context.verify_mode = ssl.CERT_REQUIRED + ssl_context.load_cert_chain(certfile=DRIVER_CERTFILE, keyfile=DRIVER_KEYFILE) + config.ssl_context = ssl_context + + return config + + def _mocked_proxy_dns_resolution(self): + return [ + # return wrong IP at first position, so that we make sure all SNI endpoints + # do not start with first IP only + (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('100.101.102.103', 9042)), + (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, (CASSANDRA_IP, 9042)) + ] + + def _mocked_refresh_node_list_and_token_map(self, connection, preloaded_results=None, + force_token_rebuild=False): + return + + def _mocked_refresh_schema(self, connection, preloaded_results=None, schema_agreement_wait=None, + force=False, **kwargs): + return + + def _mocked_check_supported(self): + return + + # Tests verifies that driver can connect to SNI endpoint even when one IP + # returned by the DNS resolution of SNI does not respond. Mocked SNI resolution method + # returns two IPs where only one corresponds to online C* cluster started with CCM. + def test_round_robin_dns_resolution(self): + with patch('cassandra.datastax.cloud.get_cloud_config', self._mocked_cloud_config): + with patch.object(SniEndPoint, '_resolve_proxy_addresses', self._mocked_proxy_dns_resolution): + # Mock below three functions, because host ID returned from proxy will not match ID present in C* + # Network connection should be already made, so we can consider our test successful + with patch.object(ControlConnection, '_refresh_node_list_and_token_map', + self._mocked_refresh_node_list_and_token_map): + with patch.object(ControlConnection, '_refresh_schema', + self._mocked_refresh_schema): + with patch.object(ProfileManager, 'check_supported', self._mocked_check_supported): + cloud_config = { + 'secure_connect_bundle': '/path/to/secure-connect-dbname.zip' + } + cluster = Cluster(cloud=cloud_config, protocol_version=ProtocolVersion.V4, reconnection_policy=ConstantReconnectionPolicy(10)) + session = cluster.connect() + session.shutdown() + cluster.shutdown() From 72978f534213970a67eabb51cf5b9e1c420540bf Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Wed, 21 May 2025 20:17:55 +0200 Subject: [PATCH 4/9] Revert "PYTHON-1331 Recreate expired SSL certificates for integration tests" This reverts commit 9698893c96179ea58d1d5f6f50aa09b234219130. --- cassandra/connection.py | 4 +- tests/integration/long/ssl/127.0.0.1.keystore | Bin 4541 -> 4317 bytes tests/integration/long/ssl/ca-cert | 21 ------ tests/integration/long/ssl/ca-key | 28 -------- .../integration/long/ssl/cassandra.truststore | Bin 930 -> 1074 bytes tests/integration/long/ssl/client.crt | 21 ------ tests/integration/long/ssl/client.crt_signed | 19 ++++++ tests/integration/long/ssl/client.key | 56 ++++++++-------- .../integration/long/ssl/client_encrypted.key | 60 +++++++++--------- .../long/ssl/generate_certificates.sh | 31 --------- tests/integration/long/ssl/rootCa.crt | 19 ++++++ tests/integration/long/test_ssl.py | 6 +- 12 files changed, 100 insertions(+), 165 deletions(-) delete mode 100644 tests/integration/long/ssl/ca-cert delete mode 100644 tests/integration/long/ssl/ca-key delete mode 100644 tests/integration/long/ssl/client.crt create mode 100644 tests/integration/long/ssl/client.crt_signed delete mode 100755 tests/integration/long/ssl/generate_certificates.sh create mode 100644 tests/integration/long/ssl/rootCa.crt diff --git a/cassandra/connection.py b/cassandra/connection.py index 0dc0a7c4bc..5c7d71b72e 100644 --- a/cassandra/connection.py +++ b/cassandra/connection.py @@ -790,13 +790,15 @@ def __init__(self, host='127.0.0.1', port=9042, authenticator=None, self.ssl_options.update(self.endpoint.ssl_options or {}) elif self.endpoint.ssl_options: self.ssl_options = self.endpoint.ssl_options - self._check_hostname = self.ssl_options.get('check_hostname', False) # 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 should contain only args needed for the ssl_context.wrap_socket() call. if not self.ssl_context and self.ssl_options: self.ssl_context = self._build_ssl_context_from_options() diff --git a/tests/integration/long/ssl/127.0.0.1.keystore b/tests/integration/long/ssl/127.0.0.1.keystore index aefae73ca2501c16b115b6cce66089668891d453..98193ab54e271fc4d241442371204f3a63457262 100644 GIT binary patch delta 4249 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 delta 4475 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 literal 930 zcmezO_TO6u1_mY|W(3o0$%#ez`6WQ^BxTDdYZzD~^h^yb85o$84Vswa4VswT7cet1 zGBJsmF3Z-~xWL2uh>?<{aIdN5(~iUv170>xtu~Lg@4SqR+^h@+_J#rmd~D32EX+LY zAX9ad6Ja9UJnSh&nPsU(#fCfvTp%@EJk0sU#fIDloFE>XFjHu-ft)z6k%@sN5L=iU z7+OS$^BRM=Mo=#07Bn#`Av>Cpm4Ug5k)Oe!iIIz`iII_^>)XOPchp`a_vwCEyJvM_ zOGq|n=aZlLRlUm&naW6ZzS+0#vhBVV7goJ&*>l#8`Q-m2mx5Gx$tKnn9=$lb-|~E} zg_-}Zg`I1!wDV|aw0pTVD(*BZ-;rp4QJ>rGPpornpku1A{;^ed%v0{Zm0djfN5g3^ z4~H9yN1JDyT(u`BwVrb)OQY_&pj+lAcv%Dfbm~3un)b)?e*~|!>ZThdAu?Sb3`Ew6 z&WmX>-w;tR@HLCIaN&FZ8H$&m6xaUz>*N0J&_QSK<9i*BTN!1{;hb0WUef%}+(_4* zwu^q$Gw|JQ)em^EF->fL+>?X96PLf!&9u9oA*gg=fAp`vPrk5TE)TL@BFn_g$iTQb z*dWkA78onCd@N!tBI!;q4nLc8UVU2Blw~DKwl}-IJN*pgLDI@B5(Z)o*cI@D6bLgi z{%2t|U+>ECw-ZMcxp7rUuBIZ1)@ -----BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC4+c0FkD/kNWbK -7539JRTpiDFMBtwp5CmCB/t8OlAgU4pp6mFHVpg8V68dhDIDgpKVgEPiHZzzcoCX -UnQYor+CKOpggrR2F0Di3b6H0Bfju3uxU5ViwM2iRd0pO8ByRpNl8Jubiq/xF7a+ -709WtFszZj5nQzEeQgcf7q50qG+L+MIFml3iREFRkUNjntnKbNnZdJInqtIhXWmh -8ZWhxWzg4QZuxJkTx5oz2xJkwZFZQF8Lz/U1s39Cvl74LidHj2nbBvOD7JYXmO1c -NFJeo9p3rIWUdGjv6Y1QzUc0wrRtn8ISRvg+88IHFJMWw4JnlViSIKOGdwvo8X59 -sipDv4tlAgMBAAECggEAHZfxgiNa5XLZuDvvxdFJ8DbW1DgAvz7+mQwX4v8dVJ6o -9VsHJzemcXkBzjIZIlCgjQSRV7qvIo++HPeXFV3sT7GmFbyzjHUZ73HUirvzJn8X -Qf6CVuNLwtt0j6U8m8vIxzVgX9knXuYRWajFw7RlJusDrtekIxgjNaulA4rzFax3 -hoJa8JYUizjZnTe2hhZSdG7JzbBV5n9Wei2rPTMXEI1llyCBb/MfhTBrCIYeF9PO -IYCAi/0i2en5uVTgQlwejGp5/xj1KWnbD1S5FWZgj88AXwHfVvEFxheEXxYXhLav -XGlrGxb1x/uFn651c3rWxMdfZc9T9QITSWuD7EFF4QKBgQD25n5/OtcQYGUoVH4g -o+wdiWva5FgzAlcaA3ciNW5Dtx/8obrkO3zJEDP3p4tnTRJEkWjuZaHMTCsq+K9U -egHgrTCQMpMV1xydkdUPVaBD7QXLr528VvNOiHdruxt7cRxVGbGzbwCj8dDwzLhe -W8tcmz02XTzfk6Vz+l73AS6IKQKBgQC/ywxOTx0tZPeK24d4rE4ufK9GYH8LQ1M+ -9HFh5VZZPyGM8zKQk4YJzQChwpRSMEToqY7x/51QDa02/mHNkntS6fw48TnBCt41 -JfYRfhOhVDCyFKOJ+vuM6RHlkZHFTxUvtZdnneuG/9HXY4HY64dSrKLqXGjWZ9ou -zqcVrHQA3QKBgAq+lRqsUNehmkVbB/IbsBbI+Cyaa0ws+eVj6TdP4/CGc5nm3982 -x4NodRp97A8ex4C8Yzicq6HcXrSMBfVDKfnBD6/2w3fb2J7yzbbRHxxVoD7w8YhU -sFnmjmvdxKBml7kMWTNZzUlVKKaSAiP5EqyBBPTssc14+2ZEqwVMw92hAoGADgtR -UF6stUlCczGWHvkHFJJex1mDlBCPBPojX1bK1ugvjcG1Py7+TrNrS20TLV2JfjwE -UqY0H8uQlolUIhiK3UxzArxvTTp9gQjRlwBTcanXkwK94vm09+GNRPE+6mLbG05B -0v2WZKFQ/WO0+2xr0VsA5wZzStf5+xl41LZ3HCUCgYAUyrj2/elSKdaXzNCVsLTU -PmOpQUiBUTt2YJ06UiZL0V+ompEl15MhDssMJcsJSfxEYmgExNvWJEWwJQy9LNoy -YZHj8PycoQOGYtbPwstleTmdKh0MfgKO3dmSSfueQur1p9/kjy+OYB4yiKcaPw0z -aaEu6ksnOjRTK5ZBhDhK0Q== +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCeukTeDyltCfa1 +VWJ1fZbDWuH/pggfoQZb2Fcw78XnTpF4yu6/4CWKVzsHaw+kZEMpe6nLbchyl8wh +734ubvj/K7ulbnWsn+vaMyqD61WIVr8ZDFJAsp0DEMC/AloAzjWSujQMqZybhHB9 +gCW26nBzqkLsB8G1PTw8G4MrtZ9o2cVuEYBaQe8xtQs1YAps9aohMdtQS8uBL+4+ +P79bhIeTkZpiTRjaZTdpbKl4M4GoR8pOLZbSLHU7tKk4fJ8DSG9dL0iKUX8dZRG+ +v27Xeae8k17xb66paqVAx3IWxs8IiFtXBXAZhCnKjIGuDPrLlI/BLK6ABvYXxiTd +Sm+offTJAgMBAAECggEAN+VysRx3wy1aEvuRo7xpZjxQD/5BKBpFqfxioBogAFfb +xMT6FNnzfmc/o1ohdQvV1vr0jW4Iw8oPGfhD4Eg2KW4WM6jVicf7f6i7FR+/zDZ4 +L3L2WFBOGLFCn0FNvrDfjt9Byx/DxcR69Mc3ANZIaYMQ9Bu7LH73AlfR9oeMLpjL ++6g1qz2yz8Sm2CMCGXTyXtvUCgn2ld6nz8KlZ8FTUG9C9mAabuvV91Ko6rmTxuiv +YKvHSPnIjXRjuC+Ozjf1rYTOJ5LVMNNhlbIKBG/Nx5QzL7bA3XDtMD1BEI9pdHR+ +5HwA0tV2Ex67tBCJwlBAhYLxuPjfOj1R5KV8wriE3QKBgQDNvqOaGYiXwp9Rajoo +ltlOBPfnjshd9tPdc6tTUQR34vSbkHrg0HVJhvIP5LRbyx/M/8ACQxFkDRE4U7fJ +xVGDs8Pi0FqcqFTnm/AYQ5eZbJkPp9qe71aDOPanncrVNEGFeW26LaeLGbTLrOMM +6mTmsfGig0MKgml35IMrP+oPuwKBgQDFf56DdaFe08xSK9pDWuKxUuBIagGExQkQ +r9eYasBc336CXh3FWtpSlxl73dqtISh/HbKbv+OZfkVdbmkcTVGlWm/N/XvLqpPK +86kbKW6PY8FxIY/RxiZANf/JJ5gzPp6VQMJeSy+oepeWj11mTLcT02plvIMM0Jmg +Z5B9Hw37SwKBgDR/59lDmLI47FRnCc4fp/WbmPKSYZhwimFgyZ/p9XzuAcLMXD6P +ks4fTBc4IbmmnEfAHuu013QzTWiVHDm1SvaTYXG3/tcosPmkteBLJxz0NB5lk4io +w+eaGn5s6jv7KJj5gkFWswDwn0y1of5CtVqUn3b7jZjZ7DW2rq3TklNPAoGAIzaW +56+AfyzaQEhrWRkKVD2HmcG01Zxf+mav1RArjiOXJd1sB3UkehdQxuIOjFHeK5P6 +9YQoK4T1DyyRdydeCFJwntS0TuLyCPyaySoA+XX61pX6U5e12DsIiTATFgfzNH9g +aHmVXL/G6WRUbdn9xn4qeUs8Pnuu+IeenoB7+LMCgYBBnig9nTp81U+SGsNl2D3J +WUz4z+XzEfKU1nq2s4KNjIPB2T1ne+1x3Uso2hagtEHeuEbZoRY4dtCahAvYwrPM +8wtDFQXWmvFyN3X0Js65GZ++knuseQ1tdlbc/4C+k4u26tVe2GcwhKTjn08++L2E +UB3pLXbssswH271OjD+QkQ== -----END PRIVATE KEY----- diff --git a/tests/integration/long/ssl/client_encrypted.key b/tests/integration/long/ssl/client_encrypted.key index 645fd714b0..49f475d7fe 100644 --- a/tests/integration/long/ssl/client_encrypted.key +++ b/tests/integration/long/ssl/client_encrypted.key @@ -1,30 +1,30 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQGwgW+7olu2AXiupx -NxswrAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEIbwGGQKe8+6Kb+s -ngNKUFcEggTQPKEHVA1qFFXANwtAMXezfPAK9JlLHdAJNiSMy8RWvR+TLOt5xJ2b -FSP8c8ME425YXVyvV8XlQ4P9czr7UODuU7/aU0PL4gCrOWSPA+azWA3mJJEjx/QK -CY1MgrG++09TP1n7yv9kij7a3/3gOxEe9IkM+uLq8tjZi9Xhsvi68jmJUTXRD4UB -9moMtUH16LG866HeUwFk3P9ASoBoRDzKiq81FoU7iITNw+Hes5+2Tcs9ENfhPd43 -5Y9WrFFnArwuR7BZVqt72Bwme5iQfn4X7yczG4iyx7dk8DXCfvslWL1nWBoSRoVS -H1Xj9pNABgLbjO25/NI1ycTmX/f2dTq5QE5MuIAQJ5gfYjLQhYczswE/G7QqjyLA -5AMu+nz/B9oBLeRcjL2e5363bGD2/70lQdL2MvLxyTaPyYo9cOmzDSZfzYjzx1ro -y1wDlreKKT5zrPfQbZ1LTjmaWdLbI2t8UUy6X1H+E0qY5IsTIm9VfNSQJcmgtJSP -nAbdDvZlD2NGbpjDsjbmX1xwKG2z4JNyP0BS2PXd3STvBCCO6rUKovuyk7MlS3Kn -HU8F4spe0YAMuYZNG72XZuG1AhXGhGG0rCVnkaakyXH5kgUA76cmj5ONU5fX4B0Y -g/6+V/BelK5hVYUq9vUZEzUcY/IrWPoDe27nGmrFVaCTHymjrp+KUixiUJOkGP25 -z7URMsVPElkcPhNnfb9Wf1EAei//ETd5U7aVaxYSau6nijI+LhPWxBZNKjGQytEd -tFqc29GmIlIk22zZGj0OwMz6hm/OqQxAq9jHn34ZukqXzFlQ6/rmFKIQIVcA3HQL -NT7TgMCJqNB3pub2RhHS5iY8GatUT8OeXklGF7GLQV3xvEEMxm9+KmIe4F47I8P5 -V0soBKNDlZaiiKNE9WHld4zinbwZ/DNlpuuzeQeAPTii57CgSoDXyt+rST30lftp -OwCQ62j+h3sGTR2OexmILVIXBcrko/B3/MXQ4wmXKBasrEPlfuSBpm5QQ7eviM8r -55hkWlXFYA0ND+IlLnUB1MMcsGhvfrzbI1RlzL1CN0Vt3UyPZvrgJJKHfEQRUXcz -SWiZz1PaJNBNVYOfvAzWru1tv9ZVH7RMOQnoVOXoJBNHBAUA6f93W8x+dFuaaqRn -9v/snIAT5gNoNVllMWHeK1QPfEYJ90cDiUaxi8EiETuVpf/vGYSgbOV7VpTIhCq0 -buoWwN1/hEar+JhseK6b3qWKki9SHhwk3zN8y3+wt7lAA8eMhIY2dnz8rG2qiCRs -Co8qBYGgsYzqAGqutFuepMF8lGmVUw6g5MOEf2goIjdQ6PgcWHAFT//O5RrQEE86 -I4lRU0wn/kZfgPWOxMoghVTLZOLH14/pooMZwph+zLr6y3qp5QBlcPhZZETTo4B+ -iLEEoTPspJ/RsbI9OCoxTpQ/VrRKbHNUGOeI4HULEq04y0cZ+Vaaknktw2/xhUkk -78Mpj14fYmgp57jfAj8Xq8LkBPdW/FWMG+zfElu4U8Kz/Fgk2WSmj54idOu/zZUe -Y97ARqyP0upUL4PlE8glAFxbpWcwjKivoc9p2xb/gfomObeLzvxPXYzWXKqYc8dV -ZbgiJwDLOpIdBy+46sAkHXbhXLQ4+FpVEL4QohcPuPnuQoRNTjoz5wU= ------END ENCRYPTED PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,7288A409E846EBE2DE421B77598DAF98 + +ahiUSf+k9POIEUJb5BGbQ6knk4+FtTz+e+6fouqVc4Lq+RXR9f0pFBi9eDEkFNiN +AcUjLkxh+3TmihTZJprqXSbQ3jacwbnwDOFgtZE3PxoA1heHxADaKCNr+Ph0lC/T +3cIzsoIZ6slk+3n6ERieZRdmvoMH1SY8nXKT5+bLMR4RIjw1y7h26MRhjQS+lXaX +Asd5EOGROCIgefeEBGHAbrlg0FoHy7slqVBxuZphTHKtyK/VK4fRLt6doUzBu5GJ +T2jdrqJCWr5PRn3bAqMemJWxDhZLX4DyNDQPn8riZ8jMbwPOVUSnF8B8re1tNkQ0 +CsH77sYIIjmPdizCdvj91+jH6o7MRCZPvky+PHG/9G5WsPiw5W1i/nrPemT1XJyy +oPRc/fMFfbHmW3HCGqgv2/6Wg+17un/a6UyzXsbNdhDZLCVqtAQ7PSv83z5oUazT +djzFHgxSqRknUY0lOUvP8Rni67MG+Rcksj9HgszhLoC0be64IX0Ey5oc5+pBYrf9 +FVEPsuyyu4aDSRYYATC2E1V/EQRwcvpKEZNFTbqMpQhjrWtlBM/GgQnQBeQdLAGX +yefDSzkH31y5gcdgHLElriWwbHHbcaAmf3e15W94YHgTytJBsQ9A19SmtmgUmo4h +jaFoUooM5mFA8hc/snSe2PdkEefkzS72g8qxa//61LTJAAkVk43dYjoqQ34wq6WR +OB4nn/W2xlfv/ClZJTWf8YvQTrQptJY5VQq/TTEcrXy67Uc0wRHXZK2rTjKeyRj9 +65SkyyXhMopWEl2vX25ReITVfdJ0FgjqI/ugYSf25iOfJtsk+jgrtrswZ+8F2eMq +iAQ+0JSiYmlot2Pn1QCalLjtTz8zeMfXPyo5fbKNMdp52U1cPYld90kUGHZfjqju +GmY/aHa6N8lZGxj8SC/JM36GawaGKe4S/F5BetYJOpaEzkpowqlTC8Syv529rm46 +vvgf+EJL8gRvdtnIEe/qtzbtel299VhaBpuOcApfTDSxRHZmvkCpdHo9I3KgOZB9 +Cqu9Bz+FiJmTk8rGQwmI8EYj38jneEoqA+fN7tUkzxCGacg+x6ke4nOcJzgBhd94 +8DvGclrcAwBY1mlNYRceFJKFXhwLZTKBojZlS8Q9863EAH3DOBLeP85V3YvBD/MK +O+kzPoxN/jPVNho7y4gL7skcqe/IXePzPxBcZrHJjoU7mGVDcVcouRj16XSezMbB +5Pft0/gGiItRJ2+v9DlPjzDfjTuRdS78muaZ4nNqX6B+JmyPJtkb2CdiHz6B21RO +3hjGrffM1nhmYBegyjTVc88IxzYg0T8CZLq1FYxuTZmwyahA520IpwsbfwXxLVMU +5rmou5dj1pVlvoP3l+ivPqugeY3k7UjZ33m5H9p009JR40dybr1S2RbI8Gqhe953 +0bedA4DWvPakODXgYu43al92uR/tyjazeB5t7Iu8uB5Xcm3/Mqoofe9xtdQSCWa0 +jKKvXzSpL1MM2C0bRyYHIkVR65K7Zmi/BzvTaPECo1+Uv+EwqRZRyBzUZKPP8LMq +jTCOBmYaK8+0dTRk8MEzrPW2ihVVJYVMmFyTZKW0iK7kOMKZRkhDCaNSUlPEty7j +-----END RSA PRIVATE KEY----- diff --git a/tests/integration/long/ssl/generate_certificates.sh b/tests/integration/long/ssl/generate_certificates.sh deleted file mode 100755 index 75029530c8..0000000000 --- a/tests/integration/long/ssl/generate_certificates.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -# create new CA key and certificate -openssl req -new -newkey rsa:2048 -days 3650 -x509 -subj "/CN=root-ca/OU=drivers/O=oss/C=US" -keyout ca-key -out ca-cert -nodes - -# create keystore and key-pair for DSE server -keytool -genkey -keyalg RSA -keystore 127.0.0.1.keystore -validity 3650 -storepass cassandra -keypass cassandra -dname "CN=127.0.0.1,OU=drivers,O=oss,C=US" -ext "SAN=IP:127.0.0.1" -alias 127.0.0.1 -storetype pkcs12 - -# export DSE server key from keystore -openssl pkcs12 -in 127.0.0.1.keystore -nodes -nocerts -out client.key -legacy -passin pass:cassandra - -# create encrypted client key -openssl rsa -aes256 -in client.key -passout pass:cassandra -out client_encrypted.key - -# create CSR -keytool -keystore 127.0.0.1.keystore -alias 127.0.0.1 -certreq -file client.csr -storepass cassandra -ext san=ip:127.0.0.1 - -# sign CSR with CA key -openssl x509 -req -CA ca-cert -CAkey ca-key -in client.csr -out client.crt -days 3650 -copy_extensions copyall -passin pass:cassandra - -# import CA certificate to DSE node keystore -keytool -keystore 127.0.0.1.keystore -alias CARoot -import -file ca-cert -storepass cassandra -noprompt - -# import signed certificate to DSE node keystore -keytool -keystore 127.0.0.1.keystore -alias 127.0.0.1 -import -file client.crt -storepass cassandra -noprompt - -# import CA certificate to DSE node truststore -keytool -keystore cassandra.truststore -alias CARoot -import -file ca-cert -storepass cassandra -noprompt - -# cleanup -rm client.csr \ No newline at end of file diff --git a/tests/integration/long/ssl/rootCa.crt b/tests/integration/long/ssl/rootCa.crt new file mode 100644 index 0000000000..a0a0ec73cf --- /dev/null +++ b/tests/integration/long/ssl/rootCa.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCzCCAfMCFCoTNYhIQpOXMBnAq8Bw72qfKwGLMA0GCSqGSIb3DQEBCwUAMEIx +CzAJBgNVBAYTAlVTMREwDwYDVQQKDAhkYXRhc3RheDEPMA0GA1UECwwGZmllbGRz +MQ8wDQYDVQQDDAZyb290Q2EwHhcNMjEwMzE3MTcwNTE2WhcNMzEwMzE1MTcwNTE2 +WjBCMQswCQYDVQQGEwJVUzERMA8GA1UECgwIZGF0YXN0YXgxDzANBgNVBAsMBmZp +ZWxkczEPMA0GA1UEAwwGcm9vdENhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEApFoQtNu0+XQuMBPle4WAJYIMR74HL15uk9ToKBqMEXL7ah3r23xTTeGr +NyUXicM6Owiup7DK27F4vni+MYKAn7L4uZ99mW0ATYNXBDLFB+wwy1JBk4Dw5+eZ +q9lz1TGK7uBvTOXCllOA2qxRqtMTl2aPy5OuciWQe794abwFqs5+1l9GEuzJGsp1 +P9L4yljbmijC8RmvDFAeUZoKRdKXw2G5kUOHqK9Aej5gLxIK920PezpgLxm0V/PD +ZAlwlsW0vT79RgZCF/vtKcKSLtFTHgPBNPPbkZmOdE7s/6KoAkORBV/9CIsKeTC3 +Y/YeYQ2+G0gxiq1RcMavPw8f58POTQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA1 +MXBlk6u2oVBM+4SyYc2nsaHyerM+omUEysAUNFJq6S6i0pu32ULcusDfrnrIQoyR +xPJ/GSYqZkIDX0s9LvPVD6A6bnugR+Z6VfEniLkG1+TkFC+JMCblgJyaF/EbuayU +3iJX+uj7ikTySjMSDvXxOHik2i0aOh90B/351+sFnSPQrFDQ0XqxeG8s0d7EiLTV +wWJmsYglSeTo1vF3ilVRwjmHO9sX6cmQhRvRNmiQrdWaM3gLS5F6yoQ2UQQ3YdFp +quhYuNwy0Ip6ZpORHYtzkCKSanz/oUh17QWvi7aaJyqD5G5hWZgn3R4RCutoOHRS +TEJ+xzhY768rpsrrNUou +-----END CERTIFICATE----- diff --git a/tests/integration/long/test_ssl.py b/tests/integration/long/test_ssl.py index f60adc6db4..0e39cb21ad 100644 --- a/tests/integration/long/test_ssl.py +++ b/tests/integration/long/test_ssl.py @@ -42,10 +42,10 @@ SERVER_TRUSTSTORE_PATH = os.path.abspath("tests/integration/long/ssl/cassandra.truststore") # Client specific keys/certs -CLIENT_CA_CERTS = os.path.abspath("tests/integration/long/ssl/ca-cert") +CLIENT_CA_CERTS = os.path.abspath("tests/integration/long/ssl/rootCa.crt") DRIVER_KEYFILE = os.path.abspath("tests/integration/long/ssl/client.key") DRIVER_KEYFILE_ENCRYPTED = os.path.abspath("tests/integration/long/ssl/client_encrypted.key") -DRIVER_CERTFILE = os.path.abspath("tests/integration/long/ssl/client.crt") +DRIVER_CERTFILE = os.path.abspath("tests/integration/long/ssl/client.crt_signed") DRIVER_CERTFILE_BAD = os.path.abspath("tests/integration/long/ssl/client_bad.key") USES_PYOPENSSL = "twisted" in EVENT_LOOP_MANAGER or "eventlet" in EVENT_LOOP_MANAGER @@ -486,7 +486,7 @@ def test_cannot_connect_ssl_context_with_invalid_hostname(self): password="cassandra", ) ssl_context.verify_mode = ssl.CERT_REQUIRED - ssl_context.check_hostname = True + ssl_options["check_hostname"] = True with self.assertRaises(Exception): validate_ssl_options(ssl_context=ssl_context, ssl_options=ssl_options, hostname="localhost") From 36e2508892f07db8cd2f03ac2aa2859a125649a4 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Mon, 26 May 2025 15:12:23 +0200 Subject: [PATCH 5/9] PYTHON-1331 Unit test --- tests/integration/long/test_sni_connection.py | 94 ------------------- tests/unit/test_cluster.py | 53 ++++++++++- 2 files changed, 51 insertions(+), 96 deletions(-) delete mode 100644 tests/integration/long/test_sni_connection.py diff --git a/tests/integration/long/test_sni_connection.py b/tests/integration/long/test_sni_connection.py deleted file mode 100644 index fad0da6645..0000000000 --- a/tests/integration/long/test_sni_connection.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright DataStax, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import socket -import ssl -import unittest -from unittest.mock import patch - -from cassandra import ProtocolVersion -from cassandra.cluster import Cluster, ControlConnection, ProfileManager -from cassandra.connection import SniEndPoint -from cassandra.datastax.cloud import CloudConfig -from cassandra.policies import ConstantReconnectionPolicy -from tests.integration import ( - get_cluster, remove_cluster, CASSANDRA_IP -) -from tests.integration.long.test_ssl import setup_cluster_ssl, CLIENT_CA_CERTS, DRIVER_CERTFILE, DRIVER_KEYFILE - - -class SniConnectionTests(unittest.TestCase): - - @classmethod - def setUpClass(cls): - setup_cluster_ssl(client_auth=True) - - @classmethod - def tearDownClass(cls): - ccm_cluster = get_cluster() - ccm_cluster.stop() - remove_cluster() - - def _mocked_cloud_config(self, cloud_config, create_pyopenssl_context): - config = CloudConfig.from_dict({}) - config.sni_host = 'proxy.datastax.com' - config.sni_port = 9042 - config.host_ids = ['8c4b6ed7-f505-4226-b7a4-41f322520c1f', '2e25021d-8d72-41a7-a247-3da85c5d92d2'] - - ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS) - ssl_context.load_verify_locations(CLIENT_CA_CERTS) - ssl_context.verify_mode = ssl.CERT_REQUIRED - ssl_context.load_cert_chain(certfile=DRIVER_CERTFILE, keyfile=DRIVER_KEYFILE) - config.ssl_context = ssl_context - - return config - - def _mocked_proxy_dns_resolution(self): - return [ - # return wrong IP at first position, so that we make sure all SNI endpoints - # do not start with first IP only - (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('100.101.102.103', 9042)), - (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, (CASSANDRA_IP, 9042)) - ] - - def _mocked_refresh_node_list_and_token_map(self, connection, preloaded_results=None, - force_token_rebuild=False): - return - - def _mocked_refresh_schema(self, connection, preloaded_results=None, schema_agreement_wait=None, - force=False, **kwargs): - return - - def _mocked_check_supported(self): - return - - # Tests verifies that driver can connect to SNI endpoint even when one IP - # returned by the DNS resolution of SNI does not respond. Mocked SNI resolution method - # returns two IPs where only one corresponds to online C* cluster started with CCM. - def test_round_robin_dns_resolution(self): - with patch('cassandra.datastax.cloud.get_cloud_config', self._mocked_cloud_config): - with patch.object(SniEndPoint, '_resolve_proxy_addresses', self._mocked_proxy_dns_resolution): - # Mock below three functions, because host ID returned from proxy will not match ID present in C* - # Network connection should be already made, so we can consider our test successful - with patch.object(ControlConnection, '_refresh_node_list_and_token_map', - self._mocked_refresh_node_list_and_token_map): - with patch.object(ControlConnection, '_refresh_schema', - self._mocked_refresh_schema): - with patch.object(ProfileManager, 'check_supported', self._mocked_check_supported): - cloud_config = { - 'secure_connect_bundle': '/path/to/secure-connect-dbname.zip' - } - cluster = Cluster(cloud=cloud_config, protocol_version=ProtocolVersion.V4, reconnection_policy=ConstantReconnectionPolicy(10)) - session = cluster.connect() - session.shutdown() - cluster.shutdown() diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 90bcfbdca8..9863edf4d0 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -16,12 +16,14 @@ import logging import socket -from unittest.mock import patch, Mock +from unittest.mock import patch, Mock, MagicMock from cassandra import ConsistencyLevel, DriverException, Timeout, Unavailable, RequestExecutionException, ReadTimeout, WriteTimeout, CoordinationFailure, ReadFailure, WriteFailure, FunctionFailure, AlreadyExists,\ InvalidRequest, Unauthorized, AuthenticationFailed, OperationTimedOut, UnsupportedOperation, RequestValidationException, ConfigurationException, ProtocolVersion from cassandra.cluster import _Scheduler, Session, Cluster, default_lbp_factory, \ - ExecutionProfile, _ConfigMode, EXEC_PROFILE_DEFAULT + ExecutionProfile, _ConfigMode, EXEC_PROFILE_DEFAULT, ControlConnection +from cassandra.connection import SniEndPoint, Connection +from cassandra.datastax.cloud import CloudConfig from cassandra.pool import Host from cassandra.policies import HostDistance, RetryPolicy, RoundRobinPolicy, DowngradingConsistencyRetryPolicy, SimpleConvictionPolicy from cassandra.query import SimpleStatement, named_tuple_factory, tuple_factory @@ -31,6 +33,7 @@ log = logging.getLogger(__name__) + class ExceptionTypeTest(unittest.TestCase): def test_exception_types(self): @@ -85,6 +88,12 @@ def test_exception_types(self): self.assertTrue(issubclass(UnsupportedOperation, DriverException)) +class MockOrderedPolicy(RoundRobinPolicy): + all_hosts = set() + + def make_query_plan(self, working_keyspace=None, query=None): + return sorted(self.all_hosts, key=lambda x: x.endpoint.ssl_options['server_hostname']) + class ClusterTest(unittest.TestCase): def test_tuple_for_contact_points(self): @@ -119,6 +128,46 @@ def test_requests_in_flight_threshold(self): for n in (0, mn, 128): self.assertRaises(ValueError, c.set_max_requests_per_connection, d, n) + def _mocked_cloud_config(self, cloud_config, create_pyopenssl_context): + config = CloudConfig.from_dict({}) + config.sni_host = 'proxy.datastax.com' + config.sni_port = 9042 + # for 2e25021d-8d72-41a7-a247-3da85c5d92d2 we return IP 127.0.0.1 to which connection fails + config.host_ids = ['2e25021d-8d72-41a7-a247-3da85c5d92d2', '8c4b6ed7-f505-4226-b7a4-41f322520c1f'] + return config + + def _mocked_proxy_dns_resolution(self): + return [ + (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('127.0.0.1', 9042)), + (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('127.0.0.2', 9042)) + ] + + def _mocked_try_connect(self, host): + address, port = host.endpoint.resolve() + if address == '127.0.0.1': + raise socket.error + return MagicMock(spec=Connection) + + # Tests verifies that driver can connect to SNI endpoint even when one IP + # returned by the DNS resolution of SNI raises error. Mocked SNI resolution method + # returns two IPs. Trying to connect to the first one always fails + # with socket exception. + def test_sni_round_robin_dns_resolution(self): + with patch('cassandra.datastax.cloud.get_cloud_config', self._mocked_cloud_config): + with patch.object(SniEndPoint, '_resolve_proxy_addresses', self._mocked_proxy_dns_resolution): + cloud_config = { + 'secure_connect_bundle': '/path/to/secure-connect-dbname.zip' + } + cluster = Cluster(cloud=cloud_config) + lbp = MockOrderedPolicy() + cluster.load_balancing_policy = lbp + with patch.object(ControlConnection, '_try_connect', self._mocked_try_connect): + for endpoint in cluster.endpoints_resolved: + host, new = cluster.add_host(endpoint, signal=False) + lbp.all_hosts.add(host) + cluster.control_connection.connect() + cluster.shutdown() + class SchedulerTest(unittest.TestCase): # TODO: this suite could be expanded; for now just adding a test covering a ticket From bb1f1d97364c7e4fff10a87b5d7470a2b2bfbe68 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Thu, 29 May 2025 14:01:23 +0200 Subject: [PATCH 6/9] Add LBP query plan test --- tests/unit/test_cluster.py | 69 +++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 9863edf4d0..517a709c9d 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -15,6 +15,7 @@ import logging import socket +import uuid from unittest.mock import patch, Mock, MagicMock @@ -22,7 +23,7 @@ InvalidRequest, Unauthorized, AuthenticationFailed, OperationTimedOut, UnsupportedOperation, RequestValidationException, ConfigurationException, ProtocolVersion from cassandra.cluster import _Scheduler, Session, Cluster, default_lbp_factory, \ ExecutionProfile, _ConfigMode, EXEC_PROFILE_DEFAULT, ControlConnection -from cassandra.connection import SniEndPoint, Connection +from cassandra.connection import SniEndPoint, Connection, SniEndPointFactory from cassandra.datastax.cloud import CloudConfig from cassandra.pool import Host from cassandra.policies import HostDistance, RetryPolicy, RoundRobinPolicy, DowngradingConsistencyRetryPolicy, SimpleConvictionPolicy @@ -128,46 +129,66 @@ def test_requests_in_flight_threshold(self): for n in (0, mn, 128): self.assertRaises(ValueError, c.set_max_requests_per_connection, d, n) - def _mocked_cloud_config(self, cloud_config, create_pyopenssl_context): - config = CloudConfig.from_dict({}) - config.sni_host = 'proxy.datastax.com' - config.sni_port = 9042 - # for 2e25021d-8d72-41a7-a247-3da85c5d92d2 we return IP 127.0.0.1 to which connection fails - config.host_ids = ['2e25021d-8d72-41a7-a247-3da85c5d92d2', '8c4b6ed7-f505-4226-b7a4-41f322520c1f'] - return config - - def _mocked_proxy_dns_resolution(self): - return [ - (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('127.0.0.1', 9042)), - (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('127.0.0.2', 9042)) - ] - - def _mocked_try_connect(self, host): - address, port = host.endpoint.resolve() - if address == '127.0.0.1': - raise socket.error - return MagicMock(spec=Connection) - # Tests verifies that driver can connect to SNI endpoint even when one IP # returned by the DNS resolution of SNI raises error. Mocked SNI resolution method # returns two IPs. Trying to connect to the first one always fails # with socket exception. def test_sni_round_robin_dns_resolution(self): - with patch('cassandra.datastax.cloud.get_cloud_config', self._mocked_cloud_config): - with patch.object(SniEndPoint, '_resolve_proxy_addresses', self._mocked_proxy_dns_resolution): + def _mocked_cloud_config(cloud_config, create_pyopenssl_context): + config = CloudConfig.from_dict({}) + config.sni_host = 'proxy.datastax.com' + config.sni_port = 9042 + # for 2e25021d-8d72-41a7-a247-3da85c5d92d2 we return IP 127.0.0.1 to which connection fails + config.host_ids = ['2e25021d-8d72-41a7-a247-3da85c5d92d2', '8c4b6ed7-f505-4226-b7a4-41f322520c1f'] + return config + + def _mocked_proxy_dns_resolution(self): + return [ + (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('127.0.0.1', 9042)), + (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('127.0.0.2', 9042)) + ] + + def _mocked_try_connect(self, host): + address, port = host.endpoint.resolve() + if address == '127.0.0.1': + raise socket.error + return MagicMock(spec=Connection) + + with patch('cassandra.datastax.cloud.get_cloud_config', _mocked_cloud_config): + with patch.object(SniEndPoint, '_resolve_proxy_addresses', _mocked_proxy_dns_resolution): cloud_config = { 'secure_connect_bundle': '/path/to/secure-connect-dbname.zip' } cluster = Cluster(cloud=cloud_config) lbp = MockOrderedPolicy() cluster.load_balancing_policy = lbp - with patch.object(ControlConnection, '_try_connect', self._mocked_try_connect): + with patch.object(ControlConnection, '_try_connect', _mocked_try_connect): for endpoint in cluster.endpoints_resolved: host, new = cluster.add_host(endpoint, signal=False) lbp.all_hosts.add(host) + # No NoHostAvailable indicates that test passed. cluster.control_connection.connect() cluster.shutdown() + # Validate that at least the default LBP can create a query plan with end points that resolve + # to different addresses initially. This may not be exactly how things play out in practice + # (the control connection will muck with this even if nothing else does) but it should be + # a pretty good approximation. + def test_query_plan_for_sni_contains_unique_addresses(self): + node_cnt = 5 + def _mocked_proxy_dns_resolution(self): + return [(socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('127.0.0.%s' % (i,), 9042)) for i in range(node_cnt)] + + c = Cluster() + lbp = c.load_balancing_policy + lbp.local_dc = "dc1" + factory = SniEndPointFactory("proxy.foo.bar", 9042) + for host in (Host(factory.create({"host_id": uuid.uuid4().hex, "dc": "dc1"}), SimpleConvictionPolicy) for _ in range(node_cnt)): + lbp.on_up(host) + with patch.object(SniEndPoint, '_resolve_proxy_addresses', _mocked_proxy_dns_resolution): + addrs = [host.endpoint.resolve() for host in lbp.make_query_plan()] + self.assertEqual(len(addrs), len(set(addrs))) + class SchedulerTest(unittest.TestCase): # TODO: this suite could be expanded; for now just adding a test covering a ticket From 4956254a07041b2e55aea0d2a979b0ea98574dc2 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Tue, 17 Jun 2025 17:05:24 +0200 Subject: [PATCH 7/9] Unit tests consolidation --- tests/unit/test_cluster.py | 42 +------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 517a709c9d..5ed05aa878 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -129,47 +129,6 @@ def test_requests_in_flight_threshold(self): for n in (0, mn, 128): self.assertRaises(ValueError, c.set_max_requests_per_connection, d, n) - # Tests verifies that driver can connect to SNI endpoint even when one IP - # returned by the DNS resolution of SNI raises error. Mocked SNI resolution method - # returns two IPs. Trying to connect to the first one always fails - # with socket exception. - def test_sni_round_robin_dns_resolution(self): - def _mocked_cloud_config(cloud_config, create_pyopenssl_context): - config = CloudConfig.from_dict({}) - config.sni_host = 'proxy.datastax.com' - config.sni_port = 9042 - # for 2e25021d-8d72-41a7-a247-3da85c5d92d2 we return IP 127.0.0.1 to which connection fails - config.host_ids = ['2e25021d-8d72-41a7-a247-3da85c5d92d2', '8c4b6ed7-f505-4226-b7a4-41f322520c1f'] - return config - - def _mocked_proxy_dns_resolution(self): - return [ - (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('127.0.0.1', 9042)), - (socket.AF_UNIX, socket.SOCK_STREAM, 0, None, ('127.0.0.2', 9042)) - ] - - def _mocked_try_connect(self, host): - address, port = host.endpoint.resolve() - if address == '127.0.0.1': - raise socket.error - return MagicMock(spec=Connection) - - with patch('cassandra.datastax.cloud.get_cloud_config', _mocked_cloud_config): - with patch.object(SniEndPoint, '_resolve_proxy_addresses', _mocked_proxy_dns_resolution): - cloud_config = { - 'secure_connect_bundle': '/path/to/secure-connect-dbname.zip' - } - cluster = Cluster(cloud=cloud_config) - lbp = MockOrderedPolicy() - cluster.load_balancing_policy = lbp - with patch.object(ControlConnection, '_try_connect', _mocked_try_connect): - for endpoint in cluster.endpoints_resolved: - host, new = cluster.add_host(endpoint, signal=False) - lbp.all_hosts.add(host) - # No NoHostAvailable indicates that test passed. - cluster.control_connection.connect() - cluster.shutdown() - # Validate that at least the default LBP can create a query plan with end points that resolve # to different addresses initially. This may not be exactly how things play out in practice # (the control connection will muck with this even if nothing else does) but it should be @@ -187,6 +146,7 @@ def _mocked_proxy_dns_resolution(self): lbp.on_up(host) with patch.object(SniEndPoint, '_resolve_proxy_addresses', _mocked_proxy_dns_resolution): addrs = [host.endpoint.resolve() for host in lbp.make_query_plan()] + # single SNI endpoint should be resolved to multiple unique IP addresses self.assertEqual(len(addrs), len(set(addrs))) From 7ff54fad62a45f07a9bc839846e92bdbf1052e42 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Tue, 17 Jun 2025 17:06:43 +0200 Subject: [PATCH 8/9] Unit tests consolidation --- tests/unit/test_cluster.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 5ed05aa878..2feced8ed3 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -22,9 +22,8 @@ from cassandra import ConsistencyLevel, DriverException, Timeout, Unavailable, RequestExecutionException, ReadTimeout, WriteTimeout, CoordinationFailure, ReadFailure, WriteFailure, FunctionFailure, AlreadyExists,\ InvalidRequest, Unauthorized, AuthenticationFailed, OperationTimedOut, UnsupportedOperation, RequestValidationException, ConfigurationException, ProtocolVersion from cassandra.cluster import _Scheduler, Session, Cluster, default_lbp_factory, \ - ExecutionProfile, _ConfigMode, EXEC_PROFILE_DEFAULT, ControlConnection -from cassandra.connection import SniEndPoint, Connection, SniEndPointFactory -from cassandra.datastax.cloud import CloudConfig + ExecutionProfile, _ConfigMode, EXEC_PROFILE_DEFAULT +from cassandra.connection import SniEndPoint, SniEndPointFactory from cassandra.pool import Host from cassandra.policies import HostDistance, RetryPolicy, RoundRobinPolicy, DowngradingConsistencyRetryPolicy, SimpleConvictionPolicy from cassandra.query import SimpleStatement, named_tuple_factory, tuple_factory From aada3f32c17de28a91ef4eb7ea9ef0db7f2fd452 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Tue, 17 Jun 2025 17:07:07 +0200 Subject: [PATCH 9/9] Unit tests consolidation --- tests/unit/test_cluster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_cluster.py b/tests/unit/test_cluster.py index 2feced8ed3..bc6ae90142 100644 --- a/tests/unit/test_cluster.py +++ b/tests/unit/test_cluster.py @@ -17,7 +17,7 @@ import socket import uuid -from unittest.mock import patch, Mock, MagicMock +from unittest.mock import patch, Mock from cassandra import ConsistencyLevel, DriverException, Timeout, Unavailable, RequestExecutionException, ReadTimeout, WriteTimeout, CoordinationFailure, ReadFailure, WriteFailure, FunctionFailure, AlreadyExists,\ InvalidRequest, Unauthorized, AuthenticationFailed, OperationTimedOut, UnsupportedOperation, RequestValidationException, ConfigurationException, ProtocolVersion