Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,62 @@ c.start(container_id, binds={
}
})
```

Connection to daemon using HTTPS
================================

*These instructions are docker-py specific. Please refer to
http://docs.docker.com/articles/https/ first.*

* Authenticate server based on public/default CA pool

```python
client = docker.Client(base_url='<https_url>', tls=True)
```

Equivalent CLI options: `docker --tls ...`

If you want to use TLS but don't want to verify the server certificate
(for example when testing with a self-signed certificate):

```python
tls_config = docker.tls.TLSConfig(verify=False)
client = docker.Client(base_url='<https_url>', tls=tls_config)
```

* Authenticate server based on given CA

```python
tls_config = docker.tls.TLSConfig(ca_cert='/path/to/ca.pem')
client = docker.Client(base_url='<https_url>', tls=tls_config)
```

Equivalent CLI options: `docker --tlsverify --tlscacert /path/to/ca.pem ...`

* Authenticate with client certificate, do not authenticate server
based on given CA

```python
tls_config = docker.tls.TLSConfig(
client_cert=('/path/to/client-cert.pem', '/path/to/client-key.pem')
)
client = docker.Client(base_url='<https_url>', tls=tls_config)
```

Equivalent CLI options:
`docker --tls --tlscert /path/to/client-cert.pem
--tlskey /path/to/client-key.pem ...`

* Authenticate with client certificate, authenticate server based on given CA

```python
tls_config = docker.tls.TLSConfig(
client_cert=('/path/to/client-cert.pem', '/path/to/client-key.pem'),
ca_cert='/path/to/ca.pem'
)
client = docker.Client(base_url='<https_url>', tls=tls_config)
```

Equivalent CLI options:
`docker --tlsverify --tlscert /path/to/client-cert.pem
--tlskey /path/to/client-key.pem --tlscacert /path/to/ca.pem ...`
16 changes: 14 additions & 2 deletions docker/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@

from .auth import auth
from .unixconn import unixconn
from .ssladapter import ssladapter
from .utils import utils
from . import errors
from .tls import TLSConfig

if not six.PY3:
import websocket
Expand All @@ -37,17 +39,27 @@

class Client(requests.Session):
def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION,
timeout=DEFAULT_TIMEOUT_SECONDS):
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False):
super(Client, self).__init__()
base_url = utils.parse_host(base_url)
if 'http+unix:///' in base_url:
base_url = base_url.replace('unix:/', 'unix:')
if tls and not base_url.startswith('https://'):
raise errors.TLSParameterError(
'If using TLS, the base_url argument must begin with '
'"https://".')
self.base_url = base_url
self._version = version
self._timeout = timeout
self._auth_configs = auth.load_config()

self.mount('http+unix://', unixconn.UnixAdapter(base_url, timeout))
# Use SSLAdapter for the ability to specify SSL version
if isinstance(tls, TLSConfig):
tls.configure_client(self)
elif tls:
self.mount('https://', ssladapter.SSLAdapter())
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
Expand Down
11 changes: 11 additions & 0 deletions docker/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,14 @@ class InvalidConfigFile(DockerException):

class DeprecatedMethod(DockerException):
pass


class TLSParameterError(DockerException):
def __init__(self, msg):
self.msg = msg

def __str__(self):
return self.msg + (". TLS configurations should map the Docker CLI "
"client configurations. See "
"http://docs.docker.com/examples/https/ for "
"API details.")
1 change: 1 addition & 0 deletions docker/ssladapter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .ssladapter import SSLAdapter # flake8: noqa
33 changes: 33 additions & 0 deletions docker/ssladapter/ssladapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
""" 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 distutils.version import StrictVersion
from requests.adapters import HTTPAdapter
try:
import requests.packages.urllib3 as urllib3
except ImportError:
import urllib3


PoolManager = urllib3.poolmanager.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):
urllib_ver = urllib3.__version__.split('-')[0]
if urllib3 and urllib_ver != 'dev' and \
StrictVersion(urllib_ver) <= StrictVersion('1.5'):
self.poolmanager = PoolManager(num_pools=connections,
maxsize=maxsize,
block=block)
else:
self.poolmanager = PoolManager(num_pools=connections,
maxsize=maxsize,
block=block,
ssl_version=self.ssl_version)
68 changes: 68 additions & 0 deletions docker/tls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import os

from . import errors
from .ssladapter import ssladapter


class TLSConfig(object):
cert = None
verify = None
ssl_version = None

def __init__(self, client_cert=None, ca_cert=None, verify=None,
ssl_version=None):
# Argument compatibility/mapping with
# http://docs.docker.com/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
# http://tinyurl.com/kxga8hb
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 client_cert:
try:
tls_cert, tls_key = client_cert
except ValueError:
raise errors.TLSParameterError(
'client_config must be a tuple of'
' (client certificate, key file)'
)

if not (tls_cert and tls_key) or (not os.path.isfile(tls_cert) or
not os.path.isfile(tls_key)):
raise errors.TLSParameterError(
'Path to a certificate and key files must be provided'
' through the client_config param'
)
self.cert = (tls_cert, tls_key)

# Either set verify to True (public/default CA checks) or to the
# path of a CA Cert file.
if verify is not None:
if not ca_cert:
self.verify = verify
elif os.path.isfile(ca_cert):
if not verify:
raise errors.TLSParameterError(
'verify can not be False when a CA cert is'
' provided.'
)
self.verify = ca_cert
else:
raise errors.TLSParameterError(
'Invalid CA certificate provided for `tls_ca_cert`.'
)

def configure_client(self, client):
client.ssl_version = self.ssl_version
if self.verify is not None:
client.verify = self.verify
if self.cert:
client.cert = self.cert
client.mount('https://', ssladapter.SSLAdapter(self.ssl_version))
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
name="docker-py",
version=version,
description="Python client for Docker.",
packages=['docker', 'docker.auth', 'docker.unixconn', 'docker.utils'],
packages=['docker', 'docker.auth', 'docker.unixconn', 'docker.utils',
'docker.ssladapter'],
install_requires=requirements + test_requirements,
zip_safe=False,
test_suite='tests',
Expand Down