Skip to content

Fix up the interface we present to mention SPNEGO #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 14, 2017
71 changes: 39 additions & 32 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ requests GSSAPI authentication library

Requests is an HTTP library, written in Python, for human beings. This library
adds optional GSSAPI authentication support and supports mutual
authentication. Basic GET usage:
authentication.

It provides a fully backward-compatible shim for the old
python-requests-kerberos library: simply replace ``import requests_kerberos``
with ``import requests_gssapi``. A more powerful interface is provided by the
HTTPSPNEGOAuth component, but this is of course not guaranteed to be
compatible. Documentation below is written toward the new interface.

Basic GET usage:


.. code-block:: python

>>> import requests
>>> from requests_gssapi import HTTPKerberosAuth
>>> r = requests.get("http://example.org", auth=HTTPKerberosAuth())
>>> from requests_gssapi import HTTPSPNEGOAuth
>>> r = requests.get("http://example.org", auth=HTTPSPNEGOAuth())
...

The entire ``requests.api`` should be supported.
Expand All @@ -27,7 +35,7 @@ Mutual Authentication
REQUIRED
^^^^^^^^

By default, ``HTTPKerberosAuth`` will require mutual authentication from the
By default, ``HTTPSPNEGOAuth`` will require mutual authentication from the
server, and if a server emits a non-error response which cannot be
authenticated, a ``requests_gssapi.errors.MutualAuthenticationError`` will
be raised. If a server emits an error which cannot be authenticated, it will
Expand All @@ -39,8 +47,8 @@ setting ``sanitize_mutual_error_response=False``:
.. code-block:: python

>>> import requests
>>> from requests_gssapi import HTTPKerberosAuth, REQUIRED
>>> gssapi_auth = HTTPKerberosAuth(mutual_authentication=REQUIRED, sanitize_mutual_error_response=False)
>>> from requests_gssapi import HTTPSPNEGOAuth, REQUIRED
>>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=REQUIRED, sanitize_mutual_error_response=False)
>>> r = requests.get("https://windows.example.org/wsman", auth=gssapi_auth)
...

Expand All @@ -49,13 +57,13 @@ OPTIONAL
^^^^^^^^

If you'd prefer to not require mutual authentication, you can set your
preference when constructing your ``HTTPKerberosAuth`` object:
preference when constructing your ``HTTPSPNEGOAuth`` object:

.. code-block:: python

>>> import requests
>>> from requests_gssapi import HTTPKerberosAuth, OPTIONAL
>>> gssapi_auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
>>> from requests_gssapi import HTTPSPNEGOAuth, OPTIONAL
>>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=OPTIONAL)
>>> r = requests.get("http://example.org", auth=gssapi_auth)
...

Expand All @@ -72,28 +80,28 @@ authentication, you can do that as well:
.. code-block:: python

>>> import requests
>>> from requests_gssapi import HTTPKerberosAuth, DISABLED
>>> gssapi_auth = HTTPKerberosAuth(mutual_authentication=DISABLED)
>>> from requests_gssapi import HTTPSPNEGOAuth, DISABLED
>>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=DISABLED)
>>> r = requests.get("http://example.org", auth=gssapi_auth)
...

Preemptive Authentication
-------------------------
Opportunistic Authentication
----------------------------

``HTTPKerberosAuth`` can be forced to preemptively initiate the GSSAPI
``HTTPSPNEGOAuth`` can be forced to preemptively initiate the GSSAPI
exchange and present a token on the initial request (and all
subsequent). By default, authentication only occurs after a
``401 Unauthorized`` response containing a 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 GSSAPI challenges are sent after the initial auth handshake. This
behavior can be altered by setting ``force_preemptive=True``:
behavior can be altered by setting ``opportunistic_auth=True``:

.. code-block:: python

>>> import requests
>>> from requests_gssapi import HTTPKerberosAuth, REQUIRED
>>> gssapi_auth = HTTPKerberosAuth(mutual_authentication=REQUIRED, force_preemptive=True)
>>> from requests_gssapi import HTTPSPNEGOAuth, REQUIRED
>>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=REQUIRED, opportunistic_authentication=True)
>>> r = requests.get("https://windows.example.org/wsman", auth=gssapi_auth)
...

Expand All @@ -103,31 +111,30 @@ Hostname Override
If communicating with a host whose DNS name doesn't match its
hostname (eg, behind a content switch or load balancer),
the hostname used for the GSSAPI exchange can be overridden by
setting the ``hostname_override`` arg:
passing in a custom name (string or ``gssapi.Name``):

.. code-block:: python

