Skip to content

Commit

Permalink
Disable mutual authentication by default
Browse files Browse the repository at this point in the history
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
  • Loading branch information
frozencemetery committed May 17, 2019
1 parent 475fff5 commit 498da2e
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 44 deletions.
59 changes: 30 additions & 29 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,57 +52,58 @@ For a more technical explanation of what mutual authentication actually
guarantees, I refer you to rfc2743 (GSSAPIv2), rfc4120 (krb5 in GSSAPI),
rfc4178 (SPNEGO), and rfc4559 (HTTP Negotiate).

REQUIRED

DISABLED
^^^^^^^^

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
be returned to the user but with its contents and headers stripped. If the
response content is more important than the need for mutual auth on errors,
(eg, for certain WinRM calls) the stripping behavior can be suppressed by
setting ``sanitize_mutual_error_response=False``:
By default, there's no need to explicitly disable mutual authentication.
However, for compatability with older versions of request_gssapi or
requests_kerberos, you can explicitly request it not be attempted:

.. code-block:: python
>>> import requests
>>> 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)
>>> from requests_gssapi import HTTPSPNEGOAuth, DISABLED
>>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=DISABLED)
>>> r = requests.get("https://example.org", auth=gssapi_auth)
...
OPTIONAL
REQUIRED
^^^^^^^^

If you'd prefer to not require mutual authentication, you can set your
preference when constructing your ``HTTPSPNEGOAuth`` object:
This was historically the default, but no longer is. If requested,
``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. (See
above for what this means.) If a server emits an error which cannot be
authenticated, it will be returned to the user but with its contents and
headers stripped. If the response content is more important than the need for
mutual auth on errors, (eg, for certain WinRM calls) the stripping behavior
can be suppressed by setting ``sanitize_mutual_error_response=False``:

.. code-block:: python
>>> import requests
>>> from requests_gssapi import HTTPSPNEGOAuth, OPTIONAL
>>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=OPTIONAL)
>>> r = requests.get("http://example.org", auth=gssapi_auth)
>>> 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)
...
This will cause ``requests_gssapi`` to attempt mutual authentication if the
server advertises that it supports it, and cause a failure if authentication
fails, but not if the server does not support it at all.

DISABLED
OPTIONAL
^^^^^^^^

While we don't recommend it, if you'd prefer to never attempt mutual
authentication, you can do that as well:
This will cause ``requests_gssapi`` to attempt mutual authentication if the
server advertises that it supports it, and cause a failure if authentication
fails, but not if the server does not support it at all. This is probably not
what you want: link tampering will either cause hard failures, or silently
cause it to not happen at all. It is retained for compatability.

.. code-block:: python
>>> import requests
>>> from requests_gssapi import HTTPSPNEGOAuth, DISABLED
>>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=DISABLED)
>>> r = requests.get("http://example.org", auth=gssapi_auth)
>>> from requests_gssapi import HTTPSPNEGOAuth, OPTIONAL
>>> gssapi_auth = HTTPSPNEGOAuth(mutual_authentication=OPTIONAL)
>>> r = requests.get("https://example.org", auth=gssapi_auth)
...
Opportunistic Authentication
Expand Down
4 changes: 2 additions & 2 deletions requests_gssapi/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import gssapi

from .gssapi_ import REQUIRED, HTTPSPNEGOAuth, SPNEGOExchangeError, log
from .gssapi_ import DISABLED, 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.
Expand All @@ -21,7 +21,7 @@ def emit(self, record):

