For reasons I'll get into in a later post, I recently had need to deploy a krb5 KDC on top of LDAP. Normally for testing, I deploy on top of krb5's own datastore; this process is actually documented pretty well upstream. Barring that, I'm deploying using freeIPA which makes the process easy as well.

Once again

Don't do this. It's almost certainly not what you want. If you're setting up a new deployment, you should use freeIPA and free your mind to think about other things. These instructions are only intended for if you cannot pull in the dependencies of freeIPA, such as in a test suite.

Background

I'm going to assume as little familiarity with LDAP as possible, because I have very little familiarity with LDAP. So: LDAP is a directory access protocol. This means that it stores information in a rigid heirarchy, and is accessible using a standardized protocol (that features good security if you enable it).

Most often, LDAP stores users and user attributes. Here, we are going to use it to store specifically users, password information (krb5 key material), policy information, and everything else that krb5 can care about. However, by putting this information in LDAP, we can share it with other applications than just those that speak Kerberos. For instance, we can set up client machines to speak LDAP and fetch the users allowed to log in to the machine, rather than storing a user list locally. (This is beyond the scope of this document; a good project to look at is sssd.)

Setup LDAP

For this post, I'll be using OpenLDAP. There exist other servers (389ds, etc.) that are also good, but I am not going to weigh pros and cons here. So go ahead and install that.

slapd.conf

Next, we need to make some decisions. As in any Kerberos installation, you're going to need a realm name. For purposes of this, mine will be REALM.DNAME because I'm trying to illustrate something. More on that in a bit. You can pick anything here, but an uppercase version of your domain name is a good choice.

You also need to pick a password. I'm going to use "root". You should not use this password. You should pick a better one, and use the LDAP controls to store it outside the config file. I will not be doing this here for clarity.

So with that in mind, I'm going to show the resulting config file I'm using for Fedora and then indicate the changes for Debian:

include    /usr/share/doc/krb5-server-ldap/kerberos.schema
include    /etc/openldap/schema/core.schema
include    /etc/openldap/schema/cosine.schema
include    /etc/openldap/schema/inetorgperson.schema
include    /etc/openldap/schema/nis.schema

database   config
rootdn     cn=root,cn=config
rootpw     root

moduleload back_mdb
database   mdb
suffix     "dc=realm,dc=dname"
rootdn     "cn=root,dc=realm,dc=dname"
rootpw     root

For Debian, the schema file all live in /etc/ldap/schema instead. The exception is kerberos.schema, which is shipped in zipped form at /usr/share/doc/krb5-kdc-ldap/kerberos.schema.gz instead; you'll need to unzip it somewhere (I recommend /etc/ldap/schema) and then include it from there.

So, what does this file do? Well, first it includes all the database structures we need - that's the include lines. Then we define two databases - a magic configuration database "config", and an mdb database for our actual realm.

In order to go further, we need to drop some LDAP terminology. LDAP has a very verbose notion of names that comes from its understaning of heirarchy. And of course, because it's heirarchical, everything must be fully qualified; this is why it gets a reputation for verbosity. Do also note the splitting of our REALM.DNAME into seperate pieces.

  • A "dn" is a Distinguished Name, which is a fully qualified, unique reference to an entry. (So rootdn is the dn for the root user.)
  • A "dc" is a Domain Component, which we're using to locate the entry in an absolute heirarchy. This is why it is the realm in lowercase: the realm is related to the DNS entry, which is globally unique.
  • A "cn" is a Canonical Name, which is a name unique in that position in the heirarchy. So there can be two cn=root as long as they are not both in the same dc=....

All that is to say that the config database has root user named cn=root,cn=config, and the actual realm database has one named cn=root,dc=realm,dc=dname.

kerberos.ldif

Now we need to add two base objects to our LDAP setup. There are two ways to do this: online and offline (i.e., with slapd running, and without). I will be using the latter because it's easier to script.

And of course we need another config file. This time it's at least shorter:

dn: dc=realm,dc=dname
objectClass: domain
dc: realm

dn: cn=Kerberos,dc=realm,dc=dname
objectClass: krbContainer
cn: Kerberos

This is a series of instructions to LDAP. When we give the command, it will add two entries:

  • First, it will create a domain object within our realm object.
  • Second, it will create a krbContainer, into which we will place krb5 information.

So let's go ahead and perform those operations:

slapadd -f /path/to/slapd.conf -l /path/to/kerberos.ldif

and we're all set.

Start LDAP

This is actually enough to satisfy LDAP, so let's start that now:

slapd -d 0 -f /path/to/slapd.conf -h ldap://kerberos.realm.dname

of coure replacing kerberos.realm.dname with your actual hostname.

Note that I have started the server with ldap:// and not configured encryption. Do not do this in production.

Setup KDC

This is actually the easy part, though I may be biased.

stashsrvpw

First, we need to store the LDAP password somewhere the KDC can find it. You'll want somethign like:

kdb5_ldap_util stashsrvpw -w root -H ldap://kerberos.realm.dname -f /path/to/stashfile "cn=root,dc=realm,dc=dname"

(substituting hostname and realm name as above), and enter the password ("root", in my case) when prompted.

krb5.conf

Of course we need to set up a krb5.conf. It should include at least the following:

[libdefaults]
    default_realm = REALM.DNAME

[realms]
    REALM.DNAME = {
        kdc = kerberos.realm.dname
        admin_server = kerberos.realm.dname
    }

[domain_realm]
    .realm.dname = REALM.DNAME
    realm.dname = REALM.DNAME

all of which should is pretty standard.

kdc.conf

This is where things get weirder. We need something like this:

[dbmodules]
    REALM.DNAME = {
        db_library = kldap
        ldap_kerberos_container_dn = cn=Kerberos,dc=realm,dc=dname
        ldap_kdc_dn = cn=root,dc=realm,dc=dname
        ldap_kadmind_dn = cn=root,dc=realm,dc=dname
        ldap_service_password_file = /path/to/stashfile/you/made/above
        ldap_servers = ldap://kerberos.realm.dname
    }

substituting in the expected way. (We don't need much more than that because the KDC will read krb5.conf as well, though of course we can set plenty more.)

Brief explanation: we need to tell the varios krb5 components how to connect to LDAP. It is possible to have separate user accounts for the kdc, kadmin, etc., though I have not done so here.

Populate

Okay, almost done. We just have to tell Kerberos to populate a skeleton database from this configuration. Again we're going to use the LDAP tooling:

kdb5_ldap_util -H ldap://kerberos.realm.dname -D cn=root,dc=realm,dc=dname create -w root -s -r REALM.DNAME

where "root" is the LDAP password and all other substutions as before. It will prompt you for the KDC password as well; you'll want to come up with one.

This is the point at which new users should be added (using kadmin.local) and the ACL file should be configured. See the "Installing KDCs" documentation for this; it works the same way.

Pull the starter cable

Start the KDC with krb5kdc -n (depending on where your config files are, you may need to pass it additional flags) and kadmind with kadmind -nofork. This will keep them in the foreground for ease of debugging.

You're good to go

This is pretty much everything that's needed for a test setup. You can do many powerful things with additional configuration, but before doing that I recommend reading more about LDAP.

Hopefully this was helpful; if it could be more so, please let me know. In another post I'll go into what I was using the notes that became thi post for.