Skip to content

Commit

Permalink
Use SPNEGO mechanism by default (#41)
Browse files Browse the repository at this point in the history
Use the correct mechanism as described by
* RFC 4559, section 4: The "Negotiate" auth-scheme calls for the use of SPNEGO
  GSSAPI tokens that the specific mechanism type specifies.
* RFC 4178, section 3.2: The GSS-API initiator invokes GSS_Init_sec_context()
  as normal, but requests that SPNEGO be used. SPNEGO can either be explicitly
  requested or accepted as the default mechanism.

Since both MIT Kerberos and Heimdal use Kerberos 5 as their default mechanism
we must explicitly request SPNEGO. Passing raw Kerberos tokens to the acceptor
is a violation of these RFCs and some implementations complain about, thus they
always need to be wrapped.

This closes #41

Signed-off-by: Michael Osipov <michael.osipov@siemens.com>
  • Loading branch information
michael-o authored and jborean93 committed Jan 16, 2022
1 parent 7af32f3 commit 84e052b
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 25 deletions.
1 change: 1 addition & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ History
FUTURE: TBD
-----------
- Drop flag for out of sequence detection
- Use SPNEGO mechanism by default

1.2.3: 2021-02-08
-----------------
Expand Down
14 changes: 7 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -194,21 +194,21 @@ applicable). However, an explicit credential can be in instead, if desired.
Explicit Mechanism
------------------

``HTTPSPNEGOAuth`` normally lets the underlying ``gssapi`` library decide which
negotiation mechanism to use. However, an explicit mechanism can be used instead
if desired. The ``mech`` parameter will be passed straight through to ``gssapi``
without interference. It is expected to be an instance of ``gssapi.mechs.Mechanism``.
``HTTPSPNEGOAuth`` normally lets SPNEGO decide which negotiation mechanism to use.
However, an explicit mechanism can be used instead if desired. The ``mech``
parameter will be passed straight through to ``gssapi`` without interference.
It is expected to be an instance of ``gssapi.mechs.Mechanism``.

