The auth_ldap Module

This is an authentication module for Apache that allows you to authenticate HTTP clients using user entries in an LDAP directory. The current stable version is 1.4.7. The latest version and the change log is always available at http://www.rudedog.org/auth_ldap/.

auth_ldap supports the following features:

Table of Contents

Building the Module

The module is compatible with Apache 1.3.x. To use the SSL extensions, you must use the Netscape SDK. Note that the Mozilla SDK is not the same as the Netscape SDK, and does not support SSL.

Under Unix, auth_ldap can be built as a DSO (Dynamic Shared Object) using Apache's apxs program. You should be able to build it using the old-style Configure method, but I don't personally use that method, so I can't guarantee support for it. The directions in this section apply to building auth_ldap as a DSO.

Note that a separate makefile has been provided for building under Windows NT with Visual C++ 5.0.

  1. Edit Makefile or Makefile.NT to customize it for your site. Make sure that you select the appropriate SDK, and turn on or off SSL. Remember, you need the Netscape SDK to use SSL.
  2. Run make (Unix) or make -m Makefile.NT (Windows NT), which should compile the DSO.
  3. Under Unix, run make install to install the module with the rest of your Apache modules. Under Windows NT, you will have to copy the DLL by hand into the directory with the rest of your modules.
  4. To load the module, add an appropriate LoadModule directive to your httpd.conf file, such as
      # For Unix
      LoadModule   auth_ldap_module    libexec/auth_ldap.so
    
      # For Windows NT
      LoadModule   auth_ldap_module    modules/AuthLDAP.dll

Note that this module has not been extensively tested under Windows NT, as I do not have easy access to an NT box. Because Apache on NT is threaded, there are a lot of concurrency issues. I think that I have addressed most of the concurrency issues, although the locks are pretty coarse grained. If you have any experiences, good or bad, with the module under Windows NT, please let me know, so I can improve these instructions and the module. I'm especially open to receiving code improvements for NT.

Operation

auth_ldap uses the following algorithm to authenticate a user

  1. Bind to the server using the bind DN and password provided by the directives (or anonymously if no bind DN is provided). If the bind fails, access is denied.
  2. Generate a search filter based on parameters passed to the module, and the username passed by the HTTP client.
  3. Perform a search using the generated filter. If the search returns exactly one entry, get that entry's distinguished name (DN), otherwise deny access.
  4. Attempt to bind to the LDAP server using the DN retrieved in the previous step, and the password passed by the HTTP client. If the bind is successful, then grant access (subject to the restrictions of the require directives), otherwise deny access.

Module-Specific Directives

The following directives are used by the module.

AuthLDAPBindDN

Syntax: AuthLDAPBindDN distinguished-name
Context: directory, .htaccess
Override: AuthConfig
Status: Extension
Module: auth_ldap

An optional DN used to bind to the server when searching for entries. If not provided, auth_ldap will use an anonymous bind.


AuthLDAPBindPassword

Syntax: AuthLDAPBindPassword password
Context: directory, .htaccess
Override: AuthConfig
Status: Extension
Module: auth_ldap

A bind password to use in conjunction with the bind DN. Note that the bind password is probably sensitive data, and should be properly protected. You should only use the AuthLDAPBindDN and AuthLDAPBindPassword if you absolutely need them to search the directory.


AuthLDAPAuthoritative

Syntax: AuthLDAPAuthoritative < on(default) | off >
Context: directory, .htaccess
Override: AuthConfig
Status: Extension
Module: auth_ldap

Set to off if this module should let other authentication modules attempt to authenticate the user, should authentication with this module fail. Control is only passed on to lower modules if there is no DN or rule that matches the supplied user name (as passed by the client).


AuthLDAPURL

Syntax: AuthLDAPURL url
Context: directory, .htaccess
Override: AuthConfig
Status: Extension
Module: auth_ldap

An RFC 2255 URL which specifies the LDAP search parameters to use. The syntax of the URL is

      ldap://host:port/basedn?attribute?scope?filter
ldap For regular ldap, use the string ldap. For secure LDAP, use ldaps instead. Secure LDAP is only available if auth_ldap was compiled with SSL support.
host:port

The name/port of the ldap server (defaults to localhost:389 for ldap, and localhost:636 for ldaps). To specify multiple, redundant LDAP servers, just list all servers, separated by spaces. auth_ldap will try connecting to each server in turn, until it makes a successful connection.

Once a connection has been made to a server, that connection remains active for the life of the httpd process, or until the LDAP server goes down.

If the LDAP server goes down and breaks an existing connection, auth_ldap will attempt to re-connect, starting with the primary server, and trying each redundant server in turn. Note that this is different than a true round-robin search.

