From 488b1aa66de1b07274be73fe9fbc19755694495f Mon Sep 17 00:00:00 2001 From: jzrk Date: Sat, 23 Jun 2018 10:28:37 +0200 Subject: [PATCH] Provide changing source IP address bind to network interface. --- setup.py | 2 +- winrm/__init__.py | 5 +++-- winrm/protocol.py | 4 ++++ winrm/transport.py | 9 +++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index e24516ad..47593593 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ license='MIT license', packages=('winrm', 'winrm.tests'), package_data={'winrm.tests': ['*.ps1']}, - install_requires=['xmltodict', 'requests>=2.9.1', 'requests_ntlm>=0.3.0', 'six'], + install_requires=['xmltodict', 'requests>=2.9.1', 'requests_ntlm>=0.3.0', 'requests-toolbelt>=0.8.0', 'six'], extras_require = dict(kerberos=['requests-kerberos>=0.10.0'], credssp=['requests-credssp>=0.0.1']), classifiers=[ 'Development Status :: 4 - Beta', diff --git a/winrm/__init__.py b/winrm/__init__.py index 56e55413..f6b1d929 100644 --- a/winrm/__init__.py +++ b/winrm/__init__.py @@ -26,11 +26,12 @@ def __repr__(self): class Session(object): # TODO implement context manager methods - def __init__(self, target, auth, **kwargs): + def __init__(self, target, auth, bind_to=None, **kwargs): username, password = auth self.url = self._build_url(target, kwargs.get('transport', 'plaintext')) self.protocol = Protocol(self.url, - username=username, password=password, **kwargs) + username=username, password=password, + bind_to=bind_to, **kwargs) def run_cmd(self, command, args=()): # TODO optimize perf. Do not call open/close shell every time diff --git a/winrm/protocol.py b/winrm/protocol.py index a645e4d6..a7b1de5f 100644 --- a/winrm/protocol.py +++ b/winrm/protocol.py @@ -32,6 +32,7 @@ def __init__( password=None, realm=None, service="HTTP", keytab=None, ca_trust_path=None, cert_pem=None, cert_key_pem=None, server_cert_validation='validate', + bind_to=None, kerberos_delegation=False, read_timeout_sec=DEFAULT_READ_TIMEOUT_SEC, operation_timeout_sec=DEFAULT_OPERATION_TIMEOUT_SEC, @@ -52,6 +53,7 @@ def __init__( @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 string bind_to: use it on the local machine as the source address of the connection. Note that IP address must be bind to network interface # 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 @@ -76,6 +78,7 @@ def __init__( self.operation_timeout_sec = operation_timeout_sec self.max_env_sz = Protocol.DEFAULT_MAX_ENV_SIZE self.locale = Protocol.DEFAULT_LOCALE + self.bind_to = bind_to self.transport = Transport( endpoint=endpoint, username=username, password=password, @@ -83,6 +86,7 @@ def __init__( 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, + bind_to=self.bind_to, kerberos_delegation=kerberos_delegation, kerberos_hostname_override=kerberos_hostname_override, auth_method=transport, diff --git a/winrm/transport.py b/winrm/transport.py index 430c7612..5dc4bccc 100644 --- a/winrm/transport.py +++ b/winrm/transport.py @@ -16,6 +16,7 @@ import requests.auth import warnings from distutils.util import strtobool +from requests_toolbelt.adapters.source import SourceAddressAdapter HAVE_KERBEROS = False try: @@ -57,6 +58,7 @@ 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', + bind_to=None, kerberos_delegation=False, kerberos_hostname_override=None, auth_method='auto', @@ -74,6 +76,7 @@ def __init__( self.cert_key_pem = cert_key_pem self.read_timeout_sec = read_timeout_sec self.server_cert_validation = server_cert_validation + self.bind_to = bind_to self.kerberos_hostname_override = kerberos_hostname_override self.message_encryption = message_encryption self.credssp_disable_tlsv1_2 = credssp_disable_tlsv1_2 @@ -147,6 +150,12 @@ def __init__( def build_session(self): session = requests.Session() + if self.bind_to: + # SourceAddressAdapter() allows to send requests from selected IP + # address. + session.mount('http://', SourceAddressAdapter(self.bind_to)) + session.mount('https://', SourceAddressAdapter(self.bind_to)) + # allow some settings to be merged from env session.trust_env = True settings = session.merge_environment_settings(url=self.endpoint, proxies={}, stream=None,