Repository: diyan/pywinrm

Issue:
server_cert_validation 'ignore' no longer works in 0.3.0
The following no longer works against a self-signed certificate, has `server_cert_validation` changed? 

```
from winrm.protocol import Protocol

p = Protocol(
    endpoint='https://somewindows:5986/wsman',
    transport='ntlm',
    username=r'\Admin',
    password='ThePassword1',
    server_cert_validation='ignore')
shell_id = p.open_shell()
command_id = p.run_command(shell_id, 'ipconfig', ['/all'])
std_out, std_err, status_code = p.get_command_output(shell_id, command_id)
print std_out
p.cleanup_command(shell_id, command_id)
p.close_shell(shell_id)
```

> Traceback (most recent call last):
>   File "winrmtest.py", line 9, in <module>
>     shell_id = p.open_shell()
>   File "/usr/lib/python2.7/site-packages/winrm/protocol.py", line 157, in open_shell
>     res = self.send_message(xmltodict.unparse(req))
>   File "/usr/lib/python2.7/site-packages/winrm/protocol.py", line 234, in send_message
>     resp = self.transport.send_message(message)
>   File "/usr/lib/python2.7/site-packages/winrm/transport.py", line 256, in send_message
>     response = self._send_message_request(prepared_request, message)
>   File "/usr/lib/python2.7/site-packages/winrm/transport.py", line 261, in _send_message_request
>     response = self.session.send(prepared_request, timeout=self.read_timeout_sec)
>   File "/usr/lib/python2.7/site-packages/requests/sessions.py", line 618, in send
>     r = adapter.send(request, **kwargs)
>   File "/usr/lib/python2.7/site-packages/requests/adapters.py", line 506, in send
>     raise SSLError(e, request=request)
> requests.exceptions.SSLError: HTTPSConnectionPool(host='123.45.67.89', port=5986): Max retries exceeded with url: /wsman (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))



With:

```
pip show pywinrm
---
Metadata-Version: 2.0
Name: pywinrm
Version: 0.3.0
Summary: Python library for Windows Remote Management
Home-page: http://github.com/diyan/pywinrm/
Author: Alexey Diyan
Author-email: alexey.diyan@gmail.com
Installer: pip
License: MIT license
Location: /usr/lib/python2.7/site-packages
Requires: six, requests-ntlm, requests, xmltodict
Classifiers:
  Development Status :: 4 - Beta
  Environment :: Console
  Intended Audience :: Developers
  Intended Audience :: System Administrators
  Natural Language :: English
  License :: OSI Approved :: MIT License
  Programming Language :: Python
  Programming Language :: Python :: 2
  Programming Language :: Python :: 2.6
  Programming Language :: Python :: 2.7
  Programming Language :: Python :: 3
  Programming Language :: Python :: 3.3
  Programming Language :: Python :: 3.4
  Programming Language :: Python :: 3.5
  Programming Language :: Python :: 3.6
  Programming Language :: Python :: Implementation :: PyPy
  Topic :: Software Development :: Libraries :: Python Modules
  Topic :: System :: Clustering
  Topic :: System :: Distributed Computing
  Topic :: System :: Systems Administration
```

However this previous release does work

```
pip show pywinrm
---
Metadata-Version: 2.0
Name: pywinrm
Version: 0.2.2
Summary: Python library for Windows Remote Management
Home-page: http://github.com/diyan/pywinrm/
Author: Alexey Diyan
Author-email: alexey.diyan@gmail.com
Installer: pip
License: MIT license
Location: /usr/lib/python2.7/site-packages
Requires: six, requests-ntlm, requests, xmltodict
Classifiers:
  Development Status :: 4 - Beta
  Environment :: Console
  Intended Audience :: Developers
  Intended Audience :: System Administrators
  Natural Language :: English
  License :: OSI Approved :: MIT License
  Programming Language :: Python
  Programming Language :: Python :: 2
  Programming Language :: Python :: 2.6
  Programming Language :: Python :: 2.7
  Programming Language :: Python :: 3
  Programming Language :: Python :: 3.3
  Programming Language :: Python :: 3.4
  Programming Language :: Python :: 3.5
  Programming Language :: Python :: 3.6
  Programming Language :: Python :: Implementation :: PyPy
  Topic :: Software Development :: Libraries :: Python Modules
  Topic :: System :: Clustering
  Topic :: System :: Distributed Computing
  Topic :: System :: Systems Administration
```


  

Discussion:

Patch:
diff --git a/winrm/transport.py b/winrm/transport.py
index 81e8826..430c761 100644
--- a/winrm/transport.py
+++ b/winrm/transport.py
@@ -147,19 +147,21 @@ class Transport(object):
     def build_session(self):
         session = requests.Session()
 
