keenlabs / KeenClient-Python

Official Python client for the Keen IO API. Build analytics features directly into your Python apps.
https://keen.io/docs
MIT License
133 stars 58 forks source link

"ssl.SSLError: [SSL] internal error (_ssl.c:1123)" on Ubuntu 20.04 due to use of TLSv1.0 #161

Closed edmorley closed 3 years ago

edmorley commented 3 years ago

Issue Summary

On Ubuntu 20.04, API requests made using the Keen Python client fail with:

ssl.SSLError: [SSL] internal error (_ssl.c:1123)

This is due to:

The internal error error message is presumably a bug in the Python ssl stdlib (I'll report this separately). Other users of OpenSSL (such as curl) will instead display the more helpful error message no protocols available if TLS v1.0 is used when the SECLEVEL is 2 or higher.

Steps to Reproduce

$ docker run --rm -it ubuntu:20.04 bash
root@8a2171855a84:/# apt-get update -qq && apt install python3-pip -yqq
root@8a2171855a84:/# pip3 install keen==0.6.1 -q
root@8a2171855a84:/# export KEEN_PROJECT_ID=test KEEN_WRITE_KEY=test
root@8a2171855a84:/# python3 -c 'import keen; keen.add_event("test", {})'

Expected: Some error message about invalid project or key, but no SSL error.

Actual:

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/urllib3/connectionpool.py", line 699, in urlopen
    httplib_response = self._make_request(
  File "/usr/local/lib/python3.8/dist-packages/urllib3/connectionpool.py", line 382, in _make_request
    self._validate_conn(conn)
  File "/usr/local/lib/python3.8/dist-packages/urllib3/connectionpool.py", line 1010, in _validate_conn
    conn.connect()
  File "/usr/local/lib/python3.8/dist-packages/urllib3/connection.py", line 411, in connect
    self.sock = ssl_wrap_socket(
  File "/usr/local/lib/python3.8/dist-packages/urllib3/util/ssl_.py", line 428, in ssl_wrap_socket
    ssl_sock = _ssl_wrap_socket_impl(
  File "/usr/local/lib/python3.8/dist-packages/urllib3/util/ssl_.py", line 472, in _ssl_wrap_socket_impl
    return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
  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:1123)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/requests/adapters.py", line 439, in send
    resp = conn.urlopen(
  File "/usr/local/lib/python3.8/dist-packages/urllib3/connectionpool.py", line 755, in urlopen
    retries = retries.increment(
  File "/usr/local/lib/python3.8/dist-packages/urllib3/util/retry.py", line 573, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='api.keen.io', port=443): Max retries exceeded with url: /3.0/projects/test/events/test (Caused by SSLError(SSLError(1, '[SSL] internal error (_ssl.c:1123)')))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/usr/local/lib/python3.8/dist-packages/keen/__init__.py", line 49, in add_event
    _client.add_event(event_collection, body, timestamp=timestamp)
  File "/usr/local/lib/python3.8/dist-packages/keen/client.py", line 134, in add_event
    self.persistence_strategy.persist(event)
  File "/usr/local/lib/python3.8/dist-packages/keen/persistence_strategies.py", line 37, in persist
    self.api.post_event(event)
  File "/usr/local/lib/python3.8/dist-packages/keen/utilities.py", line 91, in method_wrapper
    return func(self, *args, **kwargs)
  File "/usr/local/lib/python3.8/dist-packages/keen/api.py", line 109, in post_event
    response = self.fulfill(HTTPMethods.POST, url, data=payload, headers=headers, timeout=self.post_timeout)
  File "/usr/local/lib/python3.8/dist-packages/keen/api.py", line 94, in fulfill
    return getattr(self.session, method)(*args, **kwargs)
  File "/usr/local/lib/python3.8/dist-packages/requests/sessions.py", line 590, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "/usr/local/lib/python3.8/dist-packages/requests/sessions.py", line 542, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/local/lib/python3.8/dist-packages/requests/sessions.py", line 655, in send
    r = adapter.send(request, **kwargs)
  File "/usr/local/lib/python3.8/dist-packages/requests/adapters.py", line 514, in send
    raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='api.keen.io', port=443): Max retries exceeded with url: /3.0/projects/test/events/test (Caused by SSLError(SSLError(1, '[SSL] internal error (_ssl.c:1123)')))

Emulating the above using curl's --tls-max 1.0, in the same Docker container gives:

root@8a2171855a84:/# apt-get install -yqq curl
root@8a2171855a84:/# curl --tls-max 1.0 https://api.keen.io
curl: (35) error:141E70BF:SSL routines:tls_construct_client_hello:no protocols available

This is presumably the error message the Python ssl stdlib should be returning rather than the internal error (I'll report this upstream separately).

Removing the forced TLS v1.0 (--tls-max 1.0) stops the SSL error:

root@8a2171855a84:/# curl https://api.keen.io
{"message": "Specified API Key is invalid. API Key: 'None'.", "error_code": "InvalidApiKeyError"}

As does lowering the default OpenSSL SECLEVEL back to 1:

root@8a2171855a84:/# curl --tls-max 1.0 --ciphers DEFAULT@SECLEVEL=1 https://api.keen.io
{"message": "Specified API Key is invalid. API Key: 'None'.", "error_code": "InvalidApiKeyError"}

The correct fix here is to update the Keen Python client to not force any particular TLS protocol version, and instead let OpenSSL use the default negotiated version. Lowering the SECLEVEL back to 1 would re-enable insecure protocols and ciphers, so is not recommended.

Technical details:

wiktorolko commented 3 years ago

@edmorley thanks for a very detailed description of a problem!

edmorley commented 3 years ago

@wiktorolko No problem - thank you for the fast fix/release! :-)

Confirmed working with the STR above (but using keen==0.7.0 instead of keen==0.6.1), where the client now returns a ResourceNotFoundError as expected (since a made up project ID was used), rather than the previous SSL internal error.

clown-wang commented 3 years ago

@edmorley Thanks!I took a very long time to find this answer!