def send_message(self, message): # TODO support kerberos/ntlm session with message encryption if not self.session: 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') request = requests.Request('POST', self.endpoint, data=message) prepared_request = self.session.prepare_request(request) try: response = self.session.send(prepared_request, timeout=self.read_timeout_sec) response_text = response.text response.raise_for_status() return response_text 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 = ex.response.content 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 fix_send_message(self, message): """Hacking from winrm.transport.Transport.send_message For adding detailed error message """ if not self.session: 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, type(u'')): message = message.encode('utf-8') request = requests.Request('POST', self.endpoint, data=message) prepared_request = self.session.prepare_request(request) try: response = self.session.send(prepared_request, timeout=self.read_timeout_sec) response_text = response.text response.raise_for_status() return response_text 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 = ex.response.content 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: LOG.debug( 'Receiving cmd result from %s but exceed operation timeout, keep retry', ex.request.url) raise WinRMOperationTimeoutError() # hack start # 2150858793 Operation timeout # 2147943418 Illegal operation attempted on a registry key that has been marked for deletion. # 2147746132 Class not registered LOG.error('Send message [%s] to [%s] get HTTP code [%s] [%s]', message, self.endpoint, ex.response.status_code, response_text) err_code, err_msg = parse_error_response(response_text) if err_code == "2150858793": # raise EPASWinRMOperationTimeout(err_msg) raise OSCEWinRMOperationTimeout(err_msg) elif err_code == "2147943418": # raise EPASWinRMIllegalOperation(err_msg) raise OSCEWinRMIllegalOperation(err_msg) elif err_code == "2147746132": # raise EPASWinRMClassNotRegister(err_msg) raise OSCEWinRMClassNotRegister(err_msg) else: # raise EPASWinRMTransportException(ex.response.status_code, err_code, err_msg) raise OSCEWinRMTransportException(ex.response.status_code, err_code, err_msg)
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 send_message(self, message): # TODO support kerberos/ntlm session with message encryption if not self.session: 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') request = requests.Request('POST', self.endpoint, data=message) prepared_request = self.session.prepare_request(request) try: response = self.session.send(prepared_request, timeout=self.read_timeout_sec) response_text = response.text response.raise_for_status() return response_text 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 = ex.response.content 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) import xml.etree.ElementTree as ET if response_text: root = ET.fromstring(response_text) ns = {'s': "http://www.w3.org/2003/05/soap-envelope", 'a': "http://schemas.xmlsoap.org/ws/2004/08/addressing", 'w': "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"} for text in root.findall('s:Body/s:Fault/s:Reason/s:Text', ns): error_message += "\n%s" % text.text raise WinRMTransportError('http', error_message)
def send_message(self, message): # TODO support kerberos session with message encryption if not self.session: self.session = self.build_session() request = requests.Request('POST', self.endpoint, data=message) prepared_request = self.session.prepare_request(request) try: response = self.session.send(prepared_request, verify=False, timeout=self.timeout) response.raise_for_status() # Version 1.1 of WinRM adds the namespaces in the document instead of the envelope so we have to # add them ourselves here. This should have no affect version 2. response_text = response.text return response_text except requests.HTTPError as ex: if ex.response.status_code == 401: server_auth = ex.response.headers['WWW-Authenticate'].lower() client_auth = list(self.session.auth.auth_map.keys()) # Client can do only the Basic auth but server can not if 'basic' not in server_auth and len(client_auth) == 1 \ and client_auth[0] == 'basic': raise BasicAuthDisabledError() # Both client and server can do a Basic auth if 'basic' in server_auth and 'basic' in client_auth: raise InvalidCredentialsError() if ex.response: response_text = ex.response.content # Is this just silencing the error? 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 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive' in message and 'Code="2150858793"' in response_text: # TODO raise TimeoutError here instead of just return text return response_text error_message = 'Bad HTTP response returned from server. Code {0}'.format( ex.response.status_code) # if ex.msg: # error_message += ', {0}'.format(ex.msg) raise WinRMError('http', error_message)
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 __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'): 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 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, SNIMissingWarning, InsecureRequestWarning warnings.simplefilter('ignore', category=InsecurePlatformWarning) warnings.simplefilter('ignore', category=SNIMissingWarning) # if we're explicitly ignoring validation, try to suppress InsecureRequestWarning, since the user opted-in if self.server_cert_validation == 'ignore': 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
def build_session(self): if self.server_cert_validation == 'ignore': # if we're explicitly ignoring validation, try to suppress requests' vendored urllib3 InsecureRequestWarning try: from requests.packages.urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', category=InsecureRequestWarning) except: # oh well, we tried... pass 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'] 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.realm) 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: # client cert auth, validate accordingly 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) 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") if not self.username: raise InvalidCredentialsError("auth method ntlm requires a username") if not self.password: raise InvalidCredentialsError("auth method ntlm requires a password") session.auth = HttpNtlmAuth(username=self.username, password=self.password) # TODO: ssl is not exactly right here- should really be client_cert elif self.auth_method in ['basic','plaintext']: if not self.username: raise InvalidCredentialsError("auth method basic requires a username") if not self.password: raise InvalidCredentialsError("auth method basic requires a password") session.auth = requests.auth.HTTPBasicAuth(username=self.username, password=self.password) else: raise WinRMError("unsupported auth method: %s" % self.auth_method) session.headers.update(self.default_headers) return session