-        session.verify = self.server_cert_validation == 'validate'
-        if session.verify and self.ca_trust_path:
-                session.verify = self.ca_trust_path
-
-        # configure proxies from HTTP/HTTPS_PROXY envvars
+        # allow some settings to be merged from env
         session.trust_env = True
         settings = session.merge_environment_settings(url=self.endpoint, proxies={}, stream=None,
                                                       verify=None, cert=None)
 
-        # we're only applying proxies and/or verify from env, other settings are ignored
+        # get proxy settings from env
+        # FUTURE: allow proxy to be passed in directly to supersede this value
         session.proxies = settings['proxies']
 
-        if settings['verify'] is not None or self.ca_trust_path is not None:
+        # specified validation mode takes precedence
+        session.verify = self.server_cert_validation == 'validate'
+
+        # patch in CA path override if one was specified in init or env
+        if session.verify and (self.ca_trust_path is not None or settings['verify'] is not None):
+            # session.verify can be either a bool or path to a CA store; prefer passed-in value over env if both are present
             session.verify = self.ca_trust_path or settings['verify']
 
         encryption_available = False


Test patch:
diff --git a/winrm/tests/test_transport.py b/winrm/tests/test_transport.py
index 94ebe32..14e00cf 100644
--- a/winrm/tests/test_transport.py
+++ b/winrm/tests/test_transport.py
@@ -1,21 +1,63 @@
 # coding=utf-8
 import os
+import tempfile
 from winrm.transport import Transport
 
 
-def test_build_session():
-    transport = Transport(endpoint="Endpoint",
+def test_build_session_cert_validate():
+    t_default = Transport(endpoint="Endpoint",
                           server_cert_validation='validate',
                           username='test',
                           password='test',
                           auth_method='basic',
                           )
-    os.environ['REQUESTS_CA_BUNDLE'] = 'path_to_REQUESTS_CA_CERT'
-    transport.build_session()
-    assert(transport.session.verify == 'path_to_REQUESTS_CA_CERT')
-    del os.environ['REQUESTS_CA_BUNDLE']
+    t_ca_override = Transport(endpoint="Endpoint",
+                              server_cert_validation='validate',
+                              username='test',
+                              password='test',
+                              auth_method='basic',
+                              ca_trust_path='overridepath',
+                              )
+    try:
+        os.environ['REQUESTS_CA_BUNDLE'] = 'path_to_REQUESTS_CA_CERT'
+        t_default.build_session()
+        t_ca_override.build_session()
+        assert(t_default.session.verify == 'path_to_REQUESTS_CA_CERT')
+        assert(t_ca_override.session.verify == 'overridepath')
+    finally:
+        del os.environ['REQUESTS_CA_BUNDLE']
 
-    os.environ['CURL_CA_BUNDLE'] = 'path_to_CURL_CA_CERT'
-    transport.build_session()
-    assert(transport.session.verify == 'path_to_CURL_CA_CERT')
-    del os.environ['CURL_CA_BUNDLE']
+    try:
+        os.environ['CURL_CA_BUNDLE'] = 'path_to_CURL_CA_CERT'
+        t_default.build_session()
+        t_ca_override.build_session()
+        assert(t_default.session.verify == 'path_to_CURL_CA_CERT')
+        assert (t_ca_override.session.verify == 'overridepath')
+    finally:
+        del os.environ['CURL_CA_BUNDLE']
+
+
+def test_build_session_cert_ignore():
+    t_default = Transport(endpoint="Endpoint",
+                          server_cert_validation='ignore',
+                          username='test',
+                          password='test',
+                          auth_method='basic',
+                          )
+    t_ca_override = Transport(endpoint="Endpoint",
+                              server_cert_validation='ignore',
+                              username='test',
+                              password='test',
+                              auth_method='basic',
+                              ca_trust_path='boguspath'
+                              )
+    try:
+        os.environ['REQUESTS_CA_BUNDLE'] = 'path_to_REQUESTS_CA_CERT'
+        os.environ['CURL_CA_BUNDLE'] = 'path_to_CURL_CA_CERT'
+        t_default.build_session()
+        t_ca_override.build_session()
+        assert(isinstance(t_default.session.verify, bool) and not t_default.session.verify)
+        assert (isinstance(t_ca_override.session.verify, bool) and not t_ca_override.session.verify)
+    finally:
+        del os.environ['REQUESTS_CA_BUNDLE']
+        del os.environ['CURL_CA_BUNDLE']