Create SSL context using PROTOCOL_TLS, fallback to highest supported version

Zuul test: tests.unit.test_scheduler.TestSchedulerSSL.test_jobs_executed
fails on ubuntu focal with the following exception:

Traceback (most recent call last):
  File "/home/gchauvel/zuul/zuul/.tox/py38/lib/python3.8/site-packages/gear/__init__.py", line 2835, in _doConnectLoop
    self.connectLoop()
  File "/home/gchauvel/zuul/zuul/.tox/py38/lib/python3.8/site-packages/gear/__init__.py", line 2865, in connectLoop
    c = context.wrap_socket(c, server_side=True)
  File "/usr/lib/python3.8/ssl.py", line 500, in wrap_socket
    return self.sslsocket_class._create(
  File "/usr/lib/python3.8/ssl.py", line 1040, in _create
    self.do_handshake()
  File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL] internal error (_ssl.c:1108)

This is due to libssl1.1 being compiled with
"-DOPENSSL_TLS_SECURITY_LEVEL=2" and gear forcing TLSv1.0

Extracted from ubuntu source package:

  The default security level for TLS connections was increased from
  level 1 to level 2. This moves from the 80 bit security level to the
  112 bit security level and will require 2048 bit or larger RSA and
  DHE keys, 224 bit or larger ECC keys, SHA-2, TLSv1.2 or DTLSv1.2.

Allowing to negotiate TLS to the highest available version between
server and client solves the issue, provided that TLSv1.2 is useable.
The option is supported by in the latest version of all pythons >=3.5
[1][2][3]. Unfortunately Xenial doesn't have latest 3.5 and lacks the
ssl.PROTOCOL_TLS definition. We provide a fallback to select the highest
version of TLS supported in that case.

There is some risk using the fallback beacuse both the client and server
need to agree on the version supported in this case. Xenial python 3.5
does support TLSv1_2 which means that for all practical purposes TLS
v1.2 should be available on all platforms that gear runs avoiding this
problem.

Disable TLSv1.3:
According to https://bugs.python.org/issue43622#msg389497, an event on
ssl socket can happen without data being available at application level.
As gear is using a polling loop with multiple file descriptors and ssl
socket used as a blocking one, a blocked state could happen.
This is highlighted by Zuul SSL test: TestSchedulerSSL, where such
blocked state appears consistently.
note: gear tests and zuul tests are ok when using TLSv1.2 but the
previous behavior could also happen

[1] https://docs.python.org/2.7/library/ssl.html?highlight=protocol_tls#ssl.PROTOCOL_TLS
[2] https://docs.python.org/3.5/library/ssl.html?highlight=protocol_tls#ssl.PROTOCOL_TLS
[3] https://docs.python.org/3/library/ssl.html?highlight=protocol_tls#ssl.PROTOCOL_TLS

Change-Id: I5efb6c0576987815c5b93f8bc4020cdee2898d04
This commit is contained in:
Guillaume Chauvel 2020-07-15 19:34:49 +02:00
parent 29e9d1fa99
commit 66ba8442dc
1 changed files with 40 additions and 2 deletions

View File

@ -91,6 +91,44 @@ def convert_to_bytes(data):
return data
def best_tls_version():
if hasattr(ssl, 'PROTOCOL_TLS'):
return ssl.PROTOCOL_TLS
# Note there is some risk in selecting tls 1.2 if available
# as both the client and server may not support it and need 1.1
# or 1.0. However, a xenial installation with python 3.5 does
# support 1.2 which is probably as old a setup as we need to worry
# about.
elif hasattr(ssl, 'PROTOCOL_TLSv1_2'):
return ssl.PROTOCOL_TLSv1_2
elif hasattr(ssl, 'PROTOCOL_TLSv1_1'):
return ssl.PROTOCOL_TLSv1_1
elif hasattr(ssl, 'PROTOCOL_TLSv1'):
return ssl.PROTOCOL_TLSv1
else:
raise ConnectionError('No supported TLS version available.')
def create_ssl_context():
tls_version = best_tls_version()
context = ssl.SSLContext(tls_version)
# Disable TLSv1.3
# According to https://bugs.python.org/issue43622#msg389497, an event on
# ssl socket can happen without data being available at application level.
# As gear is using a polling loop with multiple file descriptors and ssl
# socket used as a blocking one, a blocked state could happen.
# This is highlighted by Zuul SSL test: TestSchedulerSSL, where such
# blocked state appears consistently.
# note: gear tests and zuul tests are ok for TLSv1.2 but this behavior
# could also happen
if (hasattr(ssl, 'PROTOCOL_TLS') and
tls_version == ssl.PROTOCOL_TLS):
context.options |= ssl.OP_NO_TLSv1_3
return context
class Task(object):
def __init__(self):
self._wait_event = threading.Event()
@ -209,7 +247,7 @@ class Connection(object):
if self.use_ssl:
self.log.debug("Using SSL")
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context = create_ssl_context()
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = False
context.load_cert_chain(self.ssl_cert, self.ssl_key)
@ -2862,7 +2900,7 @@ class Server(BaseClientServer):
self.log.debug("Accepting new connection")
c, addr = self.socket.accept()
if self.use_ssl:
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context = create_ssl_context()
context.verify_mode = ssl.CERT_REQUIRED
context.load_cert_chain(self.ssl_cert, self.ssl_key)
context.load_verify_locations(self.ssl_ca)