Ldap – How to get SASL authentication to work with DIGEST-MD5 for OpenLDAP

ldapopenldapsaslsaslauthd

I'm setting up OpenLDAP slapd on Ubuntu 14.04 Trusty Tahr. I want certain instances (replication etc.) that aren't users to be able to login via SASL using DIGEST-MD5 mechanism.

Unlike users, they are not supposed to have a corresponding DN (along with the password) in the directory tree. Instead, their credentials are supposed to be stored externally, hence SASL.

I'm using saslauthd right now (this is not a hard requirement if it can be made to work with direct access to the sasldb, for instance) and it works fine using mechanisms PLAIN and LOGIN while it fails using mechanisms DIGEST-MD5 and CRAM-MD5.

What am I missing or doing wrong? How can I get it to work with DIGEST-MD5?

OpenLDAP is configured for SASL in /etc/ldap/sasl2/slapd.conf like this:

mech_list: EXTERNAL DIGEST-MD5 CRAM-MD5 PLAIN LOGIN
pwcheck_method: saslauthd
saslauthd_path: /var/run/saslauthd/mux

The interesting (changed) options in /etc/default/saslauthd are:

START=yes
MECHANISMS="sasldb"

They result in saslauthd being started like so:

/usr/sbin/saslauthd -a sasldb -c -m /var/run/saslauthd -n 5

I reproduce the failing case with DIGEST-MD5 like this:

# ldapsearch -U replication -ZZ -Y DIGEST-MD5 -H ldap://ldap-master.example.com/ -b "dc=example,dc=com" "(objectClass=*)"
SASL/DIGEST-MD5 authentication started
Please enter your password: 
ldap_sasl_interactive_bind_s: Invalid credentials (49)
    additional info: SASL(-13): user not found: no secret in database

The part in the slapd log where it seems to fail (logging is on any) looks like this:

slapd[23330]: [rw] authid: "uid=replication,cn=digest-md5,cn=auth" -> "uid=replication,cn=digest-md5,cn=auth"
slapd[23330]: slap_parseURI: parsing uid=replication,cn=digest-md5,cn=auth
slapd[23330]: >>> dnNormalize: <uid=replication,cn=digest-md5,cn=auth>
slapd[23330]: <<< dnNormalize: <uid=replication,cn=digest-md5,cn=auth>
slapd[23330]: <==slap_sasl2dn: Converted SASL name to uid=replication,cn=digest-md5,cn=auth
slapd[23330]: slap_sasl_getdn: dn:id converted to uid=replication,cn=digest-md5,cn=auth
slapd[23330]: SASL Canonicalize [conn=1002]: slapAuthcDN="uid=replication,cn=digest-md5,cn=auth"
slapd[23330]: SASL [conn=1002] Failure: no secret in database
slapd[23330]: SASL [conn=1002] Debug: DIGEST-MD5 common mech dispose
slapd[23330]: send_ldap_result: conn=1002 op=2 p=3
slapd[23330]: send_ldap_result: err=49 matched="" text="SASL(-13): user not found: no secret in database"
slapd[23330]: send_ldap_response: msgid=3 tag=97 err=49

There's nothing in /var/log/auth.log nor in the debug output of saslauthd when I run it manually, which likely indicates that slapd didn't even get as far as handing off the authentication to saslauthd (in contrast with the working case, see below).

I reproduce the working case with PLAIN or LOGIN like this:

# ldapsearch -U replication -ZZ -Y PLAIN -H ldap://ldap-master.example.com/ -b "dc=example,dc=com" "(objectClass=*)"
SASL/PLAIN authentication started
Please enter your password: 
SASL username: replication
SASL SSF: 0
# extended LDIF
…

The corresponding part in the slapd log that indicated failure for the case above now looks like this:

