def test_encrypt_message(): test_session = SessionTest() test_message = b"unencrypted message" test_endpoint = b"endpoint" encryption = Encryption(test_session, 'ntlm') actual = encryption.prepare_encrypted_request(test_session, test_endpoint, test_message) expected_encrypted_message = b"dW5lbmNyeXB0ZWQgbWVzc2FnZQ==" expected_signature = b"1234" signature_length = struct.pack("<i", len(expected_signature)) assert actual.headers == { "Content-Length": "272", "Content-Type": 'multipart/encrypted;protocol="application/HTTP-SPNEGO-session-encrypted";boundary="Encrypted Boundary"' } assert actual.body == b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/HTTP-SPNEGO-session-encrypted\r\n" \ b"\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=19\r\n" \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/octet-stream\r\n" + \ signature_length + expected_signature + expected_encrypted_message + \ b"--Encrypted Boundary--\r\n"
def test_encrypt_large_credssp_message(): test_session = SessionTest() test_message = b"unencrypted message " * 2048 test_endpoint = b"endpoint" message_chunks = [ test_message[i:i + 16384] for i in range(0, len(test_message), 16384) ] encryption = Encryption(test_session, 'credssp') actual = encryption.prepare_encrypted_request(test_session, test_endpoint, test_message) expected_encrypted_message1 = base64.b64encode(message_chunks[0]) expected_encrypted_message2 = base64.b64encode(message_chunks[1]) expected_encrypted_message3 = base64.b64encode(message_chunks[2]) assert actual.headers == { "Content-Length": "55303", "Content-Type": 'multipart/x-multi-encrypted;protocol="application/HTTP-CredSSP-session-encrypted";boundary="Encrypted Boundary"' } assert actual.body == b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/HTTP-CredSSP-session-encrypted\r\n" \ b"\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=16384\r\n" \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/octet-stream\r\n" + \ struct.pack("<i", 32) + expected_encrypted_message1 + \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/HTTP-CredSSP-session-encrypted\r\n" \ b"\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=16384\r\n" \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/octet-stream\r\n" + \ struct.pack("<i", 32) + expected_encrypted_message2 + \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/HTTP-CredSSP-session-encrypted\r\n" \ b"\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=8192\r\n" \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/octet-stream\r\n" + \ struct.pack("<i", 32) + expected_encrypted_message3 + \ b"--Encrypted Boundary--\r\n"
def test_encrypt_large_credssp_message(): test_session = SessionTest() test_message = b"unencrypted message " * 2048 test_endpoint = "http://testhost.com" message_chunks = [test_message[i:i + 16384] for i in range(0, len(test_message), 16384)] encryption = Encryption(test_session, 'credssp') actual = encryption.prepare_encrypted_request(test_session, test_endpoint, test_message) expected_encrypted_message1 = base64.b64encode(message_chunks[0]) expected_encrypted_message2 = base64.b64encode(message_chunks[1]) expected_encrypted_message3 = base64.b64encode(message_chunks[2]) assert actual.headers == { "Content-Length": "55303", "Content-Type": 'multipart/x-multi-encrypted;protocol="application/HTTP-CredSSP-session-encrypted";boundary="Encrypted Boundary"' } assert actual.body == b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/HTTP-CredSSP-session-encrypted\r\n" \ b"\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=16384\r\n" \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/octet-stream\r\n" + \ struct.pack("<i", 32) + expected_encrypted_message1 + \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/HTTP-CredSSP-session-encrypted\r\n" \ b"\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=16384\r\n" \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/octet-stream\r\n" + \ struct.pack("<i", 32) + expected_encrypted_message2 + \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/HTTP-CredSSP-session-encrypted\r\n" \ b"\tOriginalContent: type=application/soap+xml;charset=UTF-8;Length=8192\r\n" \ b"--Encrypted Boundary\r\n" \ b"\tContent-Type: application/octet-stream\r\n" + \ struct.pack("<i", 32) + expected_encrypted_message3 + \ b"--Encrypted Boundary--\r\n"
class Transport(object): def __init__( self, endpoint, username=None, password=None, realm=None, service=None, keytab=None, ca_trust_path='legacy_requests', cert_pem=None, cert_key_pem=None, read_timeout_sec=None, server_cert_validation='validate', kerberos_delegation=False, kerberos_hostname_override=None, auth_method='auto', message_encryption='auto', credssp_disable_tlsv1_2=False, credssp_auth_mechanism='auto', credssp_minimum_version=2, send_cbt=True, proxy='legacy_requests'): self.endpoint = endpoint self.username = username self.password = password self.realm = realm self.service = service self.keytab = keytab self.ca_trust_path = ca_trust_path self.cert_pem = cert_pem self.cert_key_pem = cert_key_pem self.read_timeout_sec = read_timeout_sec self.server_cert_validation = server_cert_validation self.kerberos_hostname_override = kerberos_hostname_override self.message_encryption = message_encryption self.credssp_disable_tlsv1_2 = credssp_disable_tlsv1_2 self.credssp_auth_mechanism = credssp_auth_mechanism self.credssp_minimum_version = credssp_minimum_version self.send_cbt = send_cbt self.proxy = proxy if self.server_cert_validation not in [None, 'validate', 'ignore']: raise WinRMError('invalid server_cert_validation mode: %s' % self.server_cert_validation) # defensively parse this to a bool if isinstance(kerberos_delegation, bool): self.kerberos_delegation = kerberos_delegation else: self.kerberos_delegation = bool(strtobool(str(kerberos_delegation))) self.auth_method = auth_method self.default_headers = { 'Content-Type': 'application/soap+xml;charset=UTF-8', 'User-Agent': 'Python WinRM client', } # try to suppress user-unfriendly warnings from requests' vendored urllib3 try: from requests.packages.urllib3.exceptions import InsecurePlatformWarning warnings.simplefilter('ignore', category=InsecurePlatformWarning) except Exception: pass # oh well, we tried... try: from requests.packages.urllib3.exceptions import SNIMissingWarning warnings.simplefilter('ignore', category=SNIMissingWarning) except Exception: pass # oh well, we tried... # if we're explicitly ignoring validation, try to suppress InsecureRequestWarning, since the user opted-in if self.server_cert_validation == 'ignore': try: from requests.packages.urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', category=InsecureRequestWarning) except Exception: pass # oh well, we tried... try: from urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', category=InsecureRequestWarning) except Exception: pass # oh well, we tried... # validate credential requirements for various auth types if self.auth_method != 'kerberos': if self.auth_method == 'certificate' or ( self.auth_method == 'ssl' and (self.cert_pem or self.cert_key_pem)): if not self.cert_pem or not self.cert_key_pem: raise InvalidCredentialsError("both cert_pem and cert_key_pem must be specified for cert auth") if not os.path.exists(self.cert_pem): raise InvalidCredentialsError("cert_pem file not found (%s)" % self.cert_pem) if not os.path.exists(self.cert_key_pem): raise InvalidCredentialsError("cert_key_pem file not found (%s)" % self.cert_key_pem) else: if not self.username: raise InvalidCredentialsError("auth method %s requires a username" % self.auth_method) if self.password is None: raise InvalidCredentialsError("auth method %s requires a password" % self.auth_method) self.session = None # Used for encrypting messages self.encryption = None # The Pywinrm Encryption class used to encrypt/decrypt messages if self.message_encryption not in ['auto', 'always', 'never']: raise WinRMError( "invalid message_encryption arg: %s. Should be 'auto', 'always', or 'never'" % self.message_encryption) def build_session(self): session = requests.Session() proxies = dict() if self.proxy is None: proxies['no_proxy'] = '*' elif self.proxy != 'legacy_requests': # If there was a proxy specified then use it proxies['http'] = self.proxy proxies['https'] = self.proxy # Merge proxy environment variables settings = session.merge_environment_settings(url=self.endpoint, proxies=proxies, stream=None, verify=None, cert=None) global DISPLAYED_PROXY_WARNING # We want to eventually stop reading proxy information from the environment. # Also only display the warning once. This method can be called many times during an application's runtime. if not DISPLAYED_PROXY_WARNING and self.proxy == 'legacy_requests' and ( 'http' in settings['proxies'] or 'https' in settings['proxies']): message = "'pywinrm' will use an environment defined proxy. This feature will be disabled in " \ "the future, please specify it explicitly." if 'http' in settings['proxies']: message += " HTTP proxy {proxy} discovered.".format(proxy=settings['proxies']['http']) if 'https' in settings['proxies']: message += " HTTPS proxy {proxy} discovered.".format(proxy=settings['proxies']['https']) DISPLAYED_PROXY_WARNING = True warnings.warn(message, DeprecationWarning) session.proxies = settings['proxies'] # 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: if self.ca_trust_path == 'legacy_requests' and settings['verify'] is not None: # We will session.verify = settings['verify'] global DISPLAYED_CA_TRUST_WARNING # We want to eventually stop reading proxy information from the environment. # Also only display the warning once. This method can be called many times during an application's runtime. if not DISPLAYED_CA_TRUST_WARNING and session.verify is not True: message = "'pywinrm' will use an environment variable defined CA Trust. This feature will be disabled in " \ "the future, please specify it explicitly." if os.environ.get('REQUESTS_CA_BUNDLE') is not None: message += " REQUESTS_CA_BUNDLE contains {ca_path}".format(ca_path=os.environ.get('REQUESTS_CA_BUNDLE')) elif os.environ.get('CURL_CA_BUNDLE') is not None: message += " CURL_CA_BUNDLE contains {ca_path}".format(ca_path=os.environ.get('CURL_CA_BUNDLE')) DISPLAYED_CA_TRUST_WARNING = True warnings.warn(message, DeprecationWarning) elif session.verify and self.ca_trust_path 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 encryption_available = False if self.auth_method == 'kerberos': if not HAVE_KERBEROS: raise WinRMError("requested auth method is kerberos, but requests_kerberos is not installed") man_args = dict( mutual_authentication=REQUIRED, ) opt_args = dict( delegate=self.kerberos_delegation, force_preemptive=True, principal=self.username, hostname_override=self.kerberos_hostname_override, sanitize_mutual_error_response=False, service=self.service, send_cbt=self.send_cbt ) kerb_args = self._get_args(man_args, opt_args, HTTPKerberosAuth.__init__) session.auth = HTTPKerberosAuth(**kerb_args) encryption_available = hasattr(session.auth, 'winrm_encryption_available') and session.auth.winrm_encryption_available elif self.auth_method in ['certificate', 'ssl']: if self.auth_method == 'ssl' and not self.cert_pem and not self.cert_key_pem: # 'ssl' was overloaded for HTTPS with optional certificate auth, # fall back to basic auth if no cert specified session.auth = requests.auth.HTTPBasicAuth(username=self.username, password=self.password) else: session.cert = (self.cert_pem, self.cert_key_pem) session.headers['Authorization'] = \ "http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/https/mutual" elif self.auth_method == 'ntlm': if not HAVE_NTLM: raise WinRMError("requested auth method is ntlm, but requests_ntlm is not installed") man_args = dict( username=self.username, password=self.password ) opt_args = dict( send_cbt=self.send_cbt ) ntlm_args = self._get_args(man_args, opt_args, HttpNtlmAuth.__init__) session.auth = HttpNtlmAuth(**ntlm_args) # check if requests_ntlm has the session_security attribute available for encryption encryption_available = hasattr(session.auth, 'session_security') # TODO: ssl is not exactly right here- should really be client_cert elif self.auth_method in ['basic', 'plaintext']: session.auth = requests.auth.HTTPBasicAuth(username=self.username, password=self.password) elif self.auth_method == 'credssp': if not HAVE_CREDSSP: raise WinRMError("requests auth method is credssp, but requests-credssp is not installed") man_args = dict( username=self.username, password=self.password ) opt_args = dict( disable_tlsv1_2=self.credssp_disable_tlsv1_2, auth_mechanism=self.credssp_auth_mechanism, minimum_version=self.credssp_minimum_version ) credssp_args = self._get_args(man_args, opt_args, HttpCredSSPAuth.__init__) session.auth = HttpCredSSPAuth(**credssp_args) encryption_available = True else: raise WinRMError("unsupported auth method: %s" % self.auth_method) session.headers.update(self.default_headers) self.session = session # Will check the current config and see if we need to setup message encryption if self.message_encryption == 'always' and not encryption_available: raise WinRMError( "message encryption is set to 'always' but the selected auth method %s does not support it" % self.auth_method) elif encryption_available: if self.message_encryption == 'always': self.setup_encryption() elif self.message_encryption == 'auto' and not self.endpoint.lower().startswith('https'): self.setup_encryption() def setup_encryption(self): # Security context doesn't exist, sending blank message to initialise context request = requests.Request('POST', self.endpoint, data=None) prepared_request = self.session.prepare_request(request) self._send_message_request(prepared_request, '') self.encryption = Encryption(self.session, self.auth_method) def close_session(self): if not self.session: return self.session.close() self.session = None def send_message(self, message): if not self.session: self.build_session() # urllib3 fails on SSL retries with unicode buffers- must send it a byte string # see https://github.com/shazow/urllib3/issues/717 if isinstance(message, unicode_type): message = message.encode('utf-8') if self.encryption: prepared_request = self.encryption.prepare_encrypted_request(self.session, self.endpoint, message) else: request = requests.Request('POST', self.endpoint, data=message) prepared_request = self.session.prepare_request(request) response = self._send_message_request(prepared_request, message) return self._get_message_response_text(response) def _send_message_request(self, prepared_request, message): try: response = self.session.send(prepared_request, timeout=self.read_timeout_sec) response.raise_for_status() return response except requests.HTTPError as ex: if ex.response.status_code == 401: raise InvalidCredentialsError("the specified credentials were rejected by the server") if ex.response.content: response_text = self._get_message_response_text(ex.response) else: response_text = '' raise WinRMTransportError('http', ex.response.status_code, response_text) def _get_message_response_text(self, response): if self.encryption: response_text = self.encryption.parse_encrypted_response(response) else: response_text = response.content return response_text def _get_args(self, mandatory_args, optional_args, function): argspec = set(inspect.getargspec(function).args) function_args = dict() for name, value in mandatory_args.items(): if name in argspec: function_args[name] = value else: raise Exception("Function %s does not contain mandatory arg " "%s, check installed version with pip list" % (str(function), name)) for name, value in optional_args.items(): if name in argspec: function_args[name] = value else: warnings.warn("Function %s does not contain optional arg %s, " "check installed version with pip list" % (str(function), name)) return function_args
class Transport(object): def __init__( self, endpoint, username=None, password=None, realm=None, service=None, keytab=None, ca_trust_path=None, cert_pem=None, cert_key_pem=None, read_timeout_sec=None, server_cert_validation='validate', kerberos_delegation=False, kerberos_hostname_override=None, auth_method='auto', message_encryption='auto', credssp_disable_tlsv1_2=False): self.endpoint = endpoint self.username = username self.password = password self.realm = realm self.service = service self.keytab = keytab self.ca_trust_path = ca_trust_path self.cert_pem = cert_pem self.cert_key_pem = cert_key_pem self.read_timeout_sec = read_timeout_sec self.server_cert_validation = server_cert_validation self.kerberos_hostname_override = kerberos_hostname_override self.message_encryption = message_encryption self.credssp_disable_tlsv1_2 = credssp_disable_tlsv1_2 if self.server_cert_validation not in [None, 'validate', 'ignore']: raise WinRMError('invalid server_cert_validation mode: %s' % self.server_cert_validation) # defensively parse this to a bool if isinstance(kerberos_delegation, bool): self.kerberos_delegation = kerberos_delegation else: self.kerberos_delegation = bool(strtobool(str(kerberos_delegation))) self.auth_method = auth_method self.default_headers = { 'Content-Type': 'application/soap+xml;charset=UTF-8', 'User-Agent': 'Python WinRM client', } # try to suppress user-unfriendly warnings from requests' vendored urllib3 try: from requests.packages.urllib3.exceptions import InsecurePlatformWarning warnings.simplefilter('ignore', category=InsecurePlatformWarning) except: pass # oh well, we tried... try: from requests.packages.urllib3.exceptions import SNIMissingWarning warnings.simplefilter('ignore', category=SNIMissingWarning) except: pass # oh well, we tried... # if we're explicitly ignoring validation, try to suppress InsecureRequestWarning, since the user opted-in if self.server_cert_validation == 'ignore': try: from requests.packages.urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', category=InsecureRequestWarning) except: pass # oh well, we tried... # validate credential requirements for various auth types if self.auth_method != 'kerberos': if self.auth_method == 'certificate' or ( self.auth_method == 'ssl' and (self.cert_pem or self.cert_key_pem)): if not self.cert_pem or not self.cert_key_pem: raise InvalidCredentialsError("both cert_pem and cert_key_pem must be specified for cert auth") if not os.path.exists(self.cert_pem): raise InvalidCredentialsError("cert_pem file not found (%s)" % self.cert_pem) if not os.path.exists(self.cert_key_pem): raise InvalidCredentialsError("cert_key_pem file not found (%s)" % self.cert_key_pem) else: if not self.username: raise InvalidCredentialsError("auth method %s requires a username" % self.auth_method) if self.password is None: raise InvalidCredentialsError("auth method %s requires a password" % self.auth_method) self.session = None # Used for encrypting messages self.encryption = None # The Pywinrm Encryption class used to encrypt/decrypt messages if self.message_encryption not in ['auto', 'always', 'never']: raise WinRMError( "invalid message_encryption arg: %s. Should be 'auto', 'always', or 'never'" % self.message_encryption) def build_session(self): session = requests.Session() session.verify = self.server_cert_validation == 'validate' # configure proxies from HTTP/HTTPS_PROXY envvars session.trust_env = True settings = session.merge_environment_settings(url=self.endpoint, proxies={}, stream=None, verify=None, cert=None) # we're only applying proxies from env, other settings are ignored session.proxies = settings['proxies'] encryption_available = False if self.auth_method == 'kerberos': if not HAVE_KERBEROS: raise WinRMError("requested auth method is kerberos, but requests_kerberos is not installed") # TODO: do argspec sniffing on extensions to ensure we're not setting bogus kwargs on older versions session.auth = HTTPKerberosAuth(mutual_authentication=REQUIRED, delegate=self.kerberos_delegation, force_preemptive=True, principal=self.username, hostname_override=self.kerberos_hostname_override, sanitize_mutual_error_response=False) elif self.auth_method in ['certificate', 'ssl']: if self.auth_method == 'ssl' and not self.cert_pem and not self.cert_key_pem: # 'ssl' was overloaded for HTTPS with optional certificate auth, # fall back to basic auth if no cert specified session.auth = requests.auth.HTTPBasicAuth(username=self.username, password=self.password) else: session.cert = (self.cert_pem, self.cert_key_pem) session.headers['Authorization'] = \ "http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/https/mutual" elif self.auth_method == 'ntlm': if not HAVE_NTLM: raise WinRMError("requested auth method is ntlm, but requests_ntlm is not installed") session.auth = HttpNtlmAuth(username=self.username, password=self.password) # check if requests_ntlm has the session_security attribute available for encryption encryption_available = hasattr(session.auth, 'session_security') # TODO: ssl is not exactly right here- should really be client_cert elif self.auth_method in ['basic', 'plaintext']: session.auth = requests.auth.HTTPBasicAuth(username=self.username, password=self.password) elif self.auth_method == 'credssp': if not HAVE_CREDSSP: raise WinRMError("requests auth method is credssp, but requests-credssp is not installed") session.auth = HttpCredSSPAuth(username=self.username, password=self.password, disable_tlsv1_2=self.credssp_disable_tlsv1_2) encryption_available = hasattr(session.auth, 'wrap') and hasattr(session.auth, 'unwrap') else: raise WinRMError("unsupported auth method: %s" % self.auth_method) session.headers.update(self.default_headers) self.session = session # Will check the current config and see if we need to setup message encryption if self.message_encryption == 'always' and not encryption_available: raise WinRMError( "message encryption is set to 'always' but the selected auth method %s does not support it" % self.auth_method) elif encryption_available: if self.message_encryption == 'always': self.setup_encryption() elif self.message_encryption == 'auto' and not self.endpoint.lower().startswith('https'): self.setup_encryption() def setup_encryption(self): # Security context doesn't exist, sending blank message to initialise context request = requests.Request('POST', self.endpoint, data=None) prepared_request = self.session.prepare_request(request) self._send_message_request(prepared_request, '') self.encryption = Encryption(self.session, self.auth_method) def send_message(self, message): if not self.session: self.build_session() # urllib3 fails on SSL retries with unicode buffers- must send it a byte string # see https://github.com/shazow/urllib3/issues/717 if isinstance(message, unicode_type): message = message.encode('utf-8') if self.encryption: prepared_request = self.encryption.prepare_encrypted_request(self.session, self.endpoint, message) else: request = requests.Request('POST', self.endpoint, data=message) prepared_request = self.session.prepare_request(request) response = self._send_message_request(prepared_request, message) return self._get_message_response_text(response) def _send_message_request(self, prepared_request, message): try: response = self.session.send(prepared_request, timeout=self.read_timeout_sec) response.raise_for_status() return response except requests.HTTPError as ex: if ex.response.status_code == 401: raise InvalidCredentialsError("the specified credentials were rejected by the server") if ex.response.content: response_text = self._get_message_response_text(ex.response) else: response_text = '' # Per http://msdn.microsoft.com/en-us/library/cc251676.aspx rule 3, # should handle this 500 error and retry receiving command output. if b'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive' in message and b'Code="2150858793"' in response_text: raise WinRMOperationTimeoutError() error_message = 'Bad HTTP response returned from server. Code {0}'.format(ex.response.status_code) raise WinRMTransportError('http', error_message) def _get_message_response_text(self, response): if self.encryption: response_text = self.encryption.parse_encrypted_response(response) else: response_text = response.content return response_text
class Transport(object): def __init__( self, endpoint, username=None, password=None, realm=None, service=None, keytab=None, ca_trust_path=None, cert_pem=None, cert_key_pem=None, read_timeout_sec=None, server_cert_validation='validate', kerberos_delegation=False, kerberos_hostname_override=None, auth_method='auto', message_encryption='auto', credssp_disable_tlsv1_2=False, credssp_auth_mechanism='auto', credssp_minimum_version=2, send_cbt=True): self.endpoint = endpoint self.username = username self.password = password self.realm = realm self.service = service self.keytab = keytab self.ca_trust_path = ca_trust_path self.cert_pem = cert_pem self.cert_key_pem = cert_key_pem self.read_timeout_sec = read_timeout_sec self.server_cert_validation = server_cert_validation self.kerberos_hostname_override = kerberos_hostname_override self.message_encryption = message_encryption self.credssp_disable_tlsv1_2 = credssp_disable_tlsv1_2 self.credssp_auth_mechanism = credssp_auth_mechanism self.credssp_minimum_version = credssp_minimum_version self.send_cbt = send_cbt if self.server_cert_validation not in [None, 'validate', 'ignore']: raise WinRMError('invalid server_cert_validation mode: %s' % self.server_cert_validation) # defensively parse this to a bool if isinstance(kerberos_delegation, bool): self.kerberos_delegation = kerberos_delegation else: self.kerberos_delegation = bool(strtobool(str(kerberos_delegation))) self.auth_method = auth_method self.default_headers = { 'Content-Type': 'application/soap+xml;charset=UTF-8', 'User-Agent': 'Python WinRM client', } # try to suppress user-unfriendly warnings from requests' vendored urllib3 try: from requests.packages.urllib3.exceptions import InsecurePlatformWarning warnings.simplefilter('ignore', category=InsecurePlatformWarning) except: pass # oh well, we tried... try: from requests.packages.urllib3.exceptions import SNIMissingWarning warnings.simplefilter('ignore', category=SNIMissingWarning) except: pass # oh well, we tried... # if we're explicitly ignoring validation, try to suppress InsecureRequestWarning, since the user opted-in if self.server_cert_validation == 'ignore': try: from requests.packages.urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', category=InsecureRequestWarning) except: pass # oh well, we tried... try: from urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', category=InsecureRequestWarning) except: pass # oh well, we tried... # validate credential requirements for various auth types if self.auth_method != 'kerberos': if self.auth_method == 'certificate' or ( self.auth_method == 'ssl' and (self.cert_pem or self.cert_key_pem)): if not self.cert_pem or not self.cert_key_pem: raise InvalidCredentialsError("both cert_pem and cert_key_pem must be specified for cert auth") if not os.path.exists(self.cert_pem): raise InvalidCredentialsError("cert_pem file not found (%s)" % self.cert_pem) if not os.path.exists(self.cert_key_pem): raise InvalidCredentialsError("cert_key_pem file not found (%s)" % self.cert_key_pem) else: if not self.username: raise InvalidCredentialsError("auth method %s requires a username" % self.auth_method) if self.password is None: raise InvalidCredentialsError("auth method %s requires a password" % self.auth_method) self.session = None # Used for encrypting messages self.encryption = None # The Pywinrm Encryption class used to encrypt/decrypt messages if self.message_encryption not in ['auto', 'always', 'never']: raise WinRMError( "invalid message_encryption arg: %s. Should be 'auto', 'always', or 'never'" % self.message_encryption) def build_session(self): session = requests.Session() # 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) # get proxy settings from env # FUTURE: allow proxy to be passed in directly to supersede this value session.proxies = settings['proxies'] # 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 if self.auth_method == 'kerberos': if not HAVE_KERBEROS: raise WinRMError("requested auth method is kerberos, but requests_kerberos is not installed") man_args = dict( mutual_authentication=REQUIRED, ) opt_args = dict( delegate=self.kerberos_delegation, force_preemptive=True, principal=self.username, hostname_override=self.kerberos_hostname_override, sanitize_mutual_error_response=False, service=self.service, send_cbt=self.send_cbt ) kerb_args = self._get_args(man_args, opt_args, HTTPKerberosAuth.__init__) session.auth = HTTPKerberosAuth(**kerb_args) encryption_available = hasattr(session.auth, 'winrm_encryption_available') and session.auth.winrm_encryption_available elif self.auth_method in ['certificate', 'ssl']: if self.auth_method == 'ssl' and not self.cert_pem and not self.cert_key_pem: # 'ssl' was overloaded for HTTPS with optional certificate auth, # fall back to basic auth if no cert specified session.auth = requests.auth.HTTPBasicAuth(username=self.username, password=self.password) else: session.cert = (self.cert_pem, self.cert_key_pem) session.headers['Authorization'] = \ "http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/https/mutual" elif self.auth_method == 'ntlm': if not HAVE_NTLM: raise WinRMError("requested auth method is ntlm, but requests_ntlm is not installed") man_args = dict( username=self.username, password=self.password ) opt_args = dict( send_cbt=self.send_cbt ) ntlm_args = self._get_args(man_args, opt_args, HttpNtlmAuth.__init__) session.auth = HttpNtlmAuth(**ntlm_args) # check if requests_ntlm has the session_security attribute available for encryption encryption_available = hasattr(session.auth, 'session_security') # TODO: ssl is not exactly right here- should really be client_cert elif self.auth_method in ['basic', 'plaintext']: session.auth = requests.auth.HTTPBasicAuth(username=self.username, password=self.password) elif self.auth_method == 'credssp': if not HAVE_CREDSSP: raise WinRMError("requests auth method is credssp, but requests-credssp is not installed") man_args = dict( username=self.username, password=self.password ) opt_args = dict( disable_tlsv1_2=self.credssp_disable_tlsv1_2, auth_mechanism=self.credssp_auth_mechanism, minimum_version=self.credssp_minimum_version ) credssp_args = self._get_args(man_args, opt_args, HttpCredSSPAuth.__init__) session.auth = HttpCredSSPAuth(**credssp_args) encryption_available = True else: raise WinRMError("unsupported auth method: %s" % self.auth_method) session.headers.update(self.default_headers) self.session = session # Will check the current config and see if we need to setup message encryption if self.message_encryption == 'always' and not encryption_available: raise WinRMError( "message encryption is set to 'always' but the selected auth method %s does not support it" % self.auth_method) elif encryption_available: if self.message_encryption == 'always': self.setup_encryption() elif self.message_encryption == 'auto' and not self.endpoint.lower().startswith('https'): self.setup_encryption() def setup_encryption(self): # Security context doesn't exist, sending blank message to initialise context request = requests.Request('POST', self.endpoint, data=None) prepared_request = self.session.prepare_request(request) self._send_message_request(prepared_request, '') self.encryption = Encryption(self.session, self.auth_method) def send_message(self, message): if not self.session: self.build_session() # urllib3 fails on SSL retries with unicode buffers- must send it a byte string # see https://github.com/shazow/urllib3/issues/717 if isinstance(message, unicode_type): message = message.encode('utf-8') if self.encryption: prepared_request = self.encryption.prepare_encrypted_request(self.session, self.endpoint, message) else: request = requests.Request('POST', self.endpoint, data=message) prepared_request = self.session.prepare_request(request) response = self._send_message_request(prepared_request, message) return self._get_message_response_text(response) def _send_message_request(self, prepared_request, message): try: response = self.session.send(prepared_request, timeout=self.read_timeout_sec) response.raise_for_status() return response except requests.HTTPError as ex: if ex.response.status_code == 401: raise InvalidCredentialsError("the specified credentials were rejected by the server") if ex.response.content: response_text = self._get_message_response_text(ex.response) else: response_text = '' raise WinRMTransportError('http', ex.response.status_code, response_text) def _get_message_response_text(self, response): if self.encryption: response_text = self.encryption.parse_encrypted_response(response) else: response_text = response.content return response_text def _get_args(self, mandatory_args, optional_args, function): argspec = set(inspect.getargspec(function).args) function_args = dict() for name, value in mandatory_args.items(): if name in argspec: function_args[name] = value else: raise Exception("Function %s does not contain mandatory arg " "%s, check installed version with pip list" % (str(function), name)) for name, value in optional_args.items(): if name in argspec: function_args[name] = value else: warnings.warn("Function %s does not contain optional arg %s, " "check installed version with pip list" % (str(function), name)) return function_args