.. code-block:: python
>>> import gssapi
>>> import requests
>>> from requests_gssapi import HTTPSPNEGOAuth
>>> try:
... spnego = gssapi.mechs.Mechanism.from_sasl_name("SPNEGO")
... krb5 = gssapi.mechs.Mechanism.from_sasl_name("GS2-KRB5")
... except AttributeError:
... spnego = gssapi.OID.from_int_seq("1.3.6.1.5.5.2")
>>> gssapi_auth = HTTPSPNEGOAuth(mech=spnego)
... krb5 = gssapi.OID.from_int_seq("1.2.840.113554.1.2.2")
>>> gssapi_auth = HTTPSPNEGOAuth(mech=krb5)
>>> r = requests.get("http://example.org", auth=gssapi_auth)
...
Expand Down
4 changes: 2 additions & 2 deletions requests_gssapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
"""
import logging

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

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

__all__ = ('HTTPSPNEGOAuth', 'HTTPKerberosAuth', 'MutualAuthenticationError',
'REQUIRED', 'OPTIONAL', 'DISABLED')
'SPNEGO', 'REQUIRED', 'OPTIONAL', 'DISABLED')
__version__ = '1.2.3'
9 changes: 6 additions & 3 deletions requests_gssapi/gssapi_.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
OPTIONAL = 2
DISABLED = 3

# OID for the SPNEGO mechanism
SPNEGO = gssapi.OID.from_int_seq("1.3.6.1.5.5.2")


class SanitizedResponse(Response):
"""The :class:`Response <Response>` object, which contains a server's
Expand Down Expand Up @@ -101,23 +104,23 @@ class HTTPSPNEGOAuth(AuthBase):
Default is `None`.
`mech` is GSSAPI Mechanism (gssapi.Mechanism) to use for negotiation.
Default is `None`
Default is `SPNEGO`
`sanitize_mutual_error_response` controls whether we should clean up
server responses. See the `SanitizedResponse` class.
"""
def __init__(self, mutual_authentication=DISABLED, target_name="HTTP",
delegate=False, opportunistic_auth=False, creds=None,
mech=None, sanitize_mutual_error_response=True):
mech=SPNEGO, sanitize_mutual_error_response=True):
self.context = {}
self.pos = None
self.mutual_authentication = mutual_authentication
self.target_name = target_name
self.delegate = delegate
self.opportunistic_auth = opportunistic_auth
self.creds = creds
self.mech = mech
self.mech = mech if mech else SPNEGO
self.sanitize_mutual_error_response = sanitize_mutual_error_response

def generate_request_header(self, response, host, is_preemptive=False):
Expand Down
27 changes: 14 additions & 13 deletions test_requests_gssapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import unittest

from requests_gssapi import REQUIRED
from requests_gssapi import SPNEGO

# Note: we're not using the @mock.patch decorator:
# > My only word of warning is that in the past, the patch decorator hides
Expand Down Expand Up @@ -110,7 +111,7 @@ def test_generate_request_header(self):
b64_negotiate_response)
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
creds=None, mech=None, flags=gssflags, usage="initiate")
creds=None, mech=SPNEGO, flags=gssflags, usage="initiate")
fake_resp.assert_called_with(b"token")

def test_generate_request_header_init_error(self):
Expand All @@ -125,7 +126,7 @@ def test_generate_request_header_init_error(self):
auth.generate_request_header, response, host)
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
usage="initiate", flags=gssflags, creds=None, mech=None)
usage="initiate", flags=gssflags, creds=None, mech=SPNEGO)

def test_generate_request_header_step_error(self):
with patch.multiple("gssapi.SecurityContext", __init__=fake_init,
Expand All @@ -139,7 +140,7 @@ def test_generate_request_header_step_error(self):
auth.generate_request_header, response, host)
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
usage="initiate", flags=gssflags, creds=None, mech=None)
usage="initiate", flags=gssflags, creds=None, mech=SPNEGO)
fail_resp.assert_called_with(b"token")

def test_authenticate_user(self):
Expand Down Expand Up @@ -176,7 +177,7 @@ def test_authenticate_user(self):
raw.release_conn.assert_called_with()
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
flags=gssflags, usage="initiate", creds=None, mech=None)
flags=gssflags, usage="initiate", creds=None, mech=SPNEGO)
fake_resp.assert_called_with(b"token")

def test_handle_401(self):
Expand Down Expand Up @@ -213,7 +214,7 @@ def test_handle_401(self):
raw.release_conn.assert_called_with()
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
creds=None, mech=None, flags=gssflags, usage="initiate")
creds=None, mech=SPNEGO, flags=gssflags, usage="initiate")
fake_resp.assert_called_with(b"token")

def test_authenticate_server(self):
Expand Down Expand Up @@ -452,7 +453,7 @@ def test_handle_response_401(self):
raw.release_conn.assert_called_with()
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
usage="initiate", flags=gssflags, creds=None, mech=None)
usage="initiate", flags=gssflags, creds=None, mech=SPNEGO)
fake_resp.assert_called_with(b"token")

def test_handle_response_401_rejected(self):
Expand Down Expand Up @@ -495,7 +496,7 @@ def connection_send(self, *args, **kwargs):
raw.release_conn.assert_called_with()
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
usage="initiate", flags=gssflags, creds=None, mech=None)
usage="initiate", flags=gssflags, creds=None, mech=SPNEGO)
fake_resp.assert_called_with(b"token")

def test_generate_request_header_custom_service(self):
Expand All @@ -509,7 +510,7 @@ def test_generate_request_header_custom_service(self):
auth.generate_request_header(response, host),
fake_init.assert_called_with(
name=gssapi_sname("barfoo@www.example.org"),
usage="initiate", flags=gssflags, creds=None, mech=None)
usage="initiate", flags=gssflags, creds=None, mech=SPNEGO)
fake_resp.assert_called_with(b"token")

def test_delegation(self):
Expand Down Expand Up @@ -547,7 +548,7 @@ def test_delegation(self):
raw.release_conn.assert_called_with()
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
usage="initiate", flags=gssdelegflags, creds=None, mech=None)
usage="initiate", flags=gssdelegflags, creds=None, mech=SPNEGO)
fake_resp.assert_called_with(b"token")

def test_principal_override(self):
Expand All @@ -566,7 +567,7 @@ def test_principal_override(self):
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
usage="initiate", flags=gssflags,
creds=b"fake creds", mech=None)
creds=b"fake creds", mech=SPNEGO)

def test_realm_override(self):
with patch.multiple("gssapi.SecurityContext", __init__=fake_init,
Expand All @@ -580,7 +581,7 @@ def test_realm_override(self):
auth.generate_request_header(response, host)
fake_init.assert_called_with(
name=gssapi_sname("HTTP@otherhost.otherdomain.org"),
usage="initiate", flags=gssflags, creds=None, mech=None)
usage="initiate", flags=gssflags, creds=None, mech=SPNEGO)
fake_resp.assert_called_with(b"token")

def test_opportunistic_auth(self):
Expand Down Expand Up @@ -610,7 +611,7 @@ def test_explicit_creds(self):
fake_init.assert_called_with(
name=gssapi_sname("HTTP@www.example.org"),
usage="initiate", flags=gssflags,
creds=b"fake creds", mech=None)
creds=b"fake creds", mech=SPNEGO)
fake_resp.assert_called_with(b"token")

def test_explicit_mech(self):
Expand Down Expand Up @@ -642,7 +643,7 @@ def test_target_name(self):
auth.generate_request_header(response, host)
fake_init.assert_called_with(
name=gssapi_sname("HTTP@otherhost.otherdomain.org"),
usage="initiate", flags=gssflags, creds=None, mech=None)
usage="initiate", flags=gssflags, creds=None, mech=SPNEGO)
fake_resp.assert_called_with(b"token")


Expand Down

0 comments on commit 84e052b

Please sign in to comment.