basedn The DN of the branch of the directory where all searches should start from. At the very least, this must be the top of your directory tree, but could also specify a subtree in the directory.
attribute The attribute to search for. Although RFC 2255 allows a comma-separated list of attributes, only the first attribute will be used, no matter how many are provided. If no attributes are provided, the default is to use uid. It's a good idea to choose an attribute that will be unique across all entries in the subtree you will be using.
scope The scope of the search. Can be either one or sub. Note that a scope of base is also supported by RFC 2255, but is not supported by this module. If the scope is not provided, or if base scope is specified, the default is to use a scope of sub.
filter A valid LDAP search filter. If not provided, defaults to (objectClass=*), which will search for all objects in the tree. Filters are limited to approximately 8000 characters (the definition of MAX_STRING_LEN in the Apache source code). This should be than sufficient for any application.

When doing searches, the attribute, filter and username passed by the HTTP client are combined to create a search filter that looks like (&(filter)(attribute=username)).

For example, consider an URL of ldap://ldap.airius.com/o=Airius?cn?sub?(posixid=*). When a client attempts to connect using a username of Babs Jenson, the resulting search filter will be (&(posixid=*)(cn=Babs Jenson)).

See below for examples of AuthLDAPURL URLs.


AuthLDAPRemoteUserIsDN

Syntax: AuthLDAPRemoteUserIsDN
Context: AuthLDAPRemoteUserIsDN < off(default) | on >
Override:
Status:
Module:

If this directive is set to on, the value of the REMOTE_USER environment variable will be set to the full distinguished name of the authenticated user, rather than just the username that was passed by the client. It is turned off by default, which is consistent with the behavior of previous auth_ldap releases.


AuthLDAPCertDBPath

Syntax: AuthLDAPCertDBPath /path/to/cert7.db/directory
Context: server config
Override: Not Applicable
Status: Extension
Module: auth_ldap

Specifies in which directory auth_ldap should look for the certificate authorities database. There should be a file named cert7.db in that directory.


AuthLDAPCacheSize

Syntax: AuthLDAPCacheSize size
Context: server config
Override: Not Applicable
Status: Extension
Module: auth_ldap

Specifies the maximum size of the LDAP search cache. Set it to -1 to disable search caching, and set it to 0 to make it unlimited. The default size is 10KB. See the section below on caching for complete information on caching LDAP operations in auth_ldap.


AuthLDAPCacheTTL

Syntax: AuthLDAPCacheTTL time
Context: server config
Override: not applicable
Status: Extension
Module: auth_ldap

Specifies the time (in seconds) that an item in the search cache remains valid. The default is 600 seconds (10 minutes). A value of 0 means that items in the cache never go stale.


AuthLDAPOpCacheSize

Syntax: AuthLDAPOpCacheSize size
Context: server config
Override: Not applicable
Status: Extension
Module: auth_ldap

This specifies the size of the cache auth_ldap uses to cache LDAP operations. The default is 1024 entries. Setting it to -1 disables operation caching.


AuthLDAPOpCacheTTL

Syntax: AuthLDAPOpCacheTTL time
Context: server config
Override: Not applicable
Status: Extension
Module: auth_ldap

Specifies the time (in seconds) that entries in the operation cache remain valid. The default is 600 seconds.


AuthLDAPCacheCompareOps

Syntax: AuthLDAPCacheCompareOps < on(default) | off >
Context: server config
Override: Not applicable
Status: Extension
Module: auth_ldap

If this directive is set to on, auth_ldap will cache any compare operations (these are used to satisfy require user directives).


AuthLDAPFrontPageHack

Syntax: AuthLDAPFrontPageHack < off(default) | on >
Context: directory, .htaccess
Override: AuthConfig
Status: Extension
Module: auth_ldap

See the section on using Microsoft FrontPage with LDAP .

Other Apache Directives

require user

The require user directive can accept multiple users on a line if the attribute values don't have spaces in them. If the attribute values do have spaces, just put multiple require user directives, with one attribute value per line. Thus, for a AuthLDAPURL of ldap://ldap/o=Airius?cn, the following directives could be used

      require user Barbara Jenson
      require user Fred User
      require user Joe Manager

Note that with this configuration, Barbara Jenson could sign on as Babs Jenson or any other cn that she has in her LDAP entry. Only the single require user line is needed to support all values of the attribute in the user's entry.

If the uid attribute was used instead of the cn attribute, the above three lines could be condensed to

      require user bjenson fuser jmanager

require group

