'passwordless LDAP login and get user information using Kerberos ticket in PHP

I'm trying to implement SSO on some intranet sites in our company using FreeIPA/Kerberos. But information on this topic is very thin.

I have three machines running in my test network:

  1. FreeIPA v4.9.8 Server on Centos 8 Stream
  2. Web Server (Apache v2.4.53, PHP v7.4.28) on Debian 11
  3. Xubuntu 22.04 Client with Kinit and Firefox

Kinit, Unix Login and Apache Kerberos Auth work. The Firefox browser on the client system can log on to the FreeIPA WebConfig without a password (using Kerberos Ticket). I would now like to transfer this function to our intranet pages. Up until now, the login to these pages has been based on a conventional LDAP login. With minor adjustments to the login script, the user can now log on to the new FreeIPA server. However, he still needs his password for this, which should actually no longer be necessary thanks to the Kerberos ticket.

The question is, what does a passwordless login look like?

a functioning snippet of the login script:

<?php
$username = $_SERVER['PHP_AUTH_USER'];
$password = 'password';

$ldap_rdn  = 'uid='.$username.',cn=users,cn=accounts,dc=exampletest,dc=de';
$ldap_server = ldap_connect('ldap://ipa.exampletest.de:389');

ldap_set_option($ldap_server, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap_server, LDAP_OPT_REFERRALS, 0);

if ($ldap_server) {
  $ldap_bind = @ldap_bind($ldap_server, $ldap_rdn, $password);
  if ($ldap_bind) {
    $search = array("uid","givenname","sn","mail","uidnumber","gidnumber");
    $result = ldap_search($ldap_server, $ldap_rdn, "mail=$username*", $search);
    $info = ldap_get_entries($ldap_server, $result);

    print_r($info);
  }
}
?>

Now I have two ideas:

  1. I could use ldap_sasl_bind() instead of ldap_bind() but this function is not documented on php.net (https://www.php.net/manual/en/function.ldap-sasl-bind.php). If anyone has any idea how to work with this function I would greatly appreciate it.
  2. If I could somehow run ldap_search() without a password to get the user information (full name, email, etc.) I'd be happy too.

Many thanks in advance.

EDIT:

The web server VM and the client VM are both initialized via "ipa-client-install". In addition, the web server has registered the apache service (ipa service-add HTTP/ebook.exampletest.de).

The apache config also reflected this:

<Directory /var/www/ebook/>
        AuthType                GSSAPI
        AuthName                "eBook Login"
        GssapiCredStore         keytab:/etc/apache2/http.keytab
        GssapiAllowedMech       krb5
        GssapiBasicAuthMech     krb5
        GssapiImpersonate       On
        GssapiDelegCcacheDir    /run/apache2/clientcaches
        GssapiLocalName         On
        
        # for production set to on:
        GssapiSSLonly           Off
        GssapiNegotiateOnce     Off
        
        GssapiUseSessions       On
        Session                 On
        SessionCookieName       gssapi_session path=/private;httponly;secure;
        Require                 valid-user
    </Directory>

And as I already mentioned, user authentication seems to works this way (client (own ticket) > web service (own ticket) > ipa server). Otherwise the apache server would not return my ldap/kerberos-username. Or am I missing something important here? Is there another way to enforce this kind of authentication?

output of: <?php print_r($_SERVER) ?> (snipped)

[GSS_MECH] => Negotiate/krb5
[GSS_NAME] => [email protected]
[REMOTE_USER] => test
[AUTH_TYPE] => Negotiate
[PHP_AUTH_USER] => test


Solution 1:[1]

However, he still needs his password for this, which should actually no longer be necessary thanks to the Kerberos ticket.

Make sure your web server has the right Kerberos ticket.

Normally Kerberos auth only transfers a ticket valid only for that server, not a blanket "everything" ticket. When the client authenticates to your webapp, all you get is a ticket for HTTP/webapp.example.tld and you can't really use it to access LDAP on behalf of the user.

If you need to access LDAP on behalf of the user, a few options exist:

  • The webapp could have its own credentials to the LDAP directory. This might be the easiest approach. The webapp could either use standard password bind, or Kerberos (SASL) bind with its own tickets acquired from a keytab.

    • LDAP also supports "impersonation", where the webapp would use its own credentials to authenticate but also specify an "authorization ID" (authzid) that determines which account's privileges you will get.

      For example, if you authenticate as 'webapp' but specify the authzid 'myuser' (and if the LDAP server allows this), then you will get exactly the privileges that 'myuser' would normally have – not those of 'webapp'.

  • The webapp's HTTP Negotiate (SPNEGO) authentication could have "delegation" enabled. Delegation does transfer the main krbtgt ticket to the web server, which will then put it in a temporary ticket cache and make available to your webapp's environment.

    Delegation has a few problems, however:

    1. It makes each HTTP request slower, as the client has to request a new krbtgt ticket with the "forwarded" flag (unless the web server can use e.g. cookies to avoid requesting Negotiate auth for further requests, like mod_auth_gssapi with the "Session" mode).

    2. It requires the webapp to be highly trusted, as it will be storing wildcard tickets for every user accessing it (including admins) – even if the webapp itself is trusted to not abuse them, they could still get stolen from the server.

    3. Most Kerberos-using APIs (including ldap_sasl_bind()) will expect the KRB5CCNAME environment variable to point at the ticket cache. But environment variables are process-wide, so they can leak across unrelated requests whenever PHP reuses the same process (or even worse, if you use mod_php to run the webapp inside the Apache process).

    In AD, this is known specifically as "unconstrained delegation", as AD introduces other variants.

  • The webapp could use S4U2Proxy aka "Constrained delegation" to create tickets on behalf of a user, for a certain limited set of services (e.g. FreeIPA could restrict it to only accessing ldap/foo.example.com).

    This is somewhat complex (PHP has no APIs for this – you'd probably need to spawn kinit with the right flags), and still has the same potential problem with KRB5CCNAME leaking across requests.

I could use ldap_sasl_bind() instead of ldap_bind() but this function is not documented on php.net (https://www.php.net/manual/en/function.ldap-sasl-bind.php). If anyone has any idea how to work with this function I would greatly appreciate it.

For regular Kerberos authentication, the usage looks like this:

ldap_sasl_bind($conn, null, null, "GSSAPI");

That's all. The GSSAPI SASL mechanism expects the environment to already have Kerberos tickets available (e.g. via $KRB5CCNAME, or via gss-proxy), and it will authenticate using whatever ticket it finds there.

If you wanted to use impersonation (assuming that's set up in the LDAP server), you'd have to specify the authz_id:

ldap_sasl_bind($conn, null, null, "GSSAPI", null, null, $theuser);

Most ldap_*() PHP functions are direct wrappers around the C libldap library, so its documentation may be useful as a partial reference.

$result = ldap_search($ldap_server, $ldap_rdn, "mail=$username*", $search);

It seems your example already specifies the user's exact DN, so there seems to be no need for the additional filtering by mail – just use objectClass=* when reading a specific DN. Also, when you want to read a specific DN, use ldap_read() to make a 'base' search instead of subtree search.

And as I already mentioned, user authentication seems to works this way (client (own ticket) > web service (own ticket) > ipa server). Otherwise the apache server would not return my ldap/kerberos-username.

No, that's not how it works. Your username (i.e. the client Kerberos principal) is stored in the client ticket, so the web server immediately knows it upon decrypting the ticket, without having to talk to IPA at all.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1