Work in progress

This site is in the process of being reviewed and updated.

Introduction

As an example of using Apache Directory's Kerberos provider, this lesson demonstrates Kerberos authentication to OpenLDAP. This uses the "SASL+GSS-API+Kerberos V5" mechanism. SASL GSSAPI allows Kerberos authentication to be used during LDAP Binds. Additionally, the GSSAPI mechanism can provide message integrity (checksums) and, optionally, message privacy (encryption). When using SASL message privacy, connections do not need SSL to protect communications. The "three-headed" Kerberos setup tested was (1) OpenLDAP clients (2) OpenLDAP server and (3) Apache Directory.

Getting Started

  1. Verify name resolution. Underlying libraries will automatically determine your host name. For this reason it is required that the hostname you wish to use appear first in your /etc/hosts file (if you are using /etc/hosts). Overloading an IP address with multiple hostnames is common in developer workstations, so checking that the host you wish to use is first in the list to resolve will save you a lot of time later!
    $ more /etc/hosts
    # Do not remove the following line, or various programs
    # that require network functionality will fail.
    127.0.0.1       localhost.localdomain   localhost
    10.0.0.2        ldap.example.com
    10.0.0.2        www.apache.org
    
  2. (OPTIONAL) Configure Wireshark. You may also find it useful to have Wireshark (formerly Ethereal) on hand. You can filter on 'kerberos' to view traffic between 'ldapsearch' and the KDC or on 'ldap' to view traffic between 'ldapsearch' and OpenLDAP.
    $ yum install wireshark-gnome
    
  3. Configure your host so that it knows where to get Kerberos tickets. On linux this is configured in '/etc/krb5.conf'. The minimum config file must list the default Kerberos realm and the location of at least one key distribution center (KDC). With ApacheDS, the KDC and LDAP server are the same, so we'll re-use our 'ldap.example.com' hostname here.
    [libdefaults]
     default_realm = EXAMPLE.COM
    
    [realms]
     EXAMPLE.COM = {
      kdc = ldap.example.com
     }
    
    [domain_realm]
     .example.com = EXAMPLE.COM
     example.com = EXAMPLE.COM
    
  4. Make sure you are using ApacheDS 1.5.1, which is currently (10-JUN-2007) only available from the HEAD of trunk in svn. (How to build the trunks)
  5. Open the server.xml for editing.
    $ cd <trunk>/server-main
    $ vi server.xml
    
  6. Enable the Kerberos protocol provider. By default, the LDAP protocol is enabled, but the Kerberos protocol is not. You may also change the Kerberos port so that Kerberos can bind if you're logged-in as a non-root user. If you change the default port of '88', you must change the KDC port in the krb5.conf, as well.
    <bean id="kdcConfiguration" class="org.apache.directory.server.kerberos.kdc.KdcConfiguration">
      <!-- Whether to enable the Kerberos protocol.                           -->
      <property name="enabled" value="true" />
      <!-- The port to run the Kerberos protocol on.                          -->
      <property name="ipPort" value="88" />
    </bean>
    
  7. (OPTIONAL) Change the ApacheDS LDAP protocol provider port. By default, the LDAP protocol is enabled. If the ApacheDS LDAP provider conflicts with OpenLDAP, you may change the ApacheDS LDAP port.
    <bean id="ldapConfiguration" class="org.apache.directory.server.ldap.LdapConfiguration">
        <!-- The port to run the LDAP protocol on.                              -->
        <property name="ipPort" value="10389" />
    </bean>
    
  8. Enable the KeyDerivationService. In contrast to the SIMPLE, CRAM-MD5, and DIGEST-MD5 SASL mechanisms, Kerberos authentication is based on symmetric keys. Since a user can't be expected to remember a symmetric key, there are "key derivation functions" that will produce symmetric key material based on the concatenation of the password, realm, and username. Any changes to the user's password must result in new keys being generated. Luckily, ApacheDS has the "KeyDerivationService" interceptor. This service will intercept any adds or modifications to the user's 'userPassword' attribute and generate keys. Service principals typically use random keys, so the interceptor will generate random keys when the special keyword 'randomKey' is used.
    <bean class="org.apache.directory.server.core.configuration.MutableInterceptorConfiguration">
      <property name="name" value="keyDerivationService" />
      <property name="interceptor">
        <bean class="org.apache.directory.server.core.kerberos.KeyDerivationService" />
      </property>
    </bean>
    
  9. Pre-load principals using an LDIF file. With the KeyDerivationService enabled, you should be able to use LDIFs or LDAP to configure principals on-the-fly. For this example, since the LDIF format is concise, we review some LDIF entries. You will find attached to this page an example LDIF. Download the LDIF and configure the 'ldifDirectory' in server.xml.
    <property name="ldifDirectory">
      <value>/path/to/kerberos-ldap-example.ldif</value>
    </property>
    
  10. Review the LDIF entries. The metaphor for Kerberos comes from the fact that it is "three-headed"; there is always a KDC principal, service principal, and user principal. All of these principals use the same objectClass'es. The attributes are the minimum to satisfy their respective schema, with the exception of the Kerberos schema. Because we are using the KeyDerivationService, we don't need to specify the Kerberos key, key types, or key version number (kvno); they are automatically added by the interceptor, which will also increment the kvno when the password changes. Looking at the LDIF file you'll see the ASL license, an organizational unit (ou) for our 'users' subcontext, and the following entries:

    Entry RDN

    Password

    Principal Name

    Description

    uid=hnelson

    userpassword: s3crEt

    krb5PrincipalName: hnelson@EXAMPLE.COM

    Our user principal. Note the user password.

    uid=krbtgt

    userpassword: randomKey

    krb5PrincipalName: krbtgt/EXAMPLE.COM@EXAMPLE.COM

    The KDC principal, with a random key.

    uid=hostldap

    userpassword: s3crEt

    krb5PrincipalName: ldap/ldap.example.com@EXAMPLE.COM

    The LDAP principal. Note the service password.

  11. (OPTIONAL) Enable logging in server-main/log4j.properties.
    log4j.logger.org.apache.directory.kerberos=DEBUG
    
  12. You are now ready to start the server. Upon startup, the server will load the entries from the LDIF.
    $ cd <trunk>/server-main
    $ ./apacheds.sh
    
  13. (OPTIONAL) You may need to stop the server and delete the database files as you're troubleshooting.
    $ rm -rf example.com/
    
  14. (OPTIONAL) Make a keytab for the LDAP service principal. In order for an LDAP to "run as" a given service principal, we must provision (export) a symmetric key for it to use. While a user principal typically uses a password, a service principal typically uses a random key. This random key is generated by the server and then exported for use by the service. This key is exported into a keytab file. Our LDAP service will automatically look for this keytab file based on the hostname. Because key export for ApacheDS is currently (10-JUN-2007) under heavy development, we won't export keys for this example. Instead, we'll take advantage of the fact that key derivation algorithms are standardized and we'll create a keytab based on the LDAP service principal from the LDIF we imported. We stated, above, that service keys are typically random. For this example, we have set the LDAP service to use a password, "s3crEt." By fixing the password like this, we can use Kerberos tools to make a keytab. When prompted, the password for the service is "s3crEt" (without the quotes). OpenLDAP will use the system keytab, located at /etc/krb5.keytab. Note that this file must be readable by the user that OpenLDAP is running as, typically 'ldap'. Service principals use the service name 'ldap', which is combined with the FQDN of the OpenLDAP server and the realm, to produce the full service principal name.

    KEYTABS CONTAIN YOUR PRINCIPAL'S KEYS. Keep them safe, like you would a private key for Apache HTTPD or your /etc/passwd file!

    $ ktutil
    ktutil:  addent -password -p ldap/ldap.example.com@EXAMPLE.COM -k 1 -e des-cbc-md5
    Password for ldap/ldap.example.com@EXAMPLE.COM:  s3crEt
    ktutil:  list
    slot KVNO Principal
    ---- ---- ---------------------------------------------------------------------
       1    1 ldap/ldap.example.com@EXAMPLE.COM
    ktutil:  wkt /etc/krb5.keytab
    ktutil:  quit
    
  15. (OPTIONAL) Sanity check: verify the presence of the server key in the keytab file.
    $ klist -5ke
    Keytab name: FILE:/etc/krb5.keytab
    KVNO Principal
    ---- --------------------------------------------------------------------------
       1 ldap/ldap.example.com@EXAMPLE.COM (DES cbc mode with RSA-MD5) 
    
  16. (OPTIONAL) Test the krb5.keytab. Use 'kinit' to test authentication with the key in the keytab. Instead of entering a password with 'kinit', you can also use a keytab file. In the following two commands, the first command specifies the location of the keytab to use. The second command doesn't specify the keytab, since we are using the system keytab, which defaults to /etc/krb5.keytab.
    $ kinit -k -t /etc/krb5.keytab ldap/ldap.example.com@EXAMPLE.COM
    $ kinit -k ldap/ldap.example.com@EXAMPLE.COM
    
    If 'kinit' returns with no message, it worked. Now, when you look in your ticket cache, you'll see default principal is set to the LDAP principal, who now has a TGT.
    $ klist -5fea
    Ticket cache: FILE:/tmp/krb5cc_0
    Default principal: ldap/ldap.example.com@EXAMPLE.COM
    
    Valid starting     Expires            Service principal
    02/18/07 20:01:36  02/19/07 20:01:36  krbtgt/EXAMPLE.COM@EXAMPLE.COM
            Etype (skey, tkt): DES cbc mode with RSA-MD5, DES cbc mode with RSA-MD5 
            Addresses: (none)
    
  17. (OPTIONAL) Destroy the LDAP service's credentials. Your default principal is now set to the LDAP key you just tested, so you need to destroy it.
    $ kdestroy
    $ klist -5fea
    klist: No credentials cache found (ticket cache FILE:/tmp/krb5cc_0)
    
  18. Request a ticket-granting ticket (TGT) using 'kinit'. If you have not already "logged in," you must request a fresh TGT. Without a TGT, 'ldapsearch', for example, will fail with error "No credentials cache found." Also, if you don't specify the user principal, kinit will guess the principal name based on the logged-in user and the realm configured in the krb5.conf.
    $ kinit hnelson@EXAMPLE.COM
    Password for hnelson@EXAMPLE.COM: <s3crEt>
    $ klist -5fea
    Ticket cache: FILE:/tmp/krb5cc_0
    Default principal: hnelson@EXAMPLE.COM
    
    Valid starting     Expires            Service principal
    02/18/07 20:02:23  02/19/07 20:02:22  krbtgt/EXAMPLE.COM@EXAMPLE.COM
            Etype (skey, tkt): DES cbc mode with RSA-MD5, DES cbc mode with RSA-MD5 
            Addresses: (none)
    
  19. Install OpenLDAP.
    $ rpm -qa | grep ldap
    nss_ldap-253-1
    openldap-servers-2.3.27-4
    openldap-2.3.27-4
    openldap-clients-2.3.27-4
    
  20. Install SASL for OpenLDAP. Note that there is a separate RPM for each SASL mechanism. If you are missing a SASL mechanism in the list of 'supportedSASLmechanisms' later in OpenLDAP, then you are likely missing the corresponding RPM.
    $ rpm -qa | grep sasl
    cyrus-sasl-plain-2.1.22-4
    cyrus-sasl-lib-2.1.22-4
    cyrus-sasl-md5-2.1.22-4
    cyrus-sasl-2.1.22-4
    
    $ yum install cyrus-sasl-gssapi
    
  21. Select schema. Put any additional schema you require in /etc/openldap/schema.
  22. Setup slapd.conf.
    $ cd /etc/openldap
    $ vi slapd.conf
    
    For testing the following slapd.conf access control entry lets us test with an admin user using username/password authentication, as well as a user that will authenticate using Kerberos credentials.
    # The admin dn has full write access
    access to *
            by dn="cn=admin,dc=example,dc=com" write
            by dn="uid=hnelson,ou=users,dc=example,dc=com" write
            by * read
    
    suffix		"dc=example,dc=com"
    rootdn		"cn=admin,dc=example,dc=com"
    
    rootpw		{SSHA}mxFx2xhxlxQx4xyxaxax4xWxTxNxsxFx
    
    sasl-realm EXAMPLE.COM
    sasl-host ldap.example.com
    sasl-secprop noplain,noanonymous,minssf=56
    
    # This regular expression matches Kerberos principals to
    # the appropriate uid inside ou=users.
    sasl-regexp
              uid=(.*),cn=example.com,cn=gssapi,cn=auth
              uid=$1,ou=users,dc=example,dc=com
    
  23. Create the 'rootpw'. The 'rootpw' is created at the command line with 'slappasswd'.
    $ slappasswd
    New password: 
    Re-enter new password: 
    {SSHA}mxFx2xhxlxQx4xyxaxax4xWxTxNxsxFx
    
  24. Add data to OpenLDAP. Use 'ldapadd' to load data while the server is running. You can also use 'slapadd' when the server is stopped, but since we're only loading test principals, we'll use 'ldapadd'. 'ldapadd' requires you to bind as the rootdn and use the password you set in slapd.conf. These commands are useful for loading and modifying the example LDIF:
    $ ldapadd -f /path/to/kerberos-ldap-example.ldif -x -D "cn=admin,dc=example,dc=com" -w secret
    
    $ ldapmodify -f /path/to/kerberos-ldap-example.ldif -x -D "cn=admin,dc=example,dc=com" -w secret -c
    
  25. (OPTIONAL) Run OpenLDAP in test mode. You may wish to test OpenLDAP (slapd) by running it with options -d -1 and then trying SASL binds. The following command will let you save the debug output to a file that you can search during troubleshooting. For this exercise you will want to search for 'slap_sasl_bind'.
    $ slapd -d -1 2>&1 | tee /tmp/output.txt
    
  26. Test anonymous binds to the RootDSE. As a first test we will attempt to retrieve the 'supportedSASLMechanisms'. This command tests an anonymous bind, which works because the RootDSE allows everyone to read.
    $ ldapsearch -s base -LLL supportedSASLMechanisms -x
    
    Depending on the installed SASL RPMs, you should see something like the following:
    dn:
    supportedSASLMechanisms: GSSAPI
    supportedSASLMechanisms: DIGEST-MD5
    supportedSASLMechanisms: CRAM-MD5
    

    You must have GSSAPI listed!

  27. Test a simple bind. This command tests our rootdn, which simple authenticates with a username/password.
    $ ldapsearch -s base -LLL supportedSASLMechanisms -x -D "cn=admin,dc=example,dc=com" -w secret
    
  28. You should now be able to query OpenLDAP using Kerberos credentials. This will work because the user principal name, by way of the sasl-regexp configured in the slapd.conf, will match a test user from the loaded example LDIF. GSSAPI will use the Kerberos credentials (TGT) of the current user. GSSAPI supports the concept of "realm," but the realm is part of the username, eg 'hnelson@EXAMPLE.COM'. This is in contrast to other SASL mechanisms where the realm is separately and explicitly specified. Note that the hostname and port here are for the OpenLDAP server, not ApacheDS.
    $ ldapsearch -H ldap://ldap.example.com:10389 -b "dc=example,dc=com" "(uid=hnelson)" -Y GSSAPI
    $ ldapsearch -H ldap://ldap.example.com:10389 -b "dc=example,dc=com" "(objectclass=*)" -Y GSSAPI
    
  29. (OPTIONAL) List your Kerberos credentials. You'll see that in addition to a TGT, you also now have a service ticket for the LDAP server.
    $ klist -5fea
    Ticket cache: FILE:/tmp/krb5cc_0
    Default principal: hnelson@EXAMPLE.COM
    Valid starting     Expires            Service principal
    06/04/07 20:42:19  06/05/07 20:41:37  krbtgt/EXAMPLE.COM@EXAMPLE.COM
            Etype (skey, tkt): DES cbc mode with RSA-MD5, DES cbc mode with RSA-MD5
            Addresses: (none)
    06/04/07 20:42:22  06/05/07 20:41:37  ldap/ldap.example.com@EXAMPLE.COM
            Etype (skey, tkt): DES cbc mode with RSA-MD5, DES cbc mode with RSA-MD5
            Addresses: (none)
    
  30. (OPTIONAL) Use "quiet mode" (-Q) to suppress SASL logging.
    $ ldapsearch -H ldap://ldap.example.com:10389 -b "dc=example,dc=com" "(uid=hnelson)" -Y GSSAPI -Q
    $ ldapsearch -H ldap://ldap.example.com:10389 -b "dc=example,dc=com" "(objectclass=*)" -Y GSSAPI -Q
    
  • No labels