예제 #1
0
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"
예제 #2
0
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"
예제 #3
0
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"
예제 #4
0
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"
예제 #5
0
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
예제 #6
0
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
예제 #7
0
파일: transport.py 프로젝트: diyan/pywinrm
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