The require group directive takes the distinguished name of any LDAP entry that has either member or uniqueMember attributes. For example, assume that the following entry existed in the LDAP directory:

      
      dn: cn=Administrators, o=Airius
      objectClass: groupOfUniqueNames
      uniqueMember: cn=Barbara Jenson, o=Airius
      uniqueMember: cn=Fred User, o=Airius

The following directive would grant access to both Fred and Barbara:

      require group cn=Administrators, o=Airius

Examples

  1. Grant access to anyone who exists in the LDAP directory, using their UID for searches.
    	  AuthLDAPURL ldap://ldap.airius.com:389/ou=People, o=Airius?uid?sub?(objectClass=*)
    	  require valid-user
  2. The next example is the same as above; but with the fields that have useful defaults omitted. Also, note the use of a redundant LDAP server.
    	  AuthLDAPURL ldap://ldap.airius.com ldap1.airius.com/ou=People, o=Airius
    	  require valid-user
  3. The next example is similar to the previous one, but is uses the common name instead of the UID. Note that this could be problematical if multiple people in the directory share the same cn, because a search on cn must return exactly one entry. That's why it's a good idea to choose an attribute that is guaranteed unique in your directory, such as uid.
    	  AuthLDAPURL ldap://ldap.airius.com/ou=People, o=Airius?cn
    	  require valid-user
  4. Grant access to anybody in the Administrators group. The users must authenticate using their UID.
    	  AuthLDAPURL ldap://ldap.airius.com/o=Airius?uid
    	  require group cn=Administrators, o=Airius
  5. The next example assumes that everyone at Airius who carries an alphanumeric pager will have an LDAP attribute of qpagePagerID. The example will grant access only to people (authenticated via their UID) who have alphanumeric pagers:
    	  AuthLDAPURL ldap://ldap.airius.com/o=Airius?uid??(qpagePagerID=*)
    	  require valid-user
  6. The next example is the same as the above example, but also grants access to Joe Manager, who doesn't have a pager, but does need to access the same resource:
    	  AuthLDAPURL ldap://ldap.airius.com/o=Airius?uid??(|(qpagePagerID=*)(uid=jmanager))
    	  require valid-user

    This last example shows the power of filters, but may be confusing. It helps to evaluate what the search filter will look like based on who connects. The text in blue is the part that is filled in based on the attribute passed in the URL. The text in red is the part that is filled in based on the filter passed in the URL. The text in green is the information that is retrieved from the HTTP client. If Fred User connects as fuser, the filter would look like

         (&(|(qpagePagerID=*)(uid=jmanager))(uid=fuser))

    This search will only succeed if fuser has a pager. When Joe Manager connects as jmanager, the filter looks like

         (&(|(qpagePagerID=*)(uid=jmanager))(uid=jmanager))

    This search will succeed whether jmanager has a pager or not.

Caching in auth_ldap

For improved performance, auth_ldap will cache many LDAP operations. Caching can easily double or triple the throughput of Apache when it's serving auth_ldap-protected pages. In addition, the load on the LDAP server will be significantly decreased.

auth_ldap supports two types of LDAP caching: search caching and operation caching.

Search Caching

auth_ldap uses LDAP searches to map the user credentials passed during a basic authentication to a distinguished name in the directory. In other words, if client connects to server using the username bjensen, and the AuthLDAPURL directive contains ldap://ldap/o=Airius?uid, then auth_ldap will do an LDAP search operation on uid=bjensen on the directory tree rooted at o=Airius to map the username bjensen to an actual DN. This could take a significant amount of time, depending on the size of the directory and other factors. Search caching is used to cache the results of those searches.

Search caching is handled with the Netscape LDAP SDK's ldap_memcache functions or the OpenLDAP ldap_enable_cache function. Therefore, cache behavior could differ depending on the SDK used. For more information about the differences in caching, consult the appropriate SDK documentation or source code.

Operation Caching

The second type of caching is used to cache LDAP operations that aren't cached using the SDK caching routiones. auth_ldap will cache both bind operations and compare operations. Without bind caching, auth_ldap is forced to do three binds during the course of a single authentication. The first is to bind with the DN and password provided by the AuthLDAP directives. The second is to rebind using the credentials provided by the client, while the third is to rebind back to the original DN/password in order to do further operations in the directory. With bind caching, auth_ldap caches the results of the last bind for a particular DN, and will not attempt to rebind if the same credentials are presented to Apache. This can result in significant performance improvements.

auth_ldap will also cache some LDAP compare operations. auth_ldap uses compare operations to validate require user directives. For example, to use the situation above, assume auth_ldap has done a search for uid=bjensen and found a distinguished name. If there is a require user bjensen directive, then auth_ldap will use a compare operation to determine if uid=bjensen is valid for the retrieved distinguished name. If compare caching is turned on, then it will cache the results of the operation.

