From 13ac993d252d6895f5ae2fb38890b092d85a4ebd Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Fri, 6 Jan 2017 11:03:10 -0600 Subject: [PATCH] Fallback to old ssl_wrap_socket --- lib/ansible/module_utils/urls.py | 33 +++++++++++---- test/integration/targets/uri/tasks/main.yml | 46 ++++++++++++++++++--- test/integration/targets/uri/vars/main.yml | 20 ++++++--- 3 files changed, 81 insertions(+), 18 deletions(-) diff --git a/lib/ansible/module_utils/urls.py b/lib/ansible/module_utils/urls.py index 2067d65e7b6..92ebf5a0a69 100644 --- a/lib/ansible/module_utils/urls.py +++ b/lib/ansible/module_utils/urls.py @@ -145,14 +145,26 @@ try: except ImportError: HAS_SSLCONTEXT = False +# SNI Handling for python < 2.7.9 with urllib3 support try: + # urllib3>=1.15 + HAS_URLLIB3_SSL_WRAP_SOCKET = False try: from urllib3.contrib.pyopenssl import PyOpenSSLContext except ImportError: from requests.packages.urllib3.contrib.pyopenssl import PyOpenSSLContext - HAS_PYOPENSSL_CONTEXT = True + HAS_URLLIB3_PYOPENSSLCONTEXT = True except ImportError: - HAS_PYOPENSSL_CONTEXT = False + # urllib3<1.15,>=1.6 + HAS_URLLIB3_PYOPENSSLCONTEXT = False + try: + try: + from urllib3.contrib.pyopenssl import ssl_wrap_socket + except ImportError: + from requests.packages.urllib3.contrib.pyopenssl import ssl_wrap_socket + HAS_URLLIB3_SSL_WRAP_SOCKET = True + except ImportError: + pass # Select a protocol that includes all secure tls protocols # Exclude insecure ssl protocols if possible @@ -362,7 +374,7 @@ if hasattr(httplib, 'HTTPSConnection') and hasattr(urllib_request, 'HTTPSHandler self.context = None if HAS_SSLCONTEXT: self.context = create_default_context() - elif HAS_PYOPENSSL_CONTEXT: + elif HAS_URLLIB3_PYOPENSSLCONTEXT: self.context = PyOpenSSLContext(PROTOCOL) if self.context and self.cert_file: self.context.load_cert_chain(self.cert_file, self.key_file) @@ -383,8 +395,11 @@ if hasattr(httplib, 'HTTPSConnection') and hasattr(urllib_request, 'HTTPSHandler self._tunnel() server_hostname = self._tunnel_host - if HAS_SSLCONTEXT or HAS_PYOPENSSL_CONTEXT: + if HAS_SSLCONTEXT or HAS_URLLIB3_PYOPENSSLCONTEXT: self.sock = self.context.wrap_socket(sock, server_hostname=server_hostname) + elif HAS_URLLIB3_SSL_WRAP_SOCKET: + self.sock = ssl_wrap_socket(sock, keyfile=self.key_file, cert_reqs=ssl.CERT_NONE, certfile=self.cert_file, ssl_version=PROTOCOL, + server_hostname=server_hostname) else: self.sock = ssl.wrap_socket(sock, keyfile=self.key_file, certfile=self.cert_file, ssl_version=PROTOCOL) @@ -541,7 +556,7 @@ def build_ssl_validation_error(hostname, port, paths, exc=None): if not HAS_SSLCONTEXT: msg.append('If the website serving the url uses SNI you need' ' python >= 2.7.9 on your managed machine') - if not HAS_PYOPENSSL_CONTEXT: + if not HAS_URLLIB3_PYOPENSSLCONTEXT or not HAS_URLLIB3_SSL_WRAP_SOCKET: msg.append('or you can install the `urllib3`, `pyOpenSSL`,' ' `ndg-httpsclient`, and `pyasn1` python modules') @@ -665,7 +680,7 @@ class SSLValidationHandler(urllib_request.BaseHandler): return True def _make_context(self, to_add_ca_cert_path): - if HAS_PYOPENSSL_CONTEXT: + if HAS_URLLIB3_PYOPENSSLCONTEXT: context = PyOpenSSLContext(PROTOCOL) else: context = create_default_context() @@ -677,7 +692,7 @@ class SSLValidationHandler(urllib_request.BaseHandler): tmp_ca_cert_path, to_add_ca_cert_path, paths_checked = self.get_ca_certs() https_proxy = os.environ.get('https_proxy') context = None - if HAS_SSLCONTEXT or HAS_PYOPENSSL_CONTEXT: + if HAS_SSLCONTEXT or HAS_URLLIB3_PYOPENSSLCONTEXT: context = self._make_context(to_add_ca_cert_path) # Detect if 'no_proxy' environment variable is set and if our URL is included @@ -708,6 +723,8 @@ class SSLValidationHandler(urllib_request.BaseHandler): self.validate_proxy_response(connect_result) if context: ssl_s = context.wrap_socket(s, server_hostname=self.hostname) + elif HAS_URLLIB3_SSL_WRAP_SOCKET: + ssl_s = ssl_wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED, ssl_version=PROTOCOL, server_hostname=self.hostname) else: ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED, ssl_version=PROTOCOL) match_hostname(ssl_s.getpeercert(), self.hostname) @@ -717,6 +734,8 @@ class SSLValidationHandler(urllib_request.BaseHandler): s.connect((self.hostname, self.port)) if context: ssl_s = context.wrap_socket(s, server_hostname=self.hostname) + elif HAS_URLLIB3_SSL_WRAP_SOCKET: + ssl_s = ssl_wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED, ssl_version=PROTOCOL, server_hostname=self.hostname) else: ssl_s = ssl.wrap_socket(s, ca_certs=tmp_ca_cert_path, cert_reqs=ssl.CERT_REQUIRED, ssl_version=PROTOCOL) match_hostname(ssl_s.getpeercert(), self.hostname) diff --git a/test/integration/targets/uri/tasks/main.yml b/test/integration/targets/uri/tasks/main.yml index dc360989763..b8a15bd2ab5 100644 --- a/test/integration/targets/uri/tasks/main.yml +++ b/test/integration/targets/uri/tasks/main.yml @@ -236,15 +236,13 @@ - name: install OS packages that are needed for SNI on old python package: name: "{{ item }}" - with_items: "{{ uri_os_packages[ansible_os_family] | default([]) }}" + with_items: "{{ uri_os_packages[ansible_os_family].step1 | default([]) }}" when: not ansible_python.has_sslcontext and not is_ubuntu_precise|bool - name: install python modules for Older Python SNI verification pip: name: "{{ item }}" with_items: - - urllib3 - - PyOpenSSL - ndg-httpsclient when: not ansible_python.has_sslcontext and not is_ubuntu_precise|bool @@ -262,7 +260,7 @@ - 'sni_host in result.content' when: not ansible_python.has_sslcontext and not is_ubuntu_precise|bool -- name: Uninstall ndg-httpsclient and urllib3 +- name: Uninstall ndg-httpsclient pip: name: "{{ item }}" state: absent @@ -274,7 +272,45 @@ package: name: "{{ item }}" state: absent - with_items: "{{ uri_os_packages[ansible_os_family] | default([]) }}" + with_items: "{{ uri_os_packages[ansible_os_family].step1 | default([]) }}" + when: not ansible_python.has_sslcontext and not is_ubuntu_precise|bool + +- name: install OS packages that are needed for building cryptography + package: + name: "{{ item }}" + with_items: "{{ uri_os_packages[ansible_os_family].step2 | default([]) }}" + when: not ansible_python.has_sslcontext and not is_ubuntu_precise|bool + +- name: install urllib3 and pyopenssl via pip + pip: + name: "{{ item }}" + state: latest + with_items: + - urllib3 + - PyOpenSSL + when: not ansible_python.has_sslcontext and not is_ubuntu_precise|bool + +- name: Verify SNI verification succeeds on old python with pip urllib3 contrib + uri: + url: 'https://{{ sni_host }}' + return_content: true + when: not ansible_python.has_sslcontext and not is_ubuntu_precise|bool + register: result + +- name: Assert SNI verification succeeds on old python with pip urllib3 contrib + assert: + that: + - result|success + - 'sni_host in result.content' + when: not ansible_python.has_sslcontext and not is_ubuntu_precise|bool + +- name: Uninstall urllib3 and PyOpenSSL + pip: + name: "{{ item }}" + state: absent + with_items: + - urllib3 + - PyOpenSSL when: not ansible_python.has_sslcontext and not is_ubuntu_precise|bool - name: validate the status_codes are correct diff --git a/test/integration/targets/uri/vars/main.yml b/test/integration/targets/uri/vars/main.yml index 6505d67a575..b50957386b5 100644 --- a/test/integration/targets/uri/vars/main.yml +++ b/test/integration/targets/uri/vars/main.yml @@ -1,9 +1,17 @@ uri_os_packages: RedHat: - - python-pyasn1 - - libffi-devel - - openssl-devel + step1: + - python-pyasn1 + - pyOpenSSL + - python-urllib3 + step2: + - libffi-devel + - openssl-devel Debian: - - python-pyasn1 - - libffi-dev - - libssl-dev \ No newline at end of file + step1: + - python-pyasn1 + - python-openssl + - python-urllib3 + step2: + - libffi-dev + - libssl-dev \ No newline at end of file