Passwordless LDAP authentication

If you're running boxes in an Active Directory or Samba4 Domain, you might get to the point where you need to access the domain's LDAP directory. Hence, you require some kind of authentication.

The classical way involves either using your own domain account, ideally only temporarily; using the Administrator account (which is strongly discouraged, meaning people do it all the time); or creating a dummy user with an even dummier password to use in the application. I admit to having used this way before, but I always felt it to be somewhat ugly, unclean. So finally, I went looking for a better way.

First and foremost, such dummy accounts are maintained by the Domain itself. The machine accounts created when joining a new box into the domain are exactly that: Accounts used by programs running on a machine to authenticate their access to Domain resources, like the LDAP directory. So creating yet another dummy account for each application is simply unnecessary. Machine accounts are named in the format HOSTNAME$ and put into the Computers subdirectory. They have all the properties of a standard user, plus a few marking them as computers:

# damien, Computers, local.lan
dn: CN=damien,CN=Computers,DC=local,DC=lan
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
objectClass: computer
cn: damien
whenCreated: 20150201112429.0Z
name: damien
badPwdCount: 0
badPasswordTime: 0
lastLogoff: 0
lastLogon: 0
sAMAccountName: damien$
objectCategory: CN=Computer,CN=Schema,CN=Configuration,DC=local,DC=lan
dNSHostName: damien.local.lan
servicePrincipalName: HOST/DAMIEN
servicePrincipalName: HOST/damien.local.lan
servicePrincipalName: SSH/damien
servicePrincipalName: SSH/damien.local.lan
distinguishedName: CN=damien,CN=Computers,DC=local,DC=lan

There are more, but you get the idea. So, no need to create yet another user. But what's the account's password?

Password Auth

Getting the password from a machine account is pretty easy if you're on a Linux box. You can use tdbtool to get the password from Samba's database, like this:

root@damien:~$ tdbtool /var/lib/samba/private/secrets.tdb
tdb> keys
key 39 bytes: SECRETS/SALTING_PRINCIPAL/DES/LOCAL.LAN
key 38 bytes: SECRETS/MACHINE_PASSWORD.PREV/LOCALLAN
key 33 bytes: SECRETS/MACHINE_PASSWORD/LOCALLAN                 <-- we need this one
key 20 bytes: SECRETS/SID/LOCALLAN
key 18 bytes: SECRETS/SID/DAMIEN
key 41 bytes: SECRETS/MACHINE_LAST_CHANGE_TIME/LOCALLAN
key 41 bytes: SECRETS/MACHINE_SEC_CHANNEL_TYPE/LOCALLAN
tdb> show SECRETS/MACHINE_PASSWORD/LOCALLAN

key 33 bytes
SECRETS/MACHINE_PASSWORD/LOCALLAN
data 15 bytes
[000] 2E ** ** ** ** ** ** **  ** ** ** ** ** 6E 00     .******* *****n

The last line shows the password's bytes encoded in hex and in ascii. Strip the blank in the middle, and you have the plain text password. So now, you have everything needed to connect to the LDAP directory using plain text auth! Behold:

root@damien:~$ ldapsearch -x -H ldap://dc.local.lan -b 'dc=local,dc=lan' -D 'LOCALLAN\DAMIEN$' -w '.************n' '(sAMAccountName=svedrin)'

# svedrin, Users, local.lan
dn: CN=svedrin,CN=Users,DC=local,DC=lan
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: svedrin
whenCreated: 20150201113122.0Z
name: svedrin

Getting the Machine account password using Python

In order to retrieve the machine account password, you can also use this little Python script:

import tdb
secrets = tdb.open("/var/lib/samba/private/secrets.tdb")
pwkey = [key for key in secrets if "MACHINE_PASSWORD" in key][0]
print secrets[pwkey].rstrip("\0")

Passwordless Auth

Just in case you haven't noticed, now we're really entering the seventh circle of hell. I'm just gonna throw the words Kerberos, SASL and GSSAPI at you. If you're still with me and haven't died of a heart attack, you might even be ready for what's going to be next.

Passwords suck, everyone knows that. So suppose we have joined our box into the domain using a sensible smb.conf that specifies these options:

kerberos method = dedicated keytab
dedicated keytab file = /etc/krb5.keytab

and thereby gives us a keytab to use, we can tell ldapsearch to authenticate using Kerberos. This eliminates the need for passwords completely, because the machine already has everything it needs.

However, you will want to make sure,

  1. that the GSSAPI SASL modules are installed. On Debian(ish), apt-get install libsasl2-modules-gssapi-mit should do the trick. (If ldapsearch tries to use SASL/EXTERNAL instead of SASL/GSSAPI, the module isn't installed.)

  2. that your DC's name resolution works correctly -- forward and backward. You can check this like so:

    root@damien:~$ getent hosts dc.local.lan
    2001:6f8:108f:0:5054:ff:febc:c54a dc.local.lan
    root@damien:~$ getent hosts 2001:6f8:108f:0:5054:ff:febc:c54a
    2001:6f8:108f:0:5054:ff:febc:c54a dc.local.lan

    So, both entries have to look exactly the same, or else kerberos will fail with some very awkward messages. (Kerberos libraries have a knack for producing less than helpful error messages.)

Now, first of all, we shall authenticate to our domain. To do this, use the kinit command, telling it to authenticate using our machine account, getting the password from the keytab file:

root@damien:~$ kinit -k DAMIEN$
root@damien:~$ klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: DAMIEN$@LOCAL.LAN

Valid starting       Expires              Service principal
14.04.2015 20:27:03  15.04.2015 06:27:03  krbtgt/LOCAL.LAN@LOCAL.LAN
        renew until 15.04.2015 20:27:03

This looks like a good start: The domain sent us a Ticket-granting Ticket, which basically says "hey, I know you!". Now let's try querying the directory:

root@damien:~$ ldapsearch  -H 'ldap://dc.local.lan'  -b 'dc=local,dc=lan' '(sAMAccountName=svedrin)'
SASL/GSS-SPNEGO authentication started
SASL username: DAMIEN$@LOCAL.LAN
SASL SSF: 0

ldap_result: Can't contact LDAP server (-1)

Erm. What's this supposed to mean?

Well, this only means that for some reason, ldapsearch chose to use the wrong authentication mechanism and we need to give it just a little hint to use GSSAPI instead of GSS-SPNEGO:

root@damien:~$ ldapsearch -Y GSSAPI -H 'ldap://dc.local.lan'  -b 'dc=local,dc=lan' '(sAMAccountName=svedrin)'
SASL/GSSAPI authentication started
SASL username: DAMIEN$@LOCAL.LAN
SASL SSF: 56
SASL data security layer installed.

# svedrin, Users, local.lan
dn: CN=svedrin,CN=Users,DC=local,DC=lan
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: svedrin
whenCreated: 20150201113122.0Z
name: svedrin

Hey, there's our user again! So this time, we accessed the directory without even thinking about passwords.

As I said, we're wandering around circles of hell here. There are no helpful error messages if anything goes wrong, and there's a lot to go wrong which is not at all obvious. So I'm not sure how much of this stuff you'll actually want to use. (Maybe someday I'll even figure out how to get SPNEGO to work, too. So far, no helpful error messages.)

Still, enjoy!