Kerberos has two major weaknesses: first, the protocol for changing the password is not standardized (similar to the case with LDAP), and second, MIT Kerberos does not have intrinsic replication to the slave servers; you need to dump the master's database (kdb5_util), copy it to the slaves (kprop), and then update them. Heimdal does have realtime replication, and so does Microsoft Windows Active Directory, but we're using MIT Kerberos. I can't do much about the password issue, but there is a possibility for replication: use LDAP.
The first challenge is to identify the name of the package. On OpenSuSE 11.4 it is krb5-plugin-kdb-ldap . It is available from the SuSE Build Service, and has also been sent out in security update campaigns. On Ubuntu it is called krb5-kdc-ldap .
If you already have a functioning Kerberos realm, dump your database before altering your krb5.conf file, and save a copy of your database master key, whose default location is /var/lib/kerberos/krb5kdc/.k5.EXAMPLE.ORG , i.e. .k5 with the realm appended. Here is the command line to dump it. In this example, ./principal is the basename of the file-based database. You need dumps in both the default and old formats.
kdb5_util dump ./principal > db.dumpi
kdb5_util dump -ov ./principal > db.dumpiov
Beware! The dumped information is encrypted with the current database key. You must restore it to a database that uses the same key. This means that for disaster recovery you need to save the key (and other configuration information) with the dumps. Preferably everything should be encrypted with the system adminstrators' own role account key which is also recoverable in a disaster.
Specifically for this project, you are going to have to provide the plaintext master password when you create the database in LDAP. kdb5_ldap_util does not want to read it from a pre-existing stash file (despite hints in the man page that it should do so). You did save the plaintext password, didn't you? If not, it turns out to be satisfactory to just restore the master key file.
The admin guide chapter 6 tells how to set up the OpenLDAP backend. First you need to add LDAP-specific (and not so specific) sections to krb5.conf. See section 3.3.8 and 3.3.9 for details on these parameters. I'm converting an existing fully configured Kerberos to use the LDAP backend, so I've omitted from this list the sections that have to be set up for a new realm.
[realms]
Inside the stanza(s) for the realm(s) that your KDC
serves, you need to specify:
database_module = openldap_ldapconf
[dbdefaults]
Default values for some of the database specific
parameters. However, the KDC seems to have a compulsion to use the
file-based backend despite a default given here. I import them by:
database_module = openldap_ldapconf
database_module = openldap_ldapconf
This may appear inside {…} enclosed
stanzs in [realms], and at toplevel in [dbdefaults]; it names a
subsection (a stanza) in [dbmodules]
which should be used for database-specific parameters pertinent to
this stanza. The name openldap_ldapconf
is arbitrary but is
used in one of the howtos.
[dbmodules]
openldap_ldapconf = { … }
This stanza contains the following parameters (inside the { }). The name is referred to in the database_module parameter in [dbdefaults]. See section 3.3.8 or 3.3.9 (duplicates) for descriptions of the parameters.
db_library = kldap
Specifies the library (shared object, DLL) containing the backend.
db_module_dir
Presumably you could specifiy the directory containing this library if it differs from the compiled-in default. Not necessary for me since I'm using a vendor-provided package. The admin guide for krb5-1.6 does not mention this parameter.
ldap_kerberos_container_dn = "dc=example,dc=org"
All the Kerberos containers and leaf entries will be located under this domain container. The MIT howto shows this as the suffix of their DIT, but that seems troublesome to me since I have a complete LDAP database already. I'm going to use cn=Kerberos,dc=math,dc=ucla,dc=edu . (Update: yes, this works.)
ldap_kdc_dn = "cn=kdc,dc=example,dc=org"
This is the identity of the KDC and this name appears in an ACL giving it write permission on the Kerberos database. It is not an inetorgperson or posixAccount. Creating it directly under the suffix doesn't seem best to me; I plan to put it within the Kerberos container. (Update: yes, this works.)
ldap_kadmind_dn = "cn=kadmind,dc=example,dc=org"
This is the identity of the administrative daemon (including the password changing service), which appears in the ACL giving it write access to the Kerberos database. The Ubuntu instructions specify the same cn=admin for both these identities, but the MIT guide specifies separate names as shown here. Again, I plan to put it within the Kerberos OU instead of right under the suffix.
ldap_service_password_file = /etc/krb5kdc/service.keyfile
This is where you will stash the password for the KDC and kadmind (same file for both). For me a more appropriate location would be /var/lib/kerberos/krb5kdc/kdc-ldap-pwd . Yes, I back up this directory, encrypted.
ldap_server = ldapi://%2fvar%2frun%2fslapd%2fldapi/
A space separated list of LDAP URIs. I have a KDC and a LDAP server on each directory service machine, so I'm going to only specify the LDAPI URI. Between the second and third slashes you put the path to the UNIX domain socket, URL encoded, i.e. with slashes changed to %2f.
ldap_conns_per_server = 2
The KDC will pre-open this many connections to each server. The Ubuntu howto suggests to set it to 5, but given the size of my realms this seems grossly excessive.
You actually don't need to change anything in /var/lib/kerberos/krb5kdc/kdc.conf ; all I did was to adjust my maximum ticket life (max_life parameter). There are hints in wiki postings that some of the database-specific parameters can be in kdc.conf instead of krb5.conf.
You need to give the adminstrators permission to administrate. This is set in /var/lib/kerberos/krb5kdc/kadm5.acl . * means the principal has all permissions.
*/admin@EXAMPLE.ORG *
admin@EXAMPLE.ORG *
Now that you hae your flat configuration files set up, these are the steps to set up the database:
It is recommended (see DNS Discovery for MIT Kerberos 5) that you list your Kerberos KDCs (and everything else) in DNS SRV records even if flat files like /etc/krb5.conf also specify them. Windows Active Directory already does this. Under the domain matching the realm (e.g. example.org or math.ucla.edu), put a record of this form (one Kerberos example shown):
_${service}._${protocol} IN SRV ${priority} ${weight} ${port} ${FQDN}.
_kerberos._udp IN SRV 10 0 88 sunset.math.ucla.edu.
The fields are:
Set up your LDAP server and /etc/ldap.conf on the client (KDC machine). Trans-net you need SSL/TLS, but LDAPI (UNIX socket for a local connection), is not snoopable and does not need TLS (assuming your LDAP is configured to know this).
Load kerberos.schema . This schema adds to the posixAccount object the attributes krbPrincipalName, krbPrincipalKey, krbLastPwdChange, and krbExtraData. On OpenSuSE it can be found in /usr/share/doc/packages/krb5/kerberos.schema . SuSE provides a LDIF file for adding it to a LDAP server that uses cn=config (dynamic configuration), but it is radically broken. Use the slaptest method to get the cofiguration object and then insert that in your servers.
Jigger your ACLs in the DIT (payload database) giving access to a realm container for Kerberos to the KDC and kadmin services. Here are the ACLs given in the Admin Guide chapter 6.
Also the DIT needs an index on krbprincipalname and krbPwdPolicyReference .
You will need to substitute these items:
Edit the Distinguished Name to have the correct sequence number; 1 is often correct but not always.
Check the sequence numbers in olcAccess; it is probably
sufficient to use {0} (put it at the beginning),
but it mustn't go at the end because the ending access
to * by * read
would override their by * auth
(restrict read access only to Kerberos daemons).
The suffix of the database appears 5 places; change to your own suffix (dc=example,dc=org won't be appreciated). Specifically, the subtrees mentioned come from /etc/krb5.conf ldap_kerberos_container_dn and the dn's are from ldap_kdc_dn and ldap_kadmind_dn.
The admin guide chapter 6 shows a second ACL for the realm container; however, the first ACL has subtree scope and so should apply to the realm container. (Update: yes, this works.)
The admin guide shows the KDC only with permission to read
the principal objects. However, it also wants to update
(write) the last login time and some other attributes, so I've
granted that permission. Also, the admin guide ends the ACL
with by * none
, but I'm putting the user records for
the KDC and kadmind in the Kerberos container, so the users
before authentication need permission to enter the subtree to
authenticate.
Without the indices the server has to dig through every principal record for some operations, so they are important.
There is a crock in operation of the KDC: several times
while granting the TGT it has to make a list of all principals
whose krbPwdPolicyReference is the same as the principal who
is getting the ticket, which in a typical case will be all
of them. (I wonder if an index on krbPwdPolicyReference
would cut the work in half?) By default, search results are
limited to 500 entries, causing an error and preventing the
ticket from being issued if the site has more
than that many principals. (The default limit is 1000 for
Windows Active Directory LDAP.) Similarly, kadmind needs to
search for every principal in order to dump it. I've
added a olcLimits
attribute to allow unlimited search
results for both daemons.
The continuation lines start with 2 blanks. The first signals continuation and causes itself and the previous newline to be removed, and the second separates words in the value.
When executing the script, substitute the name of each server in turn, I actually used a LDAPI URI and used ssh to execute the script on the server, rather than making ldapmodify go across the net.
ldapmodify -x -D "cn=config" -y /etc/openldap/root.secret -H ldap://server.example.org/ -ZZ <<EOF dn: olcDatabase={1}hdb,cn=config changetype: modify add: olcAccess olcAccess: {0}to dn.subtree="cn=Kerberos,dc=example,dc=org" by dn.exact="cn=kdc,cn=Kerberos,dc=example,dc=org" write by dn.exact="cn=kadmind,cn=Kerberos,dc=example,dc=org" write by * auth - add: olcDbIndex olcDbIndex: krbprincipalname eq olcDbIndex: krbPwdPolicyReference eq - add: olcLimits olcLimits: dn.exact="cn=kdc,cn=Kerberos,dc=example,dc=org" size=unlimited olcLimits: dn.exact="cn=kadmind,cn=Kerberos,dc=example,dc=org" size=unlimited EOF
Continuing to actually create the Kerberos database in LDAP:
If you have an existing file-based database, you did dump it and saved the key, didn't you?
Create the database using this command line, executing as root. You do this once on the Kerberos master site (where kadmind will be running). If LDAP replication is working, the objects will be propagated to the LDAP slaves for use by the Kerberos slaves. ${xxx} refers to the value of the corresponding parameter in krb5.conf. ${RootDN} is the superuser of your DIT (LDAP directory database), which has write access to everything.
You will also need to specify the credential for ${RootDN}. I used
the motif -w "`cat /etc/openldap/root.secret`", hiss, boo. This has
the disadvantage that the password is visible if someone were to do
ps
while kdb5_ldap_util was running. Best to be lazy in this
way only on a server accessible only to sysadmins who already know
the LDAP root secret. It would be better if kdb5_ldap_util knew about
the -y parameter that ldap tools have, but it doesn't.
It's going to ask interactively for the plaintext password of the
database, the one it's now creating. If you have an existing Kerberos
realm, the right
way is to give it the password for the old
file-based one. If that's impossible, you can cheat: pretend it's
a new realm.
For a new realm, the LDAP Setup document has a procedure to generate a password that you can type in (or better, copy and paste). The encryption will use a 256 bit key, and for the password itself to match this strength you should ask for 53 (or slightly more) random bytes in the procedure's initial step.
kdb5_ldap_util -D ${RootDN} -w "`cat /etc/openldap/root.secret`" \
create -subtrees ${ldap_kerberos_container_dn} -r ${default_realm} -s
Example:
kdb5_ldap_util -D "uid=ldaproot,dc=example,dc=org" -w "`cat /etc/openldap/root.secret`" \
create -subtrees "cn=Kerberos,dc=example,dc=org" -r EXAMPLE.ORG -s
The command line arguments are: (see the man page)
Structure of the resulting LDAP entries:
If you need to remove your realm and do it over, this is the command line. If the kdc and kadmind objects are inside the Kerberos container, which is what I do, they and their container will not be removed.
kdb5_ldap_util -D ${RootDN} destroy -r ${default_realm}
Check that the LDAP entries created above are being replicated to all the slave LDAP servers, according to the style of replication that you use, so the slave KDCs can use them.
If the KDC and kadmin daemons are going to bind to LDAP, they need a password, and hence have to exist in LDAP so they can have that password. I used the procedure to generate a password mentioned previously and then, following along in the instructions, I hashed it and converted it to base64 form. (Being lazy, I used the same password for both daemons.) Here is the LDIF to create the simplest possible objects inside the Kerberos container:
#!/bin/sh ldapadd -x -D "uid=ldaproot,dc=example,dc=org" -y /etc/openldap/root.secret -H ldapi:/// <<EOF dn: cn=kadmind,cn=Kerberos,dc=example,dc=org cn: kadmind objectClass: simpleSecurityObject objectClass: organizationalRole description: Default bind DN for the Kerberos Admin Daemon userPassword:: e1NTQWertyuiopasdfGHJKL= dn: cn=kdc,cn=Kerberos,dc=example,dc=org cn: kdc objectClass: simpleSecurityObject objectClass: organizationalRole description: Default bind DN for the Kerberos KDC server userPassword:: e1NTQWertyuiopasdfGHJKL=
The KDC and kadmind (administrative daemon) bind to LDAP using their (shared) personal password, which is saved in a file. This is necessary so they will have read or write access to the Kerberos database, using their privileges in the ACL. This command line will set a new password for the given principal and save it in the stash file. It works to save both passwords in the same file. The filename comes from /etc/krb5.conf ${ldap_service_password_file}. Do this for each of ${ldap_kadmind_dn} and ${ldap_kdc_dn}.
Beware: I have my doubts whether it's changing the password in the daemon's LDAP entry. It's best to create the daemon's entry with the correct password preset. Also I had a great deal of trouble to provide the password; I suspect that a spurious newline was sneaking in. Be sure to test if you can bind.
kdb5_ldap_util -D ${RootDN} stashsrvpw ${ldap_kadmind_dn}
Example:
kdb5_ldap_util -D "uid=ldaproot,dc=example,dc=org" -w "`cat /etc/openldap/root.secret`" \
stashsrvpw "cn=kadmind,cn=Kerberos,dc=example,dc=org"
Command line arguments are: (see the man page)
Here's a command line to test if you can bind as the daemon. It just prints out its own user entry if successful. Beware of error 49, invalid credential. I'm assuming that you're doing this on a LDAP server and /etc/ldap.conf specifies a LDAPI (or LDAPS) URI. If not, you'll also need -ZZ to turn on TLS.
ldapsearch -x -D ${ldap_kadmind_dn} -y ${ldap_service_password_file} \
-b ${ldap_kadmind_dn} -LLL
Example:
ldapsearch -x -D "cn=kadmind,cn=Kerberos,dc=example,dc=org" \
-y /var/lib/kerberos/krb5kdc/kdc-ldap-pwd \
-b "cn=kadmind,cn=Kerberos,dc=example,dc=org" -LLL
Create the admin principal. I've managed to do without one, but the rjsystems HOWTO recommends it, so here is the procedure. Kadmin.local normally is interactive.
You probably also will want to set the ticket lifetime parameters. These are upper bounds on what the user can request. These should match the max_life and max_renewable_life parameters in kdc.conf.
kadmin.local
Authenticating as principal …
kadmin.local: addprinc admin
WARNING: no policy specified …
Enter password for principal "admin@EXAMPLE.ORG": #I'm duplicating the one for kdc and kadmind
Re-enter password for principal "admin@EXAMPLE.ORG":
Principal "admin@EXAMPLE.ORG" created.
kadmin.local: modprinc -maxlife "1 day" -maxrenewlife "7 day" krbtgt/EXAMPLE.ORG@EXAMPLE.ORG
Principal "krbtgt/EXAMPLE.ORG@EXAMPLE.ORG" modified.
kadmin.local: quit
The slave servers are supposed to be exact copies of the master, using the replicated LDAP service via LDAPI (UNIX sockets) on their own localhost. Make sure these files are synced on the slave servers. All but the first are in the KDC's directory, var/lib/kerberos/krb5kdc .
Now restart all the KDCs and the admin daemon (suggestion: one at a time, starting with the master KDC). When they have the right passwords in the right files, both daemons start right up. Look in the log files to see problem reports: /var/log/krb5/kdc.log and /var/log/krb5/misc.log (for kadmind).
KDC should be listening on UDP port 88, and kadmind should listen on TCP ports 749 (administration) and 464 (password changing). To verify this you can use nmap, or fuser.
fuser -v 88/udp
88/udp: root 28676 F.... krb5kdc
fuser -v 749/tcp
749/tcp: root 28675 F.... kadmind
fuser -v 464/tcp
464/tcp: root 28675 F.... kadmind
nmap -sT -sU server.example.org
88/tcp closed kerberos-sec (correct, this wasn't turned on)
464/tcp open kpasswd5
749/tcp open kerberos-adm
(and lots of others, but it misses 88/udp.)
Restore the database.
kdb5_util load -update db.dumpi
kdb5_util load -update db.dumpiov
Beware! The dump is encrypted with the database master key at the time of dumping. If you were unable to preserve the master database password, the restored credentials will be unreadable. It would then be best to not restore, but to create anew all of the principals.
If you did save the database key (password), i.e. .k5.${REALM},
replace the file written by kdb5_ldap_util create
with the
saved key.
When doing maintenance with kadmin or kadmin.local, e.g. adding users, you need to specify the user's Distinguished Name in at least the addprinc command, like this:
addprinc -x dn="uid=steve,ou=People,dc=example,dc=org" steve
Specify -x dn=… when the user's entry already exists. If you do not specify it, kadmin will attempt to create a new entry.