Note that while auth_ldap also uses compare operations to determine group membership in require group directives, it does not (yet) cache these operations.

Caching compare operations is the most memory intensive cache. It also seems to have the least impact on performance, and should be the first one to get turned off

Note that the AuthLDAPOpCacheSize directive only determines the initial size of the cache, but doesn't limit the size of the cache at all. Choosing too small a number will result in a seriously reduced cache efficiency. Choosing too large a number will waste some memory, but not much. It's much better to err on the large side when choosing the size. The default is 1024 entries, which should be sufficient to cache at least 3000 distinguished names.

Benchmarks

These benchmarks compare the performance for different cache configurations. They list the time to complete the benchmark, and the number of LDAP operations actually performed the LDAP server. All benchmarks were performed with Apache 1.3.4. Apache, the WWW client and the directory server all resided on the same system.

The first benchmark did 10,000 retrievals of the same page. The page was protected by a <Location> specification in access.conf that contained a single require user directive.

    LDAP Operations
Cache Level Real Time Connect Bind Search Compare Total
None 6:04 6 60,006 30,000 30,000 120,012
Search 4:53 6 60,006 6 30,000 90,018
Search, bind 2:20 6 18 6 30,000 30,030
Search, bind, compare 1:46 6 18 6 6 36

The second benchmark did 10,000 retrievals of the same page, but chose a random username from a pool of 1000 names for each connection. The page was protected by a <Location> specification in access.conf that contained a require valid-user directive. Since this doesn't require a LDAP compare operation, there is no benchmark for the third level of caching (search, bind and compare).

    LDAP Operations
Cache Level Real Time Connect Bind Search Total
None 5:47 7 60,002 30,001 90,009
Search 5:00 7 60,002 9,970 69,979
Search, bind 2:51 7 9,672 9,968 19,647

These benchmarks are only included to give an idea about how caching can improve performance. They do not really simulate real world conditions, since both servers and the client were in a sandbox.

Using SSL

auth_ldap will not talk to any SSL server unless that server has a certificate signed by a known Certificate Authority. Once you've built auth_ldap with SSL support, you have to tell auth_ldap where it can find a database containing the known CAs. This database is in the same format as Netscape Communicator's cert7.db database. I don't know what that format that is; the easiest thing to do is start up a fresh copy of netscape, and grab the resulting $HOME/.netscape/cert7.db file. If anybody can tell me how to create one of those databases myself, don't hesitate to drop me a line.

To specify a secure LDAP server, use ldaps:// in the AuthLDAPURL directive, instead of ldap://. auth_ldap does not support mixed secure and insecure servers. I can't really see a compelling reason to add support but anyone out there who wants it is welcome to try to convince me.

Using Microsoft FrontPage with LDAP

Normally, FrontPage uses FrontPage-web-specific user/group files (i.e., the mod_auth module) to handle all authentication. Unfortunately, you cannot just change to LDAP authentication by adding the proper directives, because it will break the Permissions forms in the FrontPage client, which attempt to modify the standard text-based authorization files.

If you are planning to use FrontPage with auth_ldap, you should compile auth_ldap with the -DAUTH_LDAP_FRONTPAGE_HACK. You could do this by adding it to the CFLAGS line in the Makefile.

Once you've created a FrontPage web, you can add LDAP authentication by adding the following directives to every .htaccess file that gets created in the web

      AuthLDAPURL            the url
      AuthLDAPAuthoritative  off
      AuthLDAPFrontPageHack  on

AuthLDAPAuthoritative must be off to allow auth_ldap to decline group authentication so that Apache will fall back to file authentication for checking group membership. This allows the FrontPage-managed group file to be used.

FrontPage restricts access to a web by adding the require valid-user directive to the .htaccess files. If AuthLDAPFrontPageHack is not on, the require valid-user directive will succeed for any user who is valid as far as LDAP is concerned. This means that anybody who has an entry in the LDAP directory is considered a valid user, whereas FrontPage considers only those people in the local user file to be valid. The purpose of the hack is to force Apache to consult the local user file (which is managed by FrontPage) - instead of LDAP - when handling the require valid-user directive.

Once you've added the directives as above, FrontPage users will be able to perform all management operations from the FrontPage client.

Caveats

Acknowledgements

Thanks to

Copyright

Copyright © 1998, 1999 Enbridge Pipelines Inc.
Copyright © 1999-2001 Dave Carrigan
All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Apache itself. This module is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose. The copyright holder of this module can not be held liable for any general, special, incidental or consequential damages arising out of the use of the module.


<dave@rudedog.org>
Last modified: Wed Feb 7 10:58:19 PST 2001