diff --git a/swiftclient/client.py b/swiftclient/client.py index b9f12aac..06350901 100644 --- a/swiftclient/client.py +++ b/swiftclient/client.py @@ -1798,8 +1798,13 @@ class Connection: service_token=self.service_token, **kwargs) self._add_response_dict(caller_response_dict, kwargs) return rv - except SSLError: - raise + except SSLError as e: + self._add_response_dict(caller_response_dict, kwargs) + if ('certificate verify' in str(e)) or \ + ('hostname' in str(e)) or \ + self.attempts > self.retries: + raise + self.http_conn = None except (socket.error, RequestException): self._add_response_dict(caller_response_dict, kwargs) if self.attempts > self.retries: diff --git a/test/unit/test_swiftclient.py b/test/unit/test_swiftclient.py index 55b4679a..42d470cd 100644 --- a/test/unit/test_swiftclient.py +++ b/test/unit/test_swiftclient.py @@ -25,7 +25,7 @@ import warnings import tempfile from hashlib import md5 from urllib.parse import urlparse -from requests.exceptions import RequestException +from requests.exceptions import RequestException, SSLError from .utils import (MockHttpTest, fake_get_auth_keystone, StubResponse, FakeKeystone) @@ -2176,6 +2176,62 @@ class TestConnection(MockHttpTest): self.assertEqual(mock_auth.call_count, 1) self.assertEqual(conn.attempts, conn.retries + 1) + def test_no_retry_with_cert_sslerror(self): + def quick_sleep(*args): + pass + c.sleep = quick_sleep + for err in ( + # Taken from real testing (requests==2.25.1, urllib3==1.26.5, + # pyOpenSSL==21.0.0) but note that these are actually way more + # messy/wrapped up in other exceptions + SSLError( + '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ' + 'certificate has expired (_ssl.c:997)'), + SSLError( + "hostname 'wrong.host.badssl.com' doesn't match either of " + "'*.badssl.com', 'badssl.com'"), + SSLError( + '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ' + 'self-signed certificate (_ssl.c:997)'), + SSLError( + '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ' + 'self-signed certificate in certificate chain (_ssl.c:997)'), + SSLError( + '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ' + 'unable to get local issuer certificate (_ssl.c:997)'), + SSLError( + '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: ' + 'CA signature digest algorithm too weak (_ssl.c:997)'), + ): + conn = c.Connection('http://www.test.com', 'asdf', 'asdf') + with self.subTest(err=err), mock.patch( + 'swiftclient.client.http_connection') as \ + fake_http_connection, \ + mock.patch('swiftclient.client.get_auth_1_0') as mock_auth: + mock_auth.return_value = ('http://mock.com', 'mock_token') + fake_http_connection.side_effect = err + self.assertRaises(socket.error, conn.head_account) + self.assertEqual(mock_auth.call_count, 1) + self.assertEqual(conn.attempts, 1) + + def test_retry_with_non_cert_sslerror(self): + def quick_sleep(*args): + pass + c.sleep = quick_sleep + conn = c.Connection('http://www.test.com', 'asdf', 'asdf') + with mock.patch('swiftclient.client.http_connection') as \ + fake_http_connection, \ + mock.patch('swiftclient.client.get_auth_1_0') as mock_auth: + mock_auth.return_value = ('http://mock.com', 'mock_token') + fake_http_connection.side_effect = SSLError( + "HTTPSConnectionPool(host='example.com', port=443): " + "Max retries exceeded with url: /v1/AUTH_test (Caused by " + "SSLError(SSLZeroReturnError(6, 'TLS/SSL connection has " + "been closed (EOF) (_ssl.c:997)')))") + self.assertRaises(socket.error, conn.head_account) + self.assertEqual(mock_auth.call_count, 1) + self.assertEqual(conn.attempts, conn.retries + 1) + def test_retry_with_force_auth_retry_exceptions(self): def quick_sleep(*args): pass