class HTTPKerberosAuth(HTTPSPNEGOAuth):
"""Deprecated compat shim; see HTTPSPNEGOAuth instead."""
def __init__(self, mutual_authentication=REQUIRED, service="HTTP",
def __init__(self, mutual_authentication=DISABLED, service="HTTP",
delegate=False, force_preemptive=False, principal=None,
hostname_override=None, sanitize_mutual_error_response=True):
# put these here for later
Expand Down
12 changes: 7 additions & 5 deletions requests_gssapi/gssapi_.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ class HTTPSPNEGOAuth(AuthBase):
"""Attaches HTTP GSSAPI Authentication to the given Request object.
`mutual_authentication` controls whether GSSAPI should attempt mutual
authentication. It may be `REQUIRED` (default), `OPTIONAL`, or
`DISABLED`.
authentication. It may be `REQUIRED`, `OPTIONAL`, or `DISABLED`
(default).
`target_name` specifies the remote principal name. It may be either a
GSSAPI name type or a string (default: "HTTP" at the DNS host).
Expand All @@ -101,8 +101,9 @@ class HTTPSPNEGOAuth(AuthBase):
`sanitize_mutual_error_response` controls whether we should clean up
server responses. See the `SanitizedResponse` class.
"""
def __init__(self, mutual_authentication=REQUIRED, target_name="HTTP",
def __init__(self, mutual_authentication=DISABLED, target_name="HTTP",
delegate=False, opportunistic_auth=False, creds=None,
sanitize_mutual_error_response=True):
self.context = {}
Expand All @@ -123,10 +124,11 @@ def generate_request_header(self, response, host, is_preemptive=False):
"""

gssflags = [gssapi.RequirementFlag.mutual_authentication,
gssapi.RequirementFlag.out_of_sequence_detection]
gssflags = [gssapi.RequirementFlag.out_of_sequence_detection]
if self.delegate:
gssflags.append(gssapi.RequirementFlag.delegate_to_peer)
if self.mutual_authentication != DISABLED:
gssflags.append(gssapi.RequirementFlag.mutual_authentication)

try:
gss_stage = "initiating context"
Expand Down
24 changes: 16 additions & 8 deletions test_requests_gssapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import requests_gssapi
import unittest

from requests_gssapi import REQUIRED

# Note: we're not using the @mock.patch decorator:
# > My only word of warning is that in the past, the patch decorator hides
# > tests when using the standard unittest library.
Expand All @@ -26,8 +28,8 @@
# construction, so construct a *really* fake one
fail_resp = Mock(side_effect=gssapi.exceptions.GSSError(0, 0))

gssflags = [gssapi.RequirementFlag.mutual_authentication,
gssapi.RequirementFlag.out_of_sequence_detection]
gssflags = [gssapi.RequirementFlag.out_of_sequence_detection]
mutflags = gssflags + [gssapi.RequirementFlag.mutual_authentication]
gssdelegflags = gssflags + [gssapi.RequirementFlag.delegate_to_peer]

# The base64 behavior we want is that encoding produces a string, but decoding
Expand Down Expand Up @@ -237,7 +239,8 @@ def test_handle_other(self):
'www-authenticate': b64_negotiate_server,
'authorization': b64_negotiate_response}

auth = requests_gssapi.HTTPKerberosAuth()
auth = requests_gssapi.HTTPKerberosAuth(
mutual_authentication=REQUIRED)
auth.context = {"www.example.org": gssapi.SecurityContext}

r = auth.handle_other(response_ok)
Expand All @@ -255,7 +258,8 @@ def test_handle_response_200(self):
'www-authenticate': b64_negotiate_server,
'authorization': b64_negotiate_response}

auth = requests_gssapi.HTTPKerberosAuth()
auth = requests_gssapi.HTTPKerberosAuth(
mutual_authentication=REQUIRED)
auth.context = {"www.example.org": gssapi.SecurityContext}

r = auth.handle_response(response_ok)
Expand All @@ -272,7 +276,8 @@ def test_handle_response_200_mutual_auth_required_failure(self):
response_ok.status_code = 200
response_ok.headers = {}

auth = requests_gssapi.HTTPKerberosAuth()
auth = requests_gssapi.HTTPKerberosAuth(
mutual_authentication=REQUIRED)
auth.context = {"www.example.org": "CTX"}

self.assertRaises(requests_gssapi.MutualAuthenticationError,
Expand All @@ -290,7 +295,8 @@ def test_handle_response_200_mutual_auth_required_failure_2(self):
'www-authenticate': b64_negotiate_server,
'authorization': b64_negotiate_response}

auth = requests_gssapi.HTTPKerberosAuth()
auth = requests_gssapi.HTTPKerberosAuth(
mutual_authentication=REQUIRED)
auth.context = {"www.example.org": gssapi.SecurityContext}

self.assertRaises(requests_gssapi.MutualAuthenticationError,
Expand Down Expand Up @@ -348,7 +354,8 @@ def test_handle_response_500_mutual_auth_required_failure(self):
response_500.raw = "RAW"
response_500.cookies = "COOKIES"

auth = requests_gssapi.HTTPKerberosAuth()
auth = requests_gssapi.HTTPKerberosAuth(
mutual_authentication=REQUIRED)
auth.context = {"www.example.org": "CTX"}

r = auth.handle_response(response_500)
Expand Down Expand Up @@ -524,7 +531,8 @@ def test_delegation(self):
response.connection = connection
response._content = ""
response.raw = raw
auth = requests_gssapi.HTTPKerberosAuth(1, "HTTP", True)
auth = requests_gssapi.HTTPKerberosAuth(service="HTTP",
delegate=True)
r = auth.authenticate_user(response)

self.assertTrue(response in r.history)
Expand Down

0 comments on commit 498da2e

Please sign in to comment.