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'] 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) # 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) else: raise WinRMError("unsupported auth method: %s" % self.auth_method) session.headers.update(self.default_headers) return session
def _decrypt_response(self, response, host): parts = response.content.split(self.MIME_BOUNDARY + b'\r\n') parts = list(filter(None, parts)) # filter out empty parts of the split message = b'' for i in range(0, len(parts)): if i % 2 == 1: continue header = parts[i].strip() payload = parts[i + 1] expected_length = int(header.split(b'Length=')[1]) # remove the end MIME block if it exists if payload.endswith(self.MIME_BOUNDARY + b'--\r\n'): payload = payload[:len(payload) - 24] encrypted_data = payload.replace(b'\tContent-Type: application/octet-stream\r\n', b'') decrypted_message = self._decrypt_message(encrypted_data, host) actual_length = len(decrypted_message) if actual_length != expected_length: raise WinRMError('Encrypted length from server does not match the ' 'expected size, message has been tampered with') message += decrypted_message return 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, 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 if self.server_cert_validation not in [None, 'validate', 'ignore']: raise WinRMError('invalid server_cert_validation mode: %s' % self.server_cert_validation) self.kerberos_delegation = kerberos_delegation self.auth_method = auth_method self.default_headers = { 'Content-Type': 'application/soap+xml;charset=UTF-8', 'User-Agent': 'Python WinRM client', } self.session = None
def send_message(self, message): # TODO support kerberos 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 message is unicode: 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 'http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive' in message and 'Code="2150858793"' in response_text: raise WinRMOperationTimeoutError() error_message = 'Bad HTTP response returned from server. Code {0}'.format(ex.response.status_code) raise WinRMError('http', error_message)
def send_message(self, message): # TODO add message_id vs relates_to checking # TODO port error handling code try: resp = self.transport.send_message(message) return resp except WinRMTransportError as ex: try: # if response is XML-parseable, it's probably a SOAP fault; extract the details root = ET.fromstring(ex.response_text) except Exception: # assume some other transport error; raise the original exception raise ex fault = root.find('soapenv:Body/soapenv:Fault', xmlns) if fault is not None: fault_data = dict(transport_message=ex.message, http_status_code=ex.code) wsmanfault_code = fault.find( 'soapenv:Detail/wsmanfault:WSManFault[@Code]', xmlns) if wsmanfault_code is not None: fault_data['wsmanfault_code'] = wsmanfault_code.get('Code') # convert receive timeout code to WinRMOperationTimeoutError if fault_data['wsmanfault_code'] == '2150858793': # TODO: this fault code is specific to the Receive operation; convert all op timeouts? raise WinRMOperationTimeoutError() fault_code = fault.find('soapenv:Code/soapenv:Value', xmlns) if fault_code is not None: fault_data['fault_code'] = fault_code.text fault_subcode = fault.find( 'soapenv:Code/soapenv:Subcode/soapenv:Value', xmlns) if fault_subcode is not None: fault_data['fault_subcode'] = fault_subcode.text error_message = fault.find('soapenv:Reason/soapenv:Text', xmlns) if error_message is not None: error_message = error_message.text else: error_message = "(no error message in fault)" raise WinRMError('{0} (extended fault data: {1})'.format( error_message, fault_data))
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 __init__(self, session, protocol): """ [MS-WSMV] v30.0 2016-07-14 2.2.9.1 Encrypted Message Types When using Encryption, there are three options available 1. Negotiate/SPNEGO 2. Kerberos 3. CredSSP Details for each implementation can be found in this document under this section This init sets the following values to use to encrypt and decrypt. This is to help generify the methods used in the body of the class. wrap: A method that will return the encrypted message and a signature unwrap: A method that will return an unencrypted message and verify the signature protocol_string: The protocol string used for the particular auth protocol :param session: The handle of the session to get GSS-API wrap and unwrap methods :param protocol: The auth protocol used, will determine the wrapping and unwrapping method plus the protocol string to use. Currently only NTLM and CredSSP is supported """ self.protocol = protocol self.session = session if protocol == 'ntlm': # Details under Negotiate [2.2.9.1.1] in MS-WSMV self.protocol_string = b"application/HTTP-SPNEGO-session-encrypted" self._build_message = self._build_ntlm_message self._decrypt_message = self._decrypt_ntlm_message elif protocol == 'credssp': # Details under CredSSP [2.2.9.1.3] in MS-WSMV self.protocol_string = b"application/HTTP-CredSSP-session-encrypted" self._build_message = self._build_credssp_message self._decrypt_message = self._decrypt_credssp_message elif protocol == 'kerberos': self.protocol_string = b"application/HTTP-SPNEGO-session-encrypted" self._build_message = self._build_kerberos_message self._decrypt_message = self._decrypt_kerberos_message else: raise WinRMError( "Encryption for protocol '%s' not supported in pywinrm" % protocol)
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): 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 __init__( self, endpoint, transport='plaintext', username=None, password=None, realm=None, service="HTTP", keytab=None, ca_trust_path='legacy_requests', cert_pem=None, cert_key_pem=None, server_cert_validation='validate', kerberos_delegation=False, read_timeout_sec=DEFAULT_READ_TIMEOUT_SEC, operation_timeout_sec=DEFAULT_OPERATION_TIMEOUT_SEC, kerberos_hostname_override=None, message_encryption='auto', credssp_disable_tlsv1_2=False, send_cbt=True, proxy='legacy_requests', ): """ @param string endpoint: the WinRM webservice endpoint @param string transport: transport type, one of 'plaintext' (default), 'kerberos', 'ssl', 'ntlm', 'credssp' # NOQA @param string username: username @param string password: password @param string realm: unused @param string service: the service name, default is HTTP @param string keytab: the path to a keytab file if you are using one @param string ca_trust_path: Certification Authority trust path. If server_cert_validation is set to 'validate': 'legacy_requests'(default) to use environment variables, None to explicitly disallow any additional CA trust path Any other value will be considered the CA trust path to use. @param string cert_pem: client authentication certificate file path in PEM format # NOQA @param string cert_key_pem: client authentication certificate key file path in PEM format # NOQA @param string server_cert_validation: whether server certificate should be validated on Python versions that suppport it; one of 'validate' (default), 'ignore' #NOQA @param bool kerberos_delegation: if True, TGT is sent to target server to allow multiple hops # NOQA @param int read_timeout_sec: maximum seconds to wait before an HTTP connect/read times out (default 30). This value should be slightly higher than operation_timeout_sec, as the server can block *at least* that long. # NOQA @param int operation_timeout_sec: maximum allowed time in seconds for any single wsman HTTP operation (default 20). Note that operation timeouts while receiving output (the only wsman operation that should take any significant time, and where these timeouts are expected) will be silently retried indefinitely. # NOQA @param string kerberos_hostname_override: the hostname to use for the kerberos exchange (defaults to the hostname in the endpoint URL) @param bool message_encryption_enabled: Will encrypt the WinRM messages if set to True and the transport auth supports message encryption (Default True). @param string proxy: Specify a proxy for the WinRM connection to use. 'legacy_requests'(default) to use environment variables, None to disable proxies completely or the proxy URL itself. """ try: read_timeout_sec = int(read_timeout_sec) except ValueError as ve: raise ValueError("failed to parse read_timeout_sec as int: %s" % str(ve)) try: operation_timeout_sec = int(operation_timeout_sec) except ValueError as ve: raise ValueError( "failed to parse operation_timeout_sec as int: %s" % str(ve)) if operation_timeout_sec >= read_timeout_sec or operation_timeout_sec < 1: raise WinRMError( "read_timeout_sec must exceed operation_timeout_sec, and both must be non-zero" ) self.read_timeout_sec = read_timeout_sec self.operation_timeout_sec = operation_timeout_sec self.max_env_sz = Protocol.DEFAULT_MAX_ENV_SIZE self.locale = Protocol.DEFAULT_LOCALE self.transport = Transport( endpoint=endpoint, username=username, password=password, realm=realm, service=service, keytab=keytab, ca_trust_path=ca_trust_path, cert_pem=cert_pem, cert_key_pem=cert_key_pem, read_timeout_sec=self.read_timeout_sec, server_cert_validation=server_cert_validation, kerberos_delegation=kerberos_delegation, kerberos_hostname_override=kerberos_hostname_override, auth_method=transport, message_encryption=message_encryption, credssp_disable_tlsv1_2=credssp_disable_tlsv1_2, send_cbt=send_cbt, proxy=proxy, ) self.username = username self.password = password self.service = service self.keytab = keytab self.ca_trust_path = ca_trust_path self.server_cert_validation = server_cert_validation self.kerberos_delegation = kerberos_delegation self.kerberos_hostname_override = kerberos_hostname_override self.credssp_disable_tlsv1_2 = credssp_disable_tlsv1_2
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 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 __init__( self, endpoint, transport='plaintext', username=None, password=None, realm=None, service=None, keytab=None, ca_trust_path=None, cert_pem=None, cert_key_pem=None, server_cert_validation='validate', kerberos_delegation=False, read_timeout_sec=DEFAULT_READ_TIMEOUT_SEC, operation_timeout_sec=DEFAULT_OPERATION_TIMEOUT_SEC, kerberos_hostname_override=None, ): """ @param string endpoint: the WinRM webservice endpoint @param string transport: transport type, one of 'plaintext' (default), 'kerberos', 'ssl', 'ntlm', 'credssp' # NOQA @param string username: username @param string password: password @param string realm: unused @param string service: the service name, default is HTTP @param string keytab: the path to a keytab file if you are using one @param string ca_trust_path: Certification Authority trust path @param string cert_pem: client authentication certificate file path in PEM format # NOQA @param string cert_key_pem: client authentication certificate key file path in PEM format # NOQA @param string server_cert_validation: whether server certificate should be validated on Python versions that suppport it; one of 'validate' (default), 'ignore' #NOQA @param bool kerberos_delegation: if True, TGT is sent to target server to allow multiple hops # NOQA @param int read_timeout_sec: maximum seconds to wait before an HTTP connect/read times out (default 30). This value should be slightly higher than operation_timeout_sec, as the server can block *at least* that long. # NOQA @param int operation_timeout_sec: maximum allowed time in seconds for any single wsman HTTP operation (default 20). Note that operation timeouts while receiving output (the only wsman operation that should take any significant time, and where these timeouts are expected) will be silently retried indefinitely. # NOQA @param string kerberos_hostname_override: the hostname to use for the kerberos exchange (defaults to the hostname in the endpoint URL) """ if operation_timeout_sec >= read_timeout_sec or operation_timeout_sec < 1: raise WinRMError( "read_timeout_sec must exceed operation_timeout_sec, and both must be non-zero" ) self.read_timeout_sec = read_timeout_sec self.operation_timeout_sec = operation_timeout_sec self.max_env_sz = Protocol.DEFAULT_MAX_ENV_SIZE self.locale = Protocol.DEFAULT_LOCALE self.transport = Transport( endpoint=endpoint, username=username, password=password, realm=realm, service=service, keytab=keytab, ca_trust_path=ca_trust_path, cert_pem=cert_pem, cert_key_pem=cert_key_pem, read_timeout_sec=self.read_timeout_sec, server_cert_validation=server_cert_validation, kerberos_delegation=kerberos_delegation, kerberos_hostname_override=kerberos_hostname_override, auth_method=transport) self.username = username self.password = password self.service = service self.keytab = keytab self.ca_trust_path = ca_trust_path self.server_cert_validation = server_cert_validation self.kerberos_delegation = kerberos_delegation self.kerberos_hostname_override = kerberos_hostname_override
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