Repository: docker/docker-py

Issue:
tlsv1 alert protocol version on 1.7.1 and 1.7.2 but not on 1.7.0
Similar to #949 I'm discovering issues with latest versions of `docker-py` running against docker 1.10.2 instance. I'm using `docker.utils.kwargs_from_env(assert_hostname=False)`. Things work fine with version 1.7.0.

Docker client is initialized via

```
client = docker.Client(
    version='auto',
    **docker.utils.kwargs_from_env(assert_hostname=False))
```

with docker environment variables being set to the following (via docker-machine)

```
DOCKER_HOST=tcp://192.168.156.137:2376
DOCKER_MACHINE_NAME=dev2
DOCKER_TLS_VERIFY=1
DOCKER_CERT_PATH=/Users/benjixx/.docker/machine/machines/dev2
```

docker-py 1.7.1 and 1.7.2 now raise the following exception:

```
DockerException: Error while fetching server API version: [Errno 1] _ssl.c:507: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version
```

Any idea what's happening here?



Discussion:
What happens if you `unset DOCKER_TLS_VERIFY`?

@shin- Just tested again with docker-py 1.7.2 and `unset DOCKER_TLS_VERIFY`, so that docker environment variables were:

```
DOCKER_HOST=tcp://192.168.156.137:2376
DOCKER_MACHINE_NAME=dev2
DOCKER_CERT_PATH=/Users/benjixx/.docker/machine/machines/dev2
```

But, I'm still seeing the same exception:

```
DockerException: Error while fetching server API version: [Errno 1] _ssl.c:507: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version
```

I can confirm I'm seeing the same pattern on one of the two Macs I'm running this on currently. Not sure what difference might matter, but the one exhibiting the broken behavior is on 10.10 and the newer docker-py versions are working on my box with 10.11.

I'm running latest Mac OS X 10.11.3 and have the problem, so I doubt it's directly related to the OS version.

Could you run the following code snippet to help pinpoint the issue, and post the output here?

``` python
import docker
import ssl

protocols = [
    'PROTOCOL_SSLv23', 'PROTOCOL_TLSv1', 'PROTOCOL_TLSv1_1', 'PROTOCOL_TLSv1_2'
]

kwargs = docker.utils.kwargs_from_env(assert_hostname=False)
client = docker.Client(**kwargs)
tls_config = kwargs['tls']

for proto in protocols:
    if not hasattr(ssl, proto):
        print('Protocol {0} not found in SSL protocol list'.format(proto))
    tls_config.ssl_version = getattr(ssl, proto)
    tls_config.configure_client(client)
    try:
        client.version()
        print('Successful connection with protocol {0}'.format(proto))
    except Exception as e:
        print('Connection error with protocol {0}: "{1}"'.format(proto, e))
```

@shin- Here's the requested output:

```
/Users/benjixx/src/goodplay/.tox/py27-ansible20/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:315: SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Name Indication) extension to TLS is not available on this platform. This may cause the server to present an incorrect TLS certificate, which can cause validation failures. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#snimissingwarning.
  SNIMissingWarning
/Users/benjixx/src/goodplay/.tox/py27-ansible20/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:120: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
  InsecurePlatformWarning
Connection error with protocol PROTOCOL_SSLv23: "[Errno 1] _ssl.c:507: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version"
/Users/benjixx/src/goodplay/.tox/py27-ansible20/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:120: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
  InsecurePlatformWarning
Successful connection with protocol PROTOCOL_TLSv1
Protocol PROTOCOL_TLSv1_1 not found in SSL protocol list
Traceback (most recent call last):
  File "dockersnippet.py", line 15, in <module>
    tls_config.ssl_version = getattr(ssl, proto)
AttributeError: 'module' object has no attribute 'PROTOCOL_TLSv1_1'
```

Seeing the same thing on my box exhibiting the problem

