Skip to content

Commit

Permalink
Add support for IPv6 and kix expired Kerb ticket issues
Browse files Browse the repository at this point in the history
  • Loading branch information
jborean93 committed Feb 23, 2019
1 parent e16ac49 commit 46e37ea
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 10 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
* Fix issue where `negotiate_delegate=True` did nothing with `pywin32` on Windows
* Fix instances of invalid escape sequences in strings that will break in future Python versions - https://bugs.python.org/issue27364
* Added warning if requests version is older than 2.14.0 as it does not support status retries. Pypsrp will continue but without supporting status retries.
* Fix byte orderring for the PID and RPID values of each PSRP message. This should not be an existing issue on normal hosts but it will make the move to SSH easier in the future
* Fix byte ordering for the PID and RPID values of each PSRP message. This should not be an existing issue on normal hosts but it will make the move to SSH easier in the future
* Support using a direct IPv6 address as the server name
* Manually get Kerberos ticket if the one in the cache has expired and the password is set
* Added explicit documentation to state that on MacOS/Heimdal KRB5 implementations, the Kerberos ticket will persist after running


## 0.3.0 - 2018-11-14
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ These are the options that can be used to setup `WSMan`;
* `operation_timeout`: The operation timeout, in seconds, of each WSMan operation, default is `20`. This should always be lower than `read_timeout`.
* `port`: The port to connect to, default is `5986` if `ssl=True` else `5985`
* `username`: The username to connect with, required for all auths except `certificate` and optionally required for `negotiate/kerberos`
* `password`: The password for `username`
* `password`: The password for `username`. Due to a bug on MacOS/Heimdal GSSAPI implementations, this will persist in the user's ccache when using Negotiate or Kerberos authentication, run `kdestroy` manually to remove this
* `ssl`: Whether to connect over `https` or `https`, default is `True`
* `path`: The WinRM path to connect to, default is `wsman`
* `auth`: The authentication protocol to use, default is `negotiate`, choices are `basic`, `certificate`, `negotiate`, `ntlm`, `kerberos`, `credssp`
Expand Down
8 changes: 5 additions & 3 deletions pypsrp/spgnego.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,11 +408,13 @@ def _get_security_context(name_type, mech, spn, username, password,
try:
cred = gssapi.Credentials(name=username, usage='initiate',
mechs=[mech])
except gssapi.exceptions.GSSError as err:
# raises ExpiredCredentialsError if it has expired
cred.lifetime
except gssapi.raw.GSSError:
# we can't acquire the cred if no password was supplied
if password is None:
raise err
pass
raise
cred = None
elif username is None or password is None:
raise ValueError("Can only use implicit credentials with kerberos "
"authentication")
Expand Down
22 changes: 19 additions & 3 deletions pypsrp/wsman.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from __future__ import division

import ipaddress
import logging
import requests
import sys
Expand All @@ -15,7 +16,7 @@
from pypsrp.exceptions import AuthenticationError, WinRMError, \
WinRMTransportError, WSManFaultError
from pypsrp.negotiate import HTTPNegotiateAuth
from pypsrp._utils import to_string, get_hostname
from pypsrp._utils import to_string, to_unicode, get_hostname

HAS_CREDSSP = True
CREDSSP_IMP_ERR = None
Expand Down Expand Up @@ -688,8 +689,8 @@ def __init__(self, server, port=None, username=None, password=None,
for kwarg in kwarg_list:
setattr(self, kwarg, kwargs.get(kwarg, None))

self.endpoint = "%s://%s:%d/%s" \
% ("https" if ssl else "http", server, self.port, path)
self.endpoint = self._create_endpoint(self.ssl, self.server, self.port,
self.path)
log.debug("Initialising HTTP transport for endpoint: %s, auth: %s, "
"user: %s" % (self.endpoint, self.username, self.auth))
self.session = None
Expand Down Expand Up @@ -926,3 +927,18 @@ def _suppress_library_warnings(self):
category=InsecureRequestWarning)
except: # pragma: no cover
pass

@staticmethod
def _create_endpoint(ssl, server, port, path):
scheme = "https" if ssl else "http"

# Check if the server is an IPv6 Address, enclose in [] if it is
try:
address = ipaddress.IPv6Address(to_unicode(server))
except ipaddress.AddressValueError:
pass
else:
server = "[%s]" % address.compressed

endpoint = "%s://%s:%s/%s" % (scheme, server, port, path)
return endpoint
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
':python_version<"2.7"': [
'lxml<4.3.0', # 4.3.0+ has dropped support for Python 2.6
],
':python_version<="2.7"': [
'ipaddress',
],
'credssp': [
'requests-credssp>=1.0.0'
],
Expand Down
2 changes: 1 addition & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def _get_client(self, wsman):
# the connection object was already created as part of test fixture
# we need to apply it to the Client object and set the values so the
# test will work with existing responses
client = Client(None)
client = Client("")
client.wsman = wsman
return client

Expand Down
22 changes: 21 additions & 1 deletion tests/test_wsman.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def test_wsman_defaults(self):
assert isinstance(actual.session_id, str)

# verify we get a unique session id each time this is initialised
new_wsman = WSMan(_TransportTest())
new_wsman = WSMan("")
assert actual.session_id != new_wsman.session_id

def test_override_default(self):
Expand Down Expand Up @@ -1278,3 +1278,23 @@ def test_send_winrm_encrypted_multiple(self, monkeypatch):
assert unwrap_mock.call_count == 1
assert unwrap_mock.call_args[0] == (b"content", "server")
assert unwrap_mock.call_args[1] == {}

@pytest.mark.parametrize('ssl, server, port, path, expected', [
[True, 'server', 5986, 'wsman', 'https://server:5986/wsman'],
[False, 'server', 5985, 'wsman', 'http://server:5985/wsman'],
[False, 'server', 5985, 'iis-wsman', 'http://server:5985/iis-wsman'],
[True, '127.0.0.1', 443, 'wsman', 'https://127.0.0.1:443/wsman'],
[False, '2001:0db8:0a0b:12f0:0000:0000:0000:0001', 80, 'path',
'http://[2001:db8:a0b:12f0::1]:80/path'],
[False, '2001:db8:a0b:12f0::1', 80, 'path',
'http://[2001:db8:a0b:12f0::1]:80/path'],
[False, '2001:0db8:0a0b:12f0:0001:0001:0001:0001', 5985, 'wsman',
'http://[2001:db8:a0b:12f0:1:1:1:1]:5985/wsman'],
[False, 'FE80::0202:B3FF:FE1E:8329', 5985, 'wsman',
'http://[fe80::202:b3ff:fe1e:8329]:5985/wsman'],
[True, '[2001:0db8:0a0b:12f0:0000:0000:0000:0001]', 5986, 'wsman',
'https://[2001:0db8:0a0b:12f0:0000:0000:0000:0001]:5986/wsman'],
])
def test_endpoint_forms(self, ssl, server, port, path, expected):
actual = _TransportHTTP._create_endpoint(ssl, server, port, path)
assert actual == expected

0 comments on commit 46e37ea

Please sign in to comment.