From cb155fd57e8a2c10f710660dae7948af6ef0f9b4 Mon Sep 17 00:00:00 2001 From: Michael Osipov Date: Wed, 22 Dec 2021 16:46:36 +0100 Subject: [PATCH] Use SPNEGO mechanism by default (#41) 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 --- HISTORY.rst | 1 + README.rst | 14 +++++++------- requests_gssapi/__init__.py | 2 +- requests_gssapi/gssapi_.py | 9 ++++++--- test_requests_gssapi.py | 27 ++++++++++++++------------- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 0182670..57d8d38 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -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 ----------------- diff --git a/README.rst b/README.rst index 6e3456e..6c787ca 100644 --- a/README.rst +++ b/README.rst @@ -194,10 +194,10 @@ 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 @@ -205,10 +205,10 @@ without interference. It is expected to be an instance of ``gssapi.mechs.Mechani >>> 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) ... diff --git a/requests_gssapi/__init__.py b/requests_gssapi/__init__.py index 92bd9ed..24744ea 100644 --- a/requests_gssapi/__init__.py +++ b/requests_gssapi/__init__.py @@ -14,7 +14,7 @@ """ 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 diff --git a/requests_gssapi/gssapi_.py b/requests_gssapi/gssapi_.py index aa7a87b..68e7b33 100644 --- a/requests_gssapi/gssapi_.py +++ b/requests_gssapi/gssapi_.py @@ -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 ` object, which contains a server's @@ -101,7 +104,7 @@ 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. @@ -109,7 +112,7 @@ class HTTPSPNEGOAuth(AuthBase): """ 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 @@ -117,7 +120,7 @@ def __init__(self, mutual_authentication=DISABLED, target_name="HTTP", 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): diff --git a/test_requests_gssapi.py b/test_requests_gssapi.py index bf15a9c..8c7f9b2 100644 --- a/test_requests_gssapi.py +++ b/test_requests_gssapi.py @@ -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 @@ -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): @@ -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, @@ -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): @@ -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): @@ -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): @@ -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): @@ -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): @@ -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): @@ -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): @@ -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, @@ -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): @@ -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): @@ -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")