Getting Kerberos authentication working

I’m trying to get Kerberos authentication working with Tryton version 6.6. It’s mostly working and straight forward, but I walked into a problem when things are not right.

When a user is not authenticated, unauthorized or there is an error, the webpage is showing the error, but there is nothing to get the client back or to stop it. Killing the client is the only thing to do or wait 5 minutes for a popup that is shown saying that a session could not be created. And then click on ‘close’ to completely close the Tryton client. This prevents the user to use the default login method like password.

I would suggest to show a button on the page when authentication failed. The user can click on it to return back to the client and try the next authentication method. An extra button with ‘cancel’ can also be useful to just stop and completely close the Tryton client. Also the username is lost when the login attempt failed.

Tryton does not support Kerberos but I guess you are talking about SAML.

This is not a page generated by Tryton.

Well he can re-launch the client and login with password.

This is the timeout choose but there is no good value.

As said we do not control the page, it is the SAML server that generates it.

The client is waiting for the http request from the browser. If it is not blocking then it can create trouble with other events being triggered.

Anyway patch is welcomed.

No, I am developing such a module which can authenticate with a Kerberos ticket. This module checks the ticket against a keytab file. I’m using GitHub - deshaw/flask-kerberos: Kerberos Authentication for Flask as an example and got everything working. But I walked into the said problem.

Yes, but after starting a terminal, search the PID and then kill that PID. Not really user friendly.

That’s completely valid, but when I send a 401, 403 or 500 the error is displayed and the browser is not closed. So I send a response and the webbrowser displays it, but the bit to the Tryton client seems missing. For that it would be nice to display a small page like when the authentication flow is completed (https://foss.heptapod.net/tryton/tryton/-/blob/branch/default/tryton/tryton/common/common.py#L1058)

I will dig deeper and see if I can come up with something.

That’s up to you to communicate to the client.

I think I found the problem. No solution yet.

Kerberos is basically a two step process:

  1. Client connects to the server and want to login
  2. Server responds with a 401 and ‘WWW-Authenticate’ : ‘Negotiate’ in the header
  3. Client takes the kerberos ticket and sends it to the server in the ‘Authorization’ header
  4. Server checks the ticket and grant permission or not

This works all well until there is no kerberos ticket. The server responds, but the client doesn’t have a ticket. So the client doesn’t respond anymore.

The solution would indeed be to serve a small page with a button in a Response object. Will work on that to see if it will work.

Oh yeah, that works nice! I made a small page

error_html = """\
<html>
    <head>
        <title>Authentication Status</title>
    </head>
    <body>
        <p>We couldn't authorize you.</p>
        <button onclick="window.location.replace('%s')">Close</button>
        <button onclick="window.location.reload()">Try again</button>
    </body>
</html>""" % urllib.parse.urlunsplit(parts)

return Response(error_html, 401, {'WWW-Authenticate': 'Negotiate'}, mimetype='text/html')

The user has then two options. Try to get a new ticket and retry or just close the page. When the page is closed the url is redirected to the localhost with all the data None so no login is created. The client pops up again with the error that there is no session created.

I will do a lot of clean up and hopefully it can be added as a standard module. At the moment it’s just over 200 lines including blank lines and comments.

Just one question:
It seems that everything is working but normally in the header there is the WWW-Authenticate key with the Kerberos token. Setting this in the header, will it be added to the Tryton client headers?

No Tryton authentication is only based on Authorization header.

Ok, so no need to send that one back. When the user has logged in with a valid ticket and got a session, that session will basically overrule the lifetime of the ticket right? So when a user is working in Tryton and the ticket got invalid because the renewal did go wrong, the user is still able to work in Tryton until that session should be renewed and checked. Maybe I’m to concerned and is this a non-issue, but if it is possible to set the lifetime of the Tryton session the same as the ticket (or a bit longer), that would be a nice thing to have.

Related issue Allow login using Kerberos authentication (#4412) · Issues · Tryton / Tryton · GitLab

As I am not a regular developer, before I upload the code is it safe to duplicate the authentication_saml module and make my changes. Or is it better to create a new module from scratch? And if so, is the coockiecutter method still valid?

No because the odds are high that you forget to change part of existing code.

Yes the cookiecutter template is in the repository.

On the merge request Add module for Kerberos authentication (!184) · Merge requests · Tryton / Tryton · GitLab a discussion started about the desktop client to do the Kerberos authentication without opening a web browser. The reason is that the web browser needs to be configured by setting the trusted domains in network.negotiate-auth.trusted-uri. Otherwise the negotiation step won’t work.

I created a small test what is needed to get Kerberos working without a browser and made some small changes to the authentication_kerberos module and to the desktop client itself. In the file https://foss.heptapod.net/tryton/tryton/-/blob/branch/default/tryton/tryton/common/common.py I imported the requests and requests_kerberos packages:

import requests
from requests_kerberos import HTTPKerberosAuth, DISABLED

And replaced https://foss.heptapod.net/tryton/tryton/-/blob/branch/default/tryton/tryton/common/common.py#L1033 with

def get_credentials(user_id=None):
    if not CONFIG['login.service']:
        Login()
    else:
        url = CONFIG['login.service']
        r = requests.get(url, auth=HTTPKerberosAuth(mutual_authentication=DISABLED, sanitize_mutual_error_response=False))
        if r.status_code == 200:
            res = r.json()
            try:
                rpc.set_service_session(res)
            except ValueError:
                pass

That’s enough to get Kerberos working, but completely disables any other login services like saml. The question is how to make a distinction between them and other services.

Looking in to the code, you have to register the service so when the client connects, the server sends those authentication services to the client. Currently the name and url are registered and I propose to add another variable redirect which tells the client to use the web browser for authentication. This variable is default set as True.

Maybe another way is to use a plugin, but I have no idea how that will fit in.

For me it will be more flexible to pass the name of the protocol so the client can choose how to deal with it.

I suppose that this change should be done in a separate issue with topic or branch?

At least in a separated commit.