From 69cd38ab8684e2a6760f8861d141822d41170c7b Mon Sep 17 00:00:00 2001 From: Deni Bertovic Date: Sat, 22 Mar 2014 01:04:12 +0100 Subject: [PATCH 1/3] initial take on adding support for tls auth with client certificates --- docker/client.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/docker/client.py b/docker/client.py index 38355b09c4..9ae21a235e 100644 --- a/docker/client.py +++ b/docker/client.py @@ -69,9 +69,18 @@ def is_server_error(self): class Client(requests.Session): - def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION, - timeout=DEFAULT_TIMEOUT_SECONDS): + def __init__(self, + base_url=None, + version=DEFAULT_DOCKER_API_VERSION, + timeout=DEFAULT_TIMEOUT_SECONDS, + tls=False, + tls_cert=None, + tls_key=None): super(Client, self).__init__() + if tls and not (tls_cert and tls_key): + raise RuntimeError('tls_key and tls_cert are required.') + if tls and not base_url.startswith('https'): + raise RuntimeError('TLS: base_url has to start with https://') if base_url is None: base_url = "http+unix://var/run/docker.sock" if 'unix:///' in base_url: @@ -87,7 +96,12 @@ def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION, self._timeout = timeout self._auth_configs = auth.load_config() - self.mount('http+unix://', unixconn.UnixAdapter(base_url, timeout)) + if tls: + self.cert = (tls_cert, tls_key) + self.verify = False # We assume the server.crt will we self signed + self.mount('https://', requests.adapters.HTTPAdapter()) + else: + self.mount('http+unix://', unixconn.UnixAdapter(base_url, timeout)) def _set_request_timeout(self, kwargs): """Prepare the kwargs for an HTTP request by inserting the timeout From 7ce73de4a710b6ccd334673bd3c4d1ee667addea Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Tue, 13 May 2014 20:35:19 -0500 Subject: [PATCH 2/3] Expanding on @denibertovic initial additions, we now have full support for SSL in docker-py. Including the ability to specify the expected SSL Version for issues with OpenSSL sslv3/tls1 recognition issues. Added an exception class for repetitive reminders to look at the CLI doc on docker.io. --- docker/client.py | 57 +++++++++++++++++++++++++++------ docker/exceptions/__init__.py | 1 + docker/exceptions/exceptions.py | 6 ++++ docker/ssladapter/__init__.py | 1 + docker/ssladapter/ssladapter.py | 23 +++++++++++++ setup.py | 2 +- 6 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 docker/exceptions/__init__.py create mode 100644 docker/exceptions/exceptions.py create mode 100644 docker/ssladapter/__init__.py create mode 100644 docker/ssladapter/ssladapter.py diff --git a/docker/client.py b/docker/client.py index 9ae21a235e..d7d130761f 100644 --- a/docker/client.py +++ b/docker/client.py @@ -16,6 +16,7 @@ import re import shlex import struct +import os import requests import requests.exceptions @@ -23,7 +24,9 @@ from .auth import auth from .unixconn import unixconn +from .ssladapter import ssladapter from .utils import utils +from .exceptions import exceptions if not six.PY3: import websocket @@ -75,12 +78,14 @@ def __init__(self, timeout=DEFAULT_TIMEOUT_SECONDS, tls=False, tls_cert=None, - tls_key=None): + tls_key=None, + tls_verify=False, + tls_ca_cert=None, + ssl_version=None): super(Client, self).__init__() - if tls and not (tls_cert and tls_key): - raise RuntimeError('tls_key and tls_cert are required.') - if tls and not base_url.startswith('https'): - raise RuntimeError('TLS: base_url has to start with https://') + + if (tls or tls_verify) and not base_url.startswith('https://'): + raise exceptions.TLSParameterError('If using TLS, the base_url argument must begin with "https://".') if base_url is None: base_url = "http+unix://var/run/docker.sock" if 'unix:///' in base_url: @@ -96,10 +101,44 @@ def __init__(self, self._timeout = timeout self._auth_configs = auth.load_config() - if tls: - self.cert = (tls_cert, tls_key) - self.verify = False # We assume the server.crt will we self signed - self.mount('https://', requests.adapters.HTTPAdapter()) + """ Argument compatibility/mapping with http://docs.docker.io/examples/https/ + + This diverges from the Docker CLI in that users can specify 'tls' here, but also + disable any public/default CA pool verification by leaving tls_verify=False + """ + """ urllib3 sets a default ssl_version if ssl_version is None + https://github.com/shazow/urllib3/blob/62ecd1523ec383802cb13b09bd7084d2da997420/urllib3/util/ssl_.py#L83 + """ + self.ssl_version = ssl_version + + """ "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.""" + if (tls or tls_verify) and (tls_cert or tls_key): + if not (tls_cert and tls_key) or (not os.path.isfile(tls_cert) or not os.path.isfile(tls_key)): + raise exceptions.TLSParameterError( + 'You must provide either both "tls_cert"/"tls_key" files, or neither, in order to use TLS.') + else: + self.cert = (tls_cert, tls_key) + + """ + Either set tls_verify to True (public/default CA checks) or to the path of a CA Cert file. + ref: https://github.com/kennethreitz/requests/blob/739d153ef77765392fa109bebead4260c05f3193/requests/adapters.py#L135-L137 + ref: https://github.com/kennethreitz/requests/blob/master/requests/sessions.py#L433-L439 + """ + if tls_verify: + if not tls_ca_cert: + self.verify = True + elif os.path.isfile(tls_ca_cert): + self.verify = tls_ca_cert + else: + raise exceptions.TLSParameterError( + 'If "tls_verify" is set, then "tls_ca_cert" must be blank (to check default/public CA list) OR a path to a CA Cert File.') + else: + self.verify = False + + """ Use SSLAdapter for the ability to specify SSL version """ + if tls or tls_verify: + self.mount('https://', ssladapter.SSLAdapter(self.ssl_version)) else: self.mount('http+unix://', unixconn.UnixAdapter(base_url, timeout)) diff --git a/docker/exceptions/__init__.py b/docker/exceptions/__init__.py new file mode 100644 index 0000000000..fdb8e77976 --- /dev/null +++ b/docker/exceptions/__init__.py @@ -0,0 +1 @@ +from .exceptions import TLSParameterError diff --git a/docker/exceptions/exceptions.py b/docker/exceptions/exceptions.py new file mode 100644 index 0000000000..a22231a902 --- /dev/null +++ b/docker/exceptions/exceptions.py @@ -0,0 +1,6 @@ +class TLSParameterError(ValueError): + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return self.msg + "\n\nTLS configurations should map the Docker CLI client configurations. See http://docs.docker.io/examples/https/ for API details." diff --git a/docker/ssladapter/__init__.py b/docker/ssladapter/__init__.py new file mode 100644 index 0000000000..182c35c581 --- /dev/null +++ b/docker/ssladapter/__init__.py @@ -0,0 +1 @@ +from .ssladapter import SSLAdapter diff --git a/docker/ssladapter/ssladapter.py b/docker/ssladapter/ssladapter.py new file mode 100644 index 0000000000..b78223ad3d --- /dev/null +++ b/docker/ssladapter/ssladapter.py @@ -0,0 +1,23 @@ +""" Resolves OpenSSL issues in some servers: + https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests/ + https://github.com/kennethreitz/requests/pull/799 +""" +from requests.adapters import HTTPAdapter +try: + from requests.packages.urllib3.poolmanager import PoolManager +except ImportError: + from urllib3.poolmanager import PoolManager + + +class SSLAdapter(HTTPAdapter): + '''An HTTPS Transport Adapter that uses an arbitrary SSL version.''' + def __init__(self, ssl_version=None, **kwargs): + self.ssl_version = ssl_version + + super(SSLAdapter, self).__init__(**kwargs) + + def init_poolmanager(self, connections, maxsize, block=False): + self.poolmanager = PoolManager(num_pools=connections, + maxsize=maxsize, + block=block, + ssl_version=self.ssl_version) \ No newline at end of file diff --git a/setup.py b/setup.py index 8c196f89d9..40e31c8427 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ name="docker-py", version='0.3.0', description="Python client for Docker.", - packages=['docker', 'docker.auth', 'docker.unixconn', 'docker.utils'], + packages=['docker', 'docker.auth', 'docker.unixconn', 'docker.utils', 'docker.ssladapter', 'docker.exceptions'], install_requires=requirements + test_requirements, zip_safe=False, test_suite='tests', From abbf685d469f1be97e755d272cc40810a9e2b995 Mon Sep 17 00:00:00 2001 From: Mo Omer Date: Tue, 13 May 2014 21:28:56 -0500 Subject: [PATCH 3/3] Erroneous change to get a rebase pushed up. --- docker/ssladapter/ssladapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/ssladapter/ssladapter.py b/docker/ssladapter/ssladapter.py index b78223ad3d..8a6cc975e4 100644 --- a/docker/ssladapter/ssladapter.py +++ b/docker/ssladapter/ssladapter.py @@ -1,5 +1,5 @@ """ Resolves OpenSSL issues in some servers: - https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests/ + https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests https://github.com/kennethreitz/requests/pull/799 """ from requests.adapters import HTTPAdapter