Search code examples
pythonpython-requestskerberos

Cannot authenticate with requests-gssapi on SPNEGO server


Im trying to access a SAS service on my company's intranet through python using requests but I cant get it to work due to authentication failure (401).

Im using python 3.7.4, requests 2.22.0 on Windows 10. So far I have tried/checked:

  • requests.get(follow_redirects=True)
  • Using HTTPBasicAuth('user', 'pass'), HTTPDigestAuth(), HttpNtlmAuth('user', 'pass')
  • Using requests_kerberos.HTTPKerberosAuth() with all possible parameter combinations, including setting force_preemptive=True flag and principal=user@REALM. I have confirmed with klist that multiple tickets do exist.
  • Installing MIT Kerberos and using requests_gssapi.HTTPSPNEGOAuth().
  • Using curl command curl -i -L -v --negotiate --user "domain\user:pswd" <protected_service_url> in git bash, does authenticate.
  • Opening the url in Internet explorer/Chrome obviously works. I do not have to input my credentials anywhere and any authentication is handled behind the scenes.
  • the exchange of packages follows the basic Kerberos authentication flow: ask for protected service, receive a redirection to the authentication service, follow redirection with no authentication, get a 401 response and a www-authenticate: Negotiate challenge, respond with authentication ticket.
  • I have checked the exchange of packages with Wireshark. Here is the critical request+response authentication part of the HTTP package from Chrome (successful):

Chrome request

[truncated]Authorization: Negotiate YIIIsgYGKwYBBQUCoIIIpjCC...
    GSS-API Generic Security Service Application Program Interface
        OID: 1.3.6.1.5.5.2 (SPNEGO - Simple Protected Negotiation)
        Simple Protected Negotiation
            negTokenInit
                mechTypes: 4 items
                mechToken: 6082086406092a864886f71201020201006e820853308208…
                krb5_blob: 6082086406092a864886f71201020201006e820853308208…
                    KRB5 OID: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5)
                    krb5_tok_id: KRB5_AP_REQ (0x0001)
                    Kerberos

=> Chrome response status

HTTP/1.1 302 Found 

The browser then proceeds to automatically redirect to the service.

And here is the requests_gssapi.HTTPSPNEGOAuth() unsuccesful request:

 [truncated]Authorization: Negotiate YIIHQQYJKoZIhvcSAQICAQBuggcw...
    GSS-API Generic Security Service Application Program Interface
        OID: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5)
        krb5_blob: 01006e8207303082072ca003020105a10302010ea2070305…
            krb5_tok_id: KRB5_AP_REQ (0x0001)
            Kerberos

=> response status

HTTP/1.1 401 Unauthorized  (text/html)

My testing code now is:

# 302 response is already handled by the first GET
r1 = session.get(url)  
    if r1.status_code != 401:
        print("Error! Server authorization failed at step 2. Expected response 401 but instead got: " + str(r1.status_code))
        return None
auth_url = r1.url
# step 2: server sends 401 and WWW-Authenticate: Negotiate header
# GSS-API (SPNEGO - Simple Protected Negotiation)
r2 = session.get(auth_url, auth=HTTPSPNEGOAuth(mutual_authentication=True))

Using the requests-gssapi library got me the closest but still authentication fails. I do not understand why. The Kerberos tickets used are the same. The only difference that I can see between the successful Chrome request and the failed python request is the OID. However I do not know how I can change that as it seems to be a library implementation detail.

Any help is appreciated.


Solution

  • I managed to solve this. So for anyone facing a similar issue, here are my steps:

    1. I used python requests-gssapi. This is a more recent version of requests-kerberos and can be dropped in place.
    2. However, this library didn't initially work with my authentication case. The reason was that the wrong authentication mechanism was selected: OID: 1.2.840.113554.1.2.2 (KRB5 - Kerberos 5) instead of OID: 1.3.6.1.5.5.2 (SPNEGO - Simple Protected Negotiation). Fortunately there was already a solution in an unaccepted pull request (as of Sept. 2019). Copy the gssapi_.py to your local install.
    3. Use the following code as is also described in the pull request updated README.rst:
    import gssapi
    import requests
    from requests_gssapi import HTTPSPNEGOAuth
    try:
       spnego = gssapi.mechs.Mechanism.from_sasl_name("SPNEGO")
    except AttributeError:
       spnego = gssapi.OID.from_int_seq("1.3.6.1.5.5.2")
    gssapi_auth = HTTPSPNEGOAuth(mech=spnego)
    r = requests.get("http://example.org", auth=gssapi_auth)
    

    And done!