Skip to content

Commit

Permalink
Custom revert of 3e72b44 to fix the SSLEOFError
Browse files Browse the repository at this point in the history
Could not "git revert" because the files have changed too much
  • Loading branch information
julien-lang committed Jun 5, 2024
1 parent f9f50d3 commit b477fbb
Show file tree
Hide file tree
Showing 3 changed files with 11 additions and 135 deletions.
7 changes: 7 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,13 @@ Stores the number of milliseconds to wait between request retries. By default,

In the case that both this environment variable and the config's ``rpc_attempt_interval`` property are set, the value in ``rpc_attempt_interal`` will be used.


SHOTGUN_DISABLE_SSL_VALIDATION
==============================

TODO


************
Localization
************
Expand Down
68 changes: 4 additions & 64 deletions shotgun_api3/shotgun.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ def _is_mimetypes_broken():
have a self-signed internal certificate that isn't included in our certificate bundle, you may
not require the added security provided by enforcing this.
"""
if os.environ.get("SHOTGUN_DISABLE_SSL_VALIDATION", False):
NO_SSL_VALIDATION = True

# ----------------------------------------------------------------------------
# Version
Expand Down Expand Up @@ -367,12 +369,11 @@ def __init__(self):

self.py_version = ".".join(str(x) for x in sys.version_info[:2])

# extract the OpenSSL version if we can. The version is only available in Python 2.7 and
# only if we successfully imported ssl
# extract the OpenSSL version if we can.
self.ssl_version = "unknown"
try:
self.ssl_version = ssl.OPENSSL_VERSION
except (AttributeError, NameError):
except AttributeError:
pass

def __str__(self):
Expand Down Expand Up @@ -3374,18 +3375,6 @@ def _get_certs_file(cls, ca_certs):
cert_file = os.path.join(cur_dir, "lib", "certifi", "cacert.pem")
return cert_file

def _turn_off_ssl_validation(self):
"""
Turn off SSL certificate validation.
"""
global NO_SSL_VALIDATION
self.config.no_ssl_validation = True
NO_SSL_VALIDATION = True
# reset ssl-validation in user-agents
self._user_agents = ["ssl %s (no-validate)" % self.client_caps.ssl_version
if ua.startswith("ssl ") else ua
for ua in self._user_agents]

# Deprecated methods from old wrapper
def schema(self, entity_type):
"""
Expand Down Expand Up @@ -3587,55 +3576,6 @@ def _make_call(self, verb, path, body, headers):
attempt += 1
try:
return self._http_request(verb, path, body, req_headers)
except ssl.SSLEOFError as e:
# SG-34910 - EOF occurred in violation of protocol (_ssl.c:2426)
# This issue seems to be related to proxy and keep alive.
# It looks like, sometimes, the proxy drops the connection on
# the TCP/TLS level despites the keep-alive. So we need to close
# the connection and make a new attempt.
LOG.debug("SSLEOFError: {}".format(e))
self._close_connection()
if attempt == max_rpc_attempts:
LOG.debug("Request failed. Giving up after %d attempts." % attempt)
raise
# This is the exact same block as the "except Exception" bellow.
# We need to do it here because the next except will match it
# otherwise and will not re-attempt.
# When we drop support of Python 2 and we will probably drop the
# next except, we might want to remove this except too.
except ssl_error_classes as e:
# Test whether the exception is due to the fact that this is an older version of
# Python that cannot validate certificates encrypted with SHA-2. If it is, then
# fall back on disabling the certificate validation and try again - unless the
# SHOTGUN_FORCE_CERTIFICATE_VALIDATION environment variable has been set by the
# user. In that case we simply raise the exception. Any other exceptions simply
# get raised as well.
#
# For more info see:
# https://www.shotgridsoftware.com/blog/important-ssl-certificate-renewal-and-sha-2/
#
# SHA-2 errors look like this:
# [Errno 1] _ssl.c:480: error:0D0C50A1:asn1 encoding routines:ASN1_item_verify:
# unknown message digest algorithm
#
# Any other exceptions simply get raised.
if "unknown message digest algorithm" not in str(e) or \
"SHOTGUN_FORCE_CERTIFICATE_VALIDATION" in os.environ:
raise

