Skip to content
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