slapd[23330]: [rw] authid: "uid=replication,cn=plain,cn=auth" -> "uid=replication,cn=plain,cn=auth"
slapd[23330]: slap_parseURI: parsing uid=replication,cn=plain,cn=auth
slapd[23330]: >>> dnNormalize: <uid=replication,cn=plain,cn=auth>
slapd[23330]: <<< dnNormalize: <uid=replication,cn=plain,cn=auth>
slapd[23330]: <==slap_sasl2dn: Converted SASL name to uid=replication,cn=plain,cn=auth
slapd[23330]: slap_sasl_getdn: dn:id converted to uid=replication,cn=plain,cn=auth
slapd[23330]: SASL Canonicalize [conn=1006]: slapAuthcDN="uid=replication,cn=plain,cn=auth"
slapd[23330]: daemon: activity on 1 descriptor
slapd[23330]: daemon: activity on:
slapd[23330]: 
slapd[23330]: daemon: epoll: listen=8 active_threads=0 tvp=zero
slapd[23330]: daemon: epoll: listen=9 active_threads=0 tvp=zero
slapd[23330]: daemon: epoll: listen=10 active_threads=0 tvp=zero
slapd[23330]: SASL proxy authorize [conn=1006]: authcid="replication" authzid="replication"
slapd[23330]: conn=1006 op=1 BIND authcid="replication" authzid="replication"
slapd[23330]: SASL Authorize [conn=1006]:  proxy authorization allowed authzDN=""
slapd[23330]: send_ldap_sasl: err=0 len=-1
slapd[23330]: conn=1006 op=1 BIND dn="uid=replication,cn=plain,cn=auth" mech=PLAIN sasl_ssf=0 ssf=128
slapd[23330]: do_bind: SASL/PLAIN bind: dn="uid=replication,cn=plain,cn=auth" sasl_ssf=0
slapd[23330]: send_ldap_response: msgid=2 tag=97 err=0

Both /var/log/auth.log and the debug output from saslauthd now contain this:

saslauthd[23354]: rel_accept_lock : released accept lock
saslauthd[23358]: get_accept_lock : acquired accept lock
saslauthd[23354]: cache_get_rlock : attempting a read lock on slot: 458
saslauthd[23354]: cache_lookup    : [login=replication] [service=] [realm=ldap]: not found, update pending
saslauthd[23354]: cache_un_lock   : attempting to release lock on slot: 458
saslauthd[23354]: cache_get_wlock : attempting a write lock on slot: 458
saslauthd[23354]: cache_commit    : lookup committed
saslauthd[23354]: cache_un_lock   : attempting to release lock on slot: 458
saslauthd[23354]: do_auth         : auth success: [user=replication] [service=ldap] [realm=] [mech=sasldb]
saslauthd[23354]: do_request      : response: OK

Apparently, there must be some difference in how it works with PLAIN and LOGIN vs. DIGEST-MD5 and CRAM-MD5.

Update and clarification:

The DNs used for authorizing access to the tree are uid=replication,cn=digest-md5,cn=auth and uid=replication,cn=plain,cn=auth respectively and according to section 15.2.5 of http://www.openldap.org/doc/admin24/sasl.html those DNs don't need to actually exist in the tree (which must be true at least for PLAIN and LOGIN as it is working fine there).
For testing purposes I'm currently using olcAccess: to * by dn.regex="replication" read by * break to ensure the replication login gets at least read access to everything (yes, I know that's not secure and I will give it proper permissions for production) in the master LDAP tree.

The credentials are in /etc/sasldb2 and they are successfully checked against when using PLAIN or LOGIN (see above). For reference, it looks like that:

# sasldblistusers2
replication@ldap-master: userPassword
…

# db_dump -p /etc/sasldb2 
VERSION=3
format=print
type=hash
h_nelem=4
db_pagesize=4096
HEADER=END
 replication\00ldap-master\00userPassword
 PasswordCensored
…

However, in the case of DIGEST-MD5 and CRAM-MD5 it doesn't seem to contact saslauthd at all (due to me missing something or doing something wrong, possibly), so the database is likely not consulted, either.

Best Answer

My recipe is for OpenLDAP to check directly /etc/sasldb2.

First step: ensure the /etc/sasldb2 is owned by slapd user.

Next step: have slapd not to look for credentials in directory tree, which is done as following:

dn: cn=config
changetype: modify
replace: olcSaslAuxprops
olcSaslAuxprops: sasldb

Later, you will also need a olcAuthzRegexp rule, but in order to test if auth works, it is not necessary.

These settings are working on Debian GNU/Linux Jessie OpenLDAP-2.4.40 builded from source.