if self.config.no_ssl_validation is False:
LOG.warning("SSL Error: this Python installation is incompatible with "
"certificates signed with SHA-2. Disabling certificate validation. "
"For more information, see https://www.shotgridsoftware.com/blog/"
"important-ssl-certificate-renewal-and-sha-2/")
self._turn_off_ssl_validation()
# reload user agent to reflect that we have turned off ssl validation
req_headers["user-agent"] = "; ".join(self._user_agents)

self._close_connection()
if attempt == max_rpc_attempts:
LOG.debug("Request failed. Giving up after %d attempts." % attempt)
raise
except Exception:
self._close_connection()
if attempt == max_rpc_attempts:
Expand Down
71 changes: 0 additions & 71 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1935,77 +1935,6 @@ def my_side_effect2(*args, **kwargs):
finally:
self.sg.config.rpc_attempt_interval = bak_rpc_attempt_interval

@patch('shotgun_api3.shotgun.Http.request')
def test_sha2_error(self, mock_request):
# Simulate the exception raised with SHA-2 errors
mock_request.side_effect = ShotgunSSLError(
"[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 "
"encoding routines:ASN1_item_verify: unknown message digest "
"algorithm"
)

# save the original state
original_env_val = os.environ.pop("SHOTGUN_FORCE_CERTIFICATE_VALIDATION", None)

# ensure we're starting with the right values
self.sg.reset_user_agent()

# ensure the initial settings are correct. These will be different depending on whether
# the ssl module imported successfully or not.
if "ssl" in sys.modules:
self.assertFalse(self.sg.config.no_ssl_validation)
self.assertFalse(shotgun_api3.shotgun.NO_SSL_VALIDATION)
self.assertTrue("(validate)" in " ".join(self.sg._user_agents))
self.assertFalse("(no-validate)" in " ".join(self.sg._user_agents))
else:
self.assertTrue(self.sg.config.no_ssl_validation)
self.assertTrue(shotgun_api3.shotgun.NO_SSL_VALIDATION)
self.assertFalse("(validate)" in " ".join(self.sg._user_agents))
self.assertTrue("(no-validate)" in " ".join(self.sg._user_agents))

try:
self.sg.info()
except ShotgunSSLError:
# ensure the api has reset the values in the correct fallback behavior
self.assertTrue(self.sg.config.no_ssl_validation)
self.assertTrue(shotgun_api3.shotgun.NO_SSL_VALIDATION)
self.assertFalse("(validate)" in " ".join(self.sg._user_agents))
self.assertTrue("(no-validate)" in " ".join(self.sg._user_agents))

if original_env_val is not None:
os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val

@patch('shotgun_api3.shotgun.Http.request')
def test_sha2_error_with_strict(self, mock_request):
# Simulate the exception raised with SHA-2 errors
mock_request.side_effect = ShotgunSSLError(
"[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 "
"encoding routines:ASN1_item_verify: unknown message digest "
"algorithm"
)

# save the original state
original_env_val = os.environ.pop("SHOTGUN_FORCE_CERTIFICATE_VALIDATION", None)
os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = "1"

# ensure we're starting with the right values
self.sg.config.no_ssl_validation = False
shotgun_api3.shotgun.NO_SSL_VALIDATION = False
self.sg.reset_user_agent()

try:
self.sg.info()
except ShotgunSSLError:
# ensure the api has NOT reset the values in the fallback behavior because we have
# set the env variable to force validation
self.assertFalse(self.sg.config.no_ssl_validation)
self.assertFalse(shotgun_api3.shotgun.NO_SSL_VALIDATION)
self.assertFalse("(no-validate)" in " ".join(self.sg._user_agents))
self.assertTrue("(validate)" in " ".join(self.sg._user_agents))

if original_env_val is not None:
os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val

@patch.object(urllib.request.OpenerDirector, 'open')
def test_sanitized_auth_params(self, mock_open):
# Simulate the server blowing up and giving us a 500 error
Expand Down

0 comments on commit b477fbb

Please sign in to comment.