diff --git a/compose/cli/command.py b/compose/cli/command.py index 4e2722648..ccc76ceb4 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import logging import os import re -import ssl import six @@ -15,6 +14,7 @@ from ..config.environment import Environment from ..const import API_VERSIONS from ..project import Project from .docker_client import docker_client +from .docker_client import get_tls_version from .docker_client import tls_config_from_options from .utils import get_version_info @@ -60,23 +60,6 @@ def get_config_path_from_options(base_dir, options, environment): return None -def get_tls_version(environment): - compose_tls_version = environment.get('COMPOSE_TLS_VERSION', None) - if not compose_tls_version: - return None - - tls_attr_name = "PROTOCOL_{}".format(compose_tls_version) - if not hasattr(ssl, tls_attr_name): - log.warn( - 'The "{}" protocol is unavailable. You may need to update your ' - 'version of Python or OpenSSL. Falling back to TLSv1 (default).' - .format(compose_tls_version) - ) - return None - - return getattr(ssl, tls_attr_name) - - def get_client(environment, verbose=False, version=None, tls_config=None, host=None, tls_version=None): diff --git a/compose/cli/docker_client.py b/compose/cli/docker_client.py index 018d24513..44c7ad91d 100644 --- a/compose/cli/docker_client.py +++ b/compose/cli/docker_client.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from __future__ import unicode_literals import logging +import ssl from docker import APIClient from docker.errors import TLSParameterError @@ -16,7 +17,24 @@ from .utils import unquote_path log = logging.getLogger(__name__) -def tls_config_from_options(options): +def get_tls_version(environment): + compose_tls_version = environment.get('COMPOSE_TLS_VERSION', None) + if not compose_tls_version: + return None + + tls_attr_name = "PROTOCOL_{}".format(compose_tls_version) + if not hasattr(ssl, tls_attr_name): + log.warn( + 'The "{}" protocol is unavailable. You may need to update your ' + 'version of Python or OpenSSL. Falling back to TLSv1 (default).' + .format(compose_tls_version) + ) + return None + + return getattr(ssl, tls_attr_name) + + +def tls_config_from_options(options, environment=None): tls = options.get('--tls', False) ca_cert = unquote_path(options.get('--tlscacert')) cert = unquote_path(options.get('--tlscert')) @@ -24,7 +42,9 @@ def tls_config_from_options(options): verify = options.get('--tlsverify') skip_hostname_check = options.get('--skip-hostname-check', False) - advanced_opts = any([ca_cert, cert, key, verify]) + tls_version = get_tls_version(environment or {}) + + advanced_opts = any([ca_cert, cert, key, verify, tls_version]) if tls is True and not advanced_opts: return True @@ -35,7 +55,8 @@ def tls_config_from_options(options): return TLSConfig( client_cert=client_cert, verify=verify, ca_cert=ca_cert, - assert_hostname=False if skip_hostname_check else None + assert_hostname=False if skip_hostname_check else None, + ssl_version=tls_version ) return None diff --git a/tests/unit/cli/command_test.py b/tests/unit/cli/command_test.py index 3655c432e..c64a0401b 100644 --- a/tests/unit/cli/command_test.py +++ b/tests/unit/cli/command_test.py @@ -2,12 +2,10 @@ from __future__ import absolute_import from __future__ import unicode_literals import os -import ssl import pytest from compose.cli.command import get_config_path_from_options -from compose.cli.command import get_tls_version from compose.config.environment import Environment from compose.const import IS_WINDOWS_PLATFORM from tests import mock @@ -57,21 +55,3 @@ class TestGetConfigPathFromOptions(object): def test_no_path(self): environment = Environment.from_env_file('.') assert not get_config_path_from_options('.', {}, environment) - - -class TestGetTlsVersion(object): - def test_get_tls_version_default(self): - environment = {} - assert get_tls_version(environment) is None - - @pytest.mark.skipif(not hasattr(ssl, 'PROTOCOL_TLSv1_2'), reason='TLS v1.2 unsupported') - def test_get_tls_version_upgrade(self): - environment = {'COMPOSE_TLS_VERSION': 'TLSv1_2'} - assert get_tls_version(environment) == ssl.PROTOCOL_TLSv1_2 - - def test_get_tls_version_unavailable(self): - environment = {'COMPOSE_TLS_VERSION': 'TLSv5_5'} - with mock.patch('compose.cli.command.log') as mock_log: - tls_version = get_tls_version(environment) - mock_log.warn.assert_called_once_with(mock.ANY) - assert tls_version is None diff --git a/tests/unit/cli/docker_client_test.py b/tests/unit/cli/docker_client_test.py index aaa935afa..482ad9850 100644 --- a/tests/unit/cli/docker_client_test.py +++ b/tests/unit/cli/docker_client_test.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import os import platform +import ssl import docker import pytest @@ -10,6 +11,7 @@ import pytest import compose from compose.cli import errors from compose.cli.docker_client import docker_client +from compose.cli.docker_client import get_tls_version from compose.cli.docker_client import tls_config_from_options from tests import mock from tests import unittest @@ -157,3 +159,29 @@ class TLSConfigTestCase(unittest.TestCase): assert result.cert == (self.client_cert, self.key) assert result.ca_cert == self.ca_cert assert result.verify is True + + def test_tls_simple_with_tls_version(self): + tls_version = 'TLSv1' + options = {'--tls': True} + environment = {'COMPOSE_TLS_VERSION': tls_version} + result = tls_config_from_options(options, environment) + assert isinstance(result, docker.tls.TLSConfig) + assert result.ssl_version == ssl.PROTOCOL_TLSv1 + + +class TestGetTlsVersion(object): + def test_get_tls_version_default(self): + environment = {} + assert get_tls_version(environment) is None + + @pytest.mark.skipif(not hasattr(ssl, 'PROTOCOL_TLSv1_2'), reason='TLS v1.2 unsupported') + def test_get_tls_version_upgrade(self): + environment = {'COMPOSE_TLS_VERSION': 'TLSv1_2'} + assert get_tls_version(environment) == ssl.PROTOCOL_TLSv1_2 + + def test_get_tls_version_unavailable(self): + environment = {'COMPOSE_TLS_VERSION': 'TLSv5_5'} + with mock.patch('compose.cli.docker_client.log') as mock_log: + tls_version = get_tls_version(environment) + mock_log.warn.assert_called_once_with(mock.ANY) + assert tls_version is None