Merge pull request #70 from nitzmahone/principal_and_realm_override
add support for principal and realm override
This commit is contained in:
commit
d824b8c538
44
README.rst
44
README.rst
|
@ -65,7 +65,14 @@ authentication, you can do that as well:
|
||||||
Preemptive Authentication
|
Preemptive Authentication
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
``HTTPKerberosAuth`` can be forced to preemptively initiate the Kerberos GSS exchange and present a Kerberos ticket on the initial request (and all subsequent). By default, authentication only occurs after a ``401 Unauthorized`` response containing a Kerberos or Negotiate challenge is received from the origin server. This can cause mutual authentication failures for hosts that use a persistent connection (eg, Windows/WinRM), as no Kerberos challenges are sent after the initial auth handshake. This behavior can be altered by setting ``force_preemptive=True``:
|
``HTTPKerberosAuth`` can be forced to preemptively initiate the Kerberos
|
||||||
|
GSS exchange and present a Kerberos ticket on the initial request (and all
|
||||||
|
subsequent). By default, authentication only occurs after a
|
||||||
|
``401 Unauthorized`` response containing a Kerberos or Negotiate challenge
|
||||||
|
is received from the origin server. This can cause mutual authentication
|
||||||
|
failures for hosts that use a persistent connection (eg, Windows/WinRM), as
|
||||||
|
no Kerberos challenges are sent after the initial auth handshake. This
|
||||||
|
behavior can be altered by setting ``force_preemptive=True``:
|
||||||
|
|
||||||
.. code-block:: pycon
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
@ -75,6 +82,41 @@ Preemptive Authentication
|
||||||
>>> r = requests.get("https://windows.example.org/wsman", auth=kerberos_auth)
|
>>> r = requests.get("https://windows.example.org/wsman", auth=kerberos_auth)
|
||||||
...
|
...
|
||||||
|
|
||||||
|
Hostname Override
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
If communicating with a host whose DNS name doesn't match its
|
||||||
|
kerberos hostname (eg, behind a content switch or load balancer),
|
||||||
|
the hostname used for the Kerberos GSS exchange can be overridden by
|
||||||
|
setting the ``hostname_override`` arg:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> import requests
|
||||||
|
>>> from requests_kerberos import HTTPKerberosAuth, REQUIRED
|
||||||
|
>>> kerberos_auth = HTTPKerberosAuth(hostname_override="internalhost.local")
|
||||||
|
>>> r = requests.get("https://externalhost.example.org/", auth=kerberos_auth)
|
||||||
|
...
|
||||||
|
|
||||||
|
Explicit Principal
|
||||||
|
------------------
|
||||||
|
|
||||||
|
``HTTPKerberosAuth`` normally uses the default principal (ie, the user for
|
||||||
|
whom you last ran ``kinit`` or ``kswitch``, or an SSO credential if
|
||||||
|
applicable). However, an explicit principal can be specified, which will
|
||||||
|
cause Kerberos to look for a matching credential cache for the named user.
|
||||||
|
This feature depends on OS support for collection-type credential caches,
|
||||||
|
as well as working principal support in pykerberos (it is broken in many
|
||||||
|
builds). An explicit principal can be specified with the ``principal`` arg:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> import requests
|
||||||
|
>>> from requests_kerberos import HTTPKerberosAuth, REQUIRED
|
||||||
|
>>> kerberos_auth = HTTPKerberosAuth(principal="user@REALM")
|
||||||
|
>>> r = requests.get("http://example.org", auth=kerberos_auth)
|
||||||
|
...
|
||||||
|
|
||||||
Logging
|
Logging
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -84,13 +84,16 @@ class HTTPKerberosAuth(AuthBase):
|
||||||
object."""
|
object."""
|
||||||
def __init__(
|
def __init__(
|
||||||
self, mutual_authentication=REQUIRED,
|
self, mutual_authentication=REQUIRED,
|
||||||
service="HTTP", delegate=False, force_preemptive=False):
|
service="HTTP", delegate=False, force_preemptive=False,
|
||||||
|
principal=None, hostname_override=None):
|
||||||
self.context = {}
|
self.context = {}
|
||||||
self.mutual_authentication = mutual_authentication
|
self.mutual_authentication = mutual_authentication
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
self.pos = None
|
self.pos = None
|
||||||
self.service = service
|
self.service = service
|
||||||
self.force_preemptive = force_preemptive
|
self.force_preemptive = force_preemptive
|
||||||
|
self.principal = principal
|
||||||
|
self.hostname_override = hostname_override
|
||||||
|
|
||||||
def generate_request_header(self, response, host, is_preemptive=False):
|
def generate_request_header(self, response, host, is_preemptive=False):
|
||||||
"""
|
"""
|
||||||
|
@ -108,8 +111,15 @@ class HTTPKerberosAuth(AuthBase):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
kerb_stage = "authGSSClientInit()"
|
kerb_stage = "authGSSClientInit()"
|
||||||
result, self.context[host] = kerberos.authGSSClientInit(
|
# contexts still need to be stored by host, but hostname_override
|
||||||
"{0}@{1}".format(self.service, host), gssflags=gssflags)
|
# allows use of an arbitrary hostname for the kerberos exchange
|
||||||
|
# (eg, in cases of aliased hosts, internal vs external, CNAMEs
|
||||||
|
# w/ name-based HTTP hosting)
|
||||||
|
kerb_host = self.hostname_override if self.hostname_override is not None else host
|
||||||
|
kerb_spn = "{0}@{1}".format(self.service, kerb_host)
|
||||||
|
|
||||||
|
result, self.context[host] = kerberos.authGSSClientInit(kerb_spn,
|
||||||
|
gssflags=gssflags, principal=self.principal)
|
||||||
|
|
||||||
if result < 1:
|
if result < 1:
|
||||||
raise EnvironmentError(result, kerb_stage)
|
raise EnvironmentError(result, kerb_stage)
|
||||||
|
|
|
@ -119,7 +119,8 @@ class KerberosTestCase(unittest.TestCase):
|
||||||
"HTTP@www.example.org",
|
"HTTP@www.example.org",
|
||||||
gssflags=(
|
gssflags=(
|
||||||
kerberos.GSS_C_MUTUAL_FLAG |
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
kerberos.GSS_C_SEQUENCE_FLAG))
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal=None)
|
||||||
clientStep_continue.assert_called_with("CTX", "token")
|
clientStep_continue.assert_called_with("CTX", "token")
|
||||||
clientResponse.assert_called_with("CTX")
|
clientResponse.assert_called_with("CTX")
|
||||||
|
|
||||||
|
@ -140,7 +141,8 @@ class KerberosTestCase(unittest.TestCase):
|
||||||
"HTTP@www.example.org",
|
"HTTP@www.example.org",
|
||||||
gssflags=(
|
gssflags=(
|
||||||
kerberos.GSS_C_MUTUAL_FLAG |
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
kerberos.GSS_C_SEQUENCE_FLAG))
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal=None)
|
||||||
self.assertFalse(clientStep_continue.called)
|
self.assertFalse(clientStep_continue.called)
|
||||||
self.assertFalse(clientResponse.called)
|
self.assertFalse(clientResponse.called)
|
||||||
|
|
||||||
|
@ -161,7 +163,8 @@ class KerberosTestCase(unittest.TestCase):
|
||||||
"HTTP@www.example.org",
|
"HTTP@www.example.org",
|
||||||
gssflags=(
|
gssflags=(
|
||||||
kerberos.GSS_C_MUTUAL_FLAG |
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
kerberos.GSS_C_SEQUENCE_FLAG))
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal=None)
|
||||||
clientStep_error.assert_called_with("CTX", "token")
|
clientStep_error.assert_called_with("CTX", "token")
|
||||||
self.assertFalse(clientResponse.called)
|
self.assertFalse(clientResponse.called)
|
||||||
|
|
||||||
|
@ -205,7 +208,8 @@ class KerberosTestCase(unittest.TestCase):
|
||||||
"HTTP@www.example.org",
|
"HTTP@www.example.org",
|
||||||
gssflags=(
|
gssflags=(
|
||||||
kerberos.GSS_C_MUTUAL_FLAG |
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
kerberos.GSS_C_SEQUENCE_FLAG))
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal=None)
|
||||||
clientStep_continue.assert_called_with("CTX", "token")
|
clientStep_continue.assert_called_with("CTX", "token")
|
||||||
clientResponse.assert_called_with("CTX")
|
clientResponse.assert_called_with("CTX")
|
||||||
|
|
||||||
|
@ -249,7 +253,8 @@ class KerberosTestCase(unittest.TestCase):
|
||||||
"HTTP@www.example.org",
|
"HTTP@www.example.org",
|
||||||
gssflags=(
|
gssflags=(
|
||||||
kerberos.GSS_C_MUTUAL_FLAG |
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
kerberos.GSS_C_SEQUENCE_FLAG))
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal=None)
|
||||||
clientStep_continue.assert_called_with("CTX", "token")
|
clientStep_continue.assert_called_with("CTX", "token")
|
||||||
clientResponse.assert_called_with("CTX")
|
clientResponse.assert_called_with("CTX")
|
||||||
|
|
||||||
|
@ -480,7 +485,8 @@ class KerberosTestCase(unittest.TestCase):
|
||||||
"HTTP@www.example.org",
|
"HTTP@www.example.org",
|
||||||
gssflags=(
|
gssflags=(
|
||||||
kerberos.GSS_C_MUTUAL_FLAG |
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
kerberos.GSS_C_SEQUENCE_FLAG))
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal=None)
|
||||||
clientStep_continue.assert_called_with("CTX", "token")
|
clientStep_continue.assert_called_with("CTX", "token")
|
||||||
clientResponse.assert_called_with("CTX")
|
clientResponse.assert_called_with("CTX")
|
||||||
|
|
||||||
|
@ -529,7 +535,8 @@ class KerberosTestCase(unittest.TestCase):
|
||||||
"HTTP@www.example.org",
|
"HTTP@www.example.org",
|
||||||
gssflags=(
|
gssflags=(
|
||||||
kerberos.GSS_C_MUTUAL_FLAG |
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
kerberos.GSS_C_SEQUENCE_FLAG))
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal=None)
|
||||||
clientStep_continue.assert_called_with("CTX", "token")
|
clientStep_continue.assert_called_with("CTX", "token")
|
||||||
clientResponse.assert_called_with("CTX")
|
clientResponse.assert_called_with("CTX")
|
||||||
|
|
||||||
|
@ -548,7 +555,8 @@ class KerberosTestCase(unittest.TestCase):
|
||||||
"barfoo@www.example.org",
|
"barfoo@www.example.org",
|
||||||
gssflags=(
|
gssflags=(
|
||||||
kerberos.GSS_C_MUTUAL_FLAG |
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
kerberos.GSS_C_SEQUENCE_FLAG))
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal=None)
|
||||||
|
|
||||||
def test_delegation(self):
|
def test_delegation(self):
|
||||||
with patch.multiple('kerberos',
|
with patch.multiple('kerberos',
|
||||||
|
@ -591,10 +599,48 @@ class KerberosTestCase(unittest.TestCase):
|
||||||
gssflags=(
|
gssflags=(
|
||||||
kerberos.GSS_C_MUTUAL_FLAG |
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
kerberos.GSS_C_SEQUENCE_FLAG |
|
kerberos.GSS_C_SEQUENCE_FLAG |
|
||||||
kerberos.GSS_C_DELEG_FLAG))
|
kerberos.GSS_C_DELEG_FLAG),
|
||||||
|
principal=None
|
||||||
|
)
|
||||||
clientStep_continue.assert_called_with("CTX", "token")
|
clientStep_continue.assert_called_with("CTX", "token")
|
||||||
clientResponse.assert_called_with("CTX")
|
clientResponse.assert_called_with("CTX")
|
||||||
|
|
||||||
|
def test_principal_override(self):
|
||||||
|
with patch.multiple(kerberos_module_name,
|
||||||
|
authGSSClientInit=clientInit_complete,
|
||||||
|
authGSSClientResponse=clientResponse,
|
||||||
|
authGSSClientStep=clientStep_continue):
|
||||||
|
response = requests.Response()
|
||||||
|
response.url = "http://www.example.org/"
|
||||||
|
response.headers = {'www-authenticate': 'negotiate token'}
|
||||||
|
host = urlparse(response.url).hostname
|
||||||
|
auth = requests_kerberos.HTTPKerberosAuth(principal="user@REALM")
|
||||||
|
auth.generate_request_header(response, host),
|
||||||
|
clientInit_complete.assert_called_with(
|
||||||
|
"HTTP@www.example.org",
|
||||||
|
gssflags=(
|
||||||
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal="user@REALM")
|
||||||
|
|
||||||
|
def test_realm_override(self):
|
||||||
|
with patch.multiple(kerberos_module_name,
|
||||||
|
authGSSClientInit=clientInit_complete,
|
||||||
|
authGSSClientResponse=clientResponse,
|
||||||
|
authGSSClientStep=clientStep_continue):
|
||||||
|
response = requests.Response()
|
||||||
|
response.url = "http://www.example.org/"
|
||||||
|
response.headers = {'www-authenticate': 'negotiate token'}
|
||||||
|
host = urlparse(response.url).hostname
|
||||||
|
auth = requests_kerberos.HTTPKerberosAuth(hostname_override="otherhost.otherdomain.org")
|
||||||
|
auth.generate_request_header(response, host),
|
||||||
|
clientInit_complete.assert_called_with(
|
||||||
|
"HTTP@otherhost.otherdomain.org",
|
||||||
|
gssflags=(
|
||||||
|
kerberos.GSS_C_MUTUAL_FLAG |
|
||||||
|
kerberos.GSS_C_SEQUENCE_FLAG),
|
||||||
|
principal=None)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in New Issue