>>> import requests
>>> from requests_gssapi import HTTPKerberosAuth, REQUIRED
>>> gssapi_auth = HTTPKerberosAuth(hostname_override="internalhost.local")
>>> r = requests.get("https://externalhost.example.org/", auth=kerberos_auth)
>>> from requests_gssapi import HTTPSPNEGOAuth, REQUIRED
>>> gssapi_auth = HTTPSPNEGOAuth(target_name="internalhost.local")
>>> r = requests.get("https://externalhost.example.org/", auth=gssapi_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 GSSAPI to look for a matching credential cache for the named user.
This feature depends on OS support for collection-type credential caches.
An explicit principal can be specified with the ``principal`` arg:
``HTTPSPNEGOAuth`` 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 credential can be in instead, if desired.

.. code-block:: python

>>> import gssapi
>>> import requests
>>> from requests_gssapi import HTTPKerberosAuth, REQUIRED
>>> gssapi_auth = HTTPKerberosAuth(principal="user@REALM")
>>> from requests_gssapi import HTTPSPNEGOAuth, REQUIRED
>>> creds = gssapi.Credentials(name=gssapi.Name("user@REALM"), usage="initiate")
>>> gssapi_auth = HTTPSPNEGOAuth(creds=creds)
>>> r = requests.get("http://example.org", auth=gssapi_auth)
...

Expand All @@ -136,13 +143,13 @@ Delegation

``requests_gssapi`` supports credential delegation (``GSS_C_DELEG_FLAG``).
To enable delegation of credentials to a server that requests delegation, pass
``delegate=True`` to ``HTTPKerberosAuth``:
``delegate=True`` to ``HTTPSPNEGOAuth``:

.. code-block:: python

>>> import requests
>>> from requests_gssapi import HTTPKerberosAuth
>>> r = requests.get("http://example.org", auth=HTTPKerberosAuth(delegate=True))
>>> from requests_gssapi import HTTPSPNEGOAuth
>>> r = requests.get("http://example.org", auth=HTTPSPNEGOAuth(delegate=True))
...

Be careful to only allow delegation to servers you trust as they will be able
Expand Down
12 changes: 6 additions & 6 deletions requests_gssapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
authentication. Basic GET usage:

>>> import requests
>>> from requests_gssapi import HTTPKerberosAuth
>>> r = requests.get("http://example.org", auth=HTTPKerberosAuth())
>>> from requests_gssapi import HTTPSPNEGOAuth
>>> r = requests.get("http://example.org", auth=HTTPSPNEGOAuth())

The entire `requests.api` should be supported.
"""
import logging

from .gssapi_ import HTTPKerberosAuth, REQUIRED, OPTIONAL, DISABLED
from .gssapi_ import HTTPSPNEGOAuth, REQUIRED, OPTIONAL, DISABLED # noqa
from .exceptions import MutualAuthenticationError
from .compat import NullHandler
from .compat import NullHandler, HTTPKerberosAuth

logging.getLogger(__name__).addHandler(NullHandler())

__all__ = ('HTTPKerberosAuth', 'MutualAuthenticationError', 'REQUIRED',
'OPTIONAL', 'DISABLED')
__all__ = ('HTTPSPNEGOAuth', 'HTTPKerberosAuth', 'MutualAuthenticationError',
'REQUIRED', 'OPTIONAL', 'DISABLED')
__version__ = '0.11.0'
58 changes: 57 additions & 1 deletion requests_gssapi/compat.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
"""
Compatibility library for older versions of python
Compatibility library for older versions of python and requests_kerberos
"""
import sys

import gssapi

from .gssapi_ import REQUIRED, HTTPSPNEGOAuth, SPNEGOExchangeError, log

# python 2.7 introduced a NullHandler which we want to use, but to support
# older versions, we implement our own if needed.
if sys.version_info[:2] > (2, 6):
Expand All @@ -13,3 +17,55 @@
class NullHandler(Handler):
def emit(self, record):
pass


class HTTPKerberosAuth(HTTPSPNEGOAuth):
"""Deprecated compat shim; see HTTPSPNEGOAuth instead."""
def __init__(self, mutual_authentication=REQUIRED, service="HTTP",
delegate=False, force_preemptive=False, principal=None,
hostname_override=None, sanitize_mutual_error_response=True):
# put these here for later
self.principal = principal
self.service = service
self.hostname_override = hostname_override

HTTPSPNEGOAuth.__init__(
self,
mutual_authentication=mutual_authentication,
target_name=None,
delegate=delegate,
opportunistic_auth=force_preemptive,
creds=None,
sanitize_mutual_error_response=sanitize_mutual_error_response)

def generate_request_header(self, response, host, is_preemptive=False):
# This method needs to be shimmed because `host` isn't exposed to
# __init__() and we need to derive things from it. Also, __init__()
# can't fail, in the strictest compatability sense.
try:
if self.principal is not None:
gss_stage = "acquiring credentials"
name = gssapi.Name(self.principal)
self.creds = gssapi.Credentials(name=name, usage="initiate")

# contexts still need to be stored by host, but hostname_override
# allows use of an arbitrary hostname for the GSSAPI exchange (eg,
# in cases of aliased hosts, internal vs external, CNAMEs w/
# name-based HTTP hosting)
if self.service is not None:
gss_stage = "initiating context"
kerb_host = host
if self.hostname_override:
kerb_host = self.hostname_override

kerb_spn = "{0}@{1}".format(self.service, kerb_host)
self.target_name = gssapi.Name(kerb_spn)

return HTTPSPNEGOAuth.generate_request_header(self, response,
host, is_preemptive)
except gssapi.exceptions.GSSError as error:
msg = error.gen_message()
log.exception(
"generate_request_header(): {0} failed:".format(gss_stage))
log.exception(msg)
raise SPNEGOExchangeError("%s failed: %s" % (gss_stage, msg))
8 changes: 6 additions & 2 deletions requests_gssapi/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,9 @@ class MutualAuthenticationError(RequestException):
"""Mutual Authentication Error"""


class KerberosExchangeError(RequestException):
"""Kerberos Exchange Failed Error"""
class SPNEGOExchangeError(RequestException):
"""SPNEGO Exchange Failed Error"""


""" Deprecated compatability shim """
KerberosExchangeError = SPNEGOExchangeError
Loading