```
/private/var/root/venv/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:315: SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Name Indication) extension to TLS is not available on this platform. This may cause the server to present an incorrect TLS certificate, which can cause validation failures. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#snimissingwarning.
  SNIMissingWarning
/private/var/root/venv/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:120: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
  InsecurePlatformWarning
Connection error with protocol PROTOCOL_SSLv23: "[Errno 1] _ssl.c:507: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version"
/private/var/root/venv/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:120: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
  InsecurePlatformWarning
Connection error with protocol PROTOCOL_TLSv1: "[Errno 1] _ssl.c:507: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed"
Protocol PROTOCOL_TLSv1_1 not found in SSL protocol list
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
AttributeError: 'module' object has no attribute 'PROTOCOL_TLSv1_1'
```

Might be a slight error in the script, on my box which works fine with the newer docker-py versions I get the following:

```
Connection error with protocol PROTOCOL_SSLv23: "[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)"
Connection error with protocol PROTOCOL_TLSv1: "[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)"
Connection error with protocol PROTOCOL_TLSv1_1: "[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)"
Connection error with protocol PROTOCOL_TLSv1_2: "[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)"
```

FWIW this is the code I run normally to get a client, this client works fine on my good box with 1.7.1+

``` python
def get_docker_client():
    """Ripped off and slightly modified based on docker-py's
    kwargs_from_env utility function."""
    env = get_docker_env() # this populates based off docker-machine env
    host, cert_path, tls_verify = env['DOCKER_HOST'], env['DOCKER_CERT_PATH'], env['DOCKER_TLS_VERIFY']

    params = {'base_url': host.replace('tcp://', 'https://'),
              'timeout': None,
              'version': 'auto'}
    if tls_verify and cert_path:
        params['tls'] = docker.tls.TLSConfig(
            client_cert=(os.path.join(cert_path, 'cert.pem'),
                         os.path.join(cert_path, 'key.pem')),
            ca_cert=os.path.join(cert_path, 'ca.pem'),
            verify=True,
            ssl_version=None,
            assert_hostname=False)
    return docker.Client(**params)
```

Trying to dump any info which might be relevant: my working box is using Python 2.7.9, non-working box is on 2.7.6. I think there were big changes to the SSL module in 2.7.9?

My non-working box is running Python 2.7.6 as well. This should be the default Python version running on latest official Mac OS X, so would be great to have it working there as well.

I realize nobody's said anything to the contrary yet but I agree, requiring 2.7.9+ would be a huge inconvenience. Hopefully we can restore functionality for 2.7.0+.

Hmm, that's interesting. Python doc says [`PROTOCOL_SSLv23`](https://docs.python.org/dev/library/ssl.html#ssl.PROTOCOL_SSLv23) selects the highest protocol supported by both server and client, but it seems to be causing issues for you.

```
Connection error with protocol PROTOCOL_SSLv23: "[Errno 1] _ssl.c:507: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version"
```

`PROTOCOL_TLSv1` seems to work fine however.

BTW, looking at the commit log of the Python `ssl` module for Python 2.7.x it seems the module attribute `PROTOCOL_TLSv1_1` and `PROTOCOL_TLSv1_2` have been [introduced](https://github.com/python/cpython/commit/71a4ee3ea2c6847b9fc4b33cbc8d565a7bf2424a) at least after version 2.7.6 (according to the commit date).

A workaround for now would be to call `kwargs_from_env` with `ssl_version=ssl.PROTOCOL_TLSv1` set.


Resolving patch:
diff --git a/docker/tls.py b/docker/tls.py
--- a/docker/tls.py
+++ b/docker/tls.py
@@ -1,4 +1,5 @@
 import os
+import ssl
 
 from . import errors
 from .ssladapter import ssladapter
@@ -19,10 +20,14 @@ def __init__(self, client_cert=None, ca_cert=None, verify=None,
         # here, but also disable any public/default CA pool verification by
         # leaving tls_verify=False
 
-        self.ssl_version = ssl_version
         self.assert_hostname = assert_hostname
         self.assert_fingerprint = assert_fingerprint
 
+        # TLS v1.0 seems to be the safest default; SSLv23 fails in mysterious
+        # ways: https://github.com/docker/docker-py/issues/963
+
+        self.ssl_version = ssl_version or ssl.PROTOCOL_TLSv1
+
         # "tls" and "tls_verify" must have both or neither cert/key files
         # In either case, Alert the user when both are expected, but any are
         # missing.


Test patch: