In a corporate environment, getting a Windows Domain account is the most basic form of granting access to standard computing resources: file shares, printers and e-mail. As a result, Windows Active Directory authentication through domain controllers is the most common used form of authentication. Leveraging the existing domain accounts and groups for authenticating web access is a great way to simplify password management and centralize access control. This article describes how to setup an Apache webserver for user and group authentication against Windows domain controllers Active Directory LDAP, using the included standard Apache LDAP modules.

Verify LDAP connectivity

Before we start, lets verify network connectivity to our domain controllers LDAP port. Some of our webservers are behind firewalls that require the LDAP port opened. By default, the active directory LDAP service listens on TCP port 389.

fm@susie112:~> telnet 389
Connected to
Escape character is '^]'.
^CConnection closed by foreign host.

How to set up the domain controllers for LDAP is a different topic, out of scope for this how-to. However it is important to note that the standard LDAP setup over port 389 sends passwords in clear and enabling secure connections through LDAPS is recommended. For further information, see Microsofts knowledge base article KB321051.

After we verified the network port access, we can do a test query to the active directory LDAP. Here I am using a small Java program that binds to LDAP and returns the groups a given user belongs to.

fm@susie112:~> java ADTestQuery
Error: Missing Arguments.
Usage: java ADTestQuery      
Example: java ADTestQuery 389 pass DC=frank4dd,DC=com frank4dd

fm@susie112:~> java ADTestQuery 389 s3cur3ldap DC=frank4dd,DC=com frank4dd
Authentication Success!
Found Object: CN=frank4dd,OU=IT_Department
Found 1 attribute(s) for this object: memberOf
List attribute values for: memberOf
 0. CN=acl_secure_exchange,OU=Global Groups,OU=User,DC=frank4dd,DC=com
 6. CN=acl_security_audits,OU=Global Groups,OU=User,DC=frank4dd,DC=com
 7. CN=adm_Linux_PRD,OU=Global Groups,OU=User,DC=frank4dd,DC=com
Total groups: 8

Now we should set up a dedicated LDAP connection user system account. We will use this system account as a "bind" user for authentication queries because active directory LDAP limits anonymous binds to rootDSE searches. In this example I use the account "ldapconnect", set up as a normal domain user.

Enable Apache LDAP modules

The module setup can differ greatly depending on the operating system. In this example I refer to SUSE Linux, which is similar for OpenSuSE and the Novell SLES server version. There, the LDAP modules are part of the main Apache software package and no extra installation is necessary.

susie112:/home/fm # rpm -q apache2-2.2.10- -l |grep ldap
susie112:/home/fm #

The example above shows the file location on a 64bit system. If the 'yast-http-server' package is available, we can enable the LDAP modules through "Network Services"-"HTTP Server"-"HTTP Server Expert Configuration"-"Server Modules".

Apache LDAP modules control in SUSE Yast

I prefer the manual configuration over using Yast, enabling the module means editing /etc/sysconfig/apache2 to include the ldap modules in the APACHE_MODULES list to be enabled.

fm@susie112:/home/fm # vi /etc/sysconfig/apache2
# apache's default installation
# APACHE_MODULES="authz_host actions alias asis auth autoindex cgi dir imap 
 include log_config mime negotiation setenvif status userdir"
# your settings
APACHE_MODULES="apparmor actions alias auth_basic authn_file authz_host 
 authz_groupfile authz_default authz_user authn_dbm ldap authnz_ldap dir
 autoindex env expires include log_config mime negotiation setenvif ssl 
 rewrite status"

susie112:/home/fm # /etc/init.d/apache2 restart
Syntax OK
Shutting down httpd2 (waiting for all children to terminate)          done
Starting httpd2 (prefork)                                             done
susie112:/home/fm #

susie112:/home/fm # a2enmod -l
apparmor actions alias auth_basic authn_file authz_host authz_groupfile 
 authz_default authz_user authn_dbm ldap authnz_ldap dir autoindex env 
 expires include log_config mime negotiation setenvif ssl dav dav_fs 
 dav_lock rewrite status proxy proxy_http auth_certificate

Configuring basic LDAP connection settings for mod_ldap

First we configure the basic LDAP connection settings, best done in the main webserver configuration file. SuSE recommends to add customized settings like this to /etc/apache2/httpd.conf.local because /etc/apache2/httpd.conf might be overwritten by a Apache software package update.

fm@susie112:/home/fm # vi /etc/apache2/httpd.conf.local
# Enable the LDAP connection pool and shared
# memory cache. Enable the LDAP cache status
# handler. Requires mod_ldap and mod_authnz_ldap
# to be loaded.

LDAPSharedCacheSize 500000
LDAPCacheEntries 1024
LDAPCacheTTL 600
LDAPOpCacheEntries 1024
LDAPOpCacheTTL 600
# Wait x seconds before trying the next LDAP server in our list
LDAPConnectionTimeout 5

<Location /ldap-status>
  SetHandler ldap-status
  Order deny,allow
  Deny from all
  # restrict access only to mgt systems
  Allow from localhost 192.168.1

Apart from configuring LDAP cache settings, we are setting a connection timeout of 5 seconds. Together with the specification of multiple domain controllers (PDC and BDC's) in the next authentication configuration section, we achieve LDAP client failover in case of the active LDAP server failure. We also specify a mod_ldap status handler with restricted access rights. This handler will provide LDAP cache statistics through the http://server/ldap-status URL, very similar to mod_status. Monitoring this page very useful for high-traffic/large userbase sites to identify possible bottlenecks. Below is a example screenshot, click on it to see a saved example HTML output:

example screenshot for /ldap-status

Configuring LDAP authentication for a resource in virtual hosts

In this part, defining the AuthLDAPURL value is the most complex task because it combines many parameters into a single, nondescriptive error-prone line. Also, this is the only place that contains values that are Active Directory-specific: the baseDN must match the domain controllers setup, the search attribute and the objectclass definition must be set as shown. The example below shows a whole authentication setup for a directory resource in a virtual host. The comments are meant to make the configuration self-explaining.

fm@susie112:/home/fm # vi /etc/apache2/vhosts/myvirtualhost.conf
<Directory "/srv/www/ssl-root/restricted-directory">
  # Basic authentication with LDAP against MS AD
  AuthType Basic
  AuthBasicProvider ldap
  # AuthLDAPURL specifies the LDAP server IP, port, base DN, scope and filter
  # using this format: ldap://host:port/basedn?attribute?scope?filter
  AuthLDAPURL "ldap://,DC=com?sAMAccountName?sub?(objectClass=user)" NONE
  # The LDAP bind username and password
  AuthLDAPBindDN ""
  AuthLDAPBindPassword "ldaps3cUr3!"
  # we want to allow authentication only through LDAP, no fallback
  AuthzLDAPAuthoritative on
  AuthUserFile /dev/null
  # The name of this authentication realm
  AuthName "Restricted Dir [Domain Account]"
  # To authenticate single domain users, list them here
  #require ldap-user frank4dd 
  # to authenticate a domain group, specify the full DN
  AuthLDAPGroupAttributeIsDN on
  require ldap-group CN=acl_secure_exchange,OU=Global Groups,OU=User,DC=frank4dd,DC=com

The example above shows the authentication of a single ldap group. Note that the group CN is not enclosed in double quotes, despite having spaces in its name. If we need to add several groups, we can do it by repeating the 'require ldap-group' group statement. Now authentication succeeds if a user is member in any of the listed groups. If we have software that relies on the environment variable REMOTE_USER being set to a particular LDAP attribute, we can use AuthLDAPRemoteUserAttribute to set it specifically, to, say sAMAccountName".

 # make sure REMOTE_USER is set to sAMAccountName
  AuthLDAPRemoteUserAttribute sAMAccountName
  # to authenticate a domain group, specify the full DN
  AuthLDAPGroupAttributeIsDN on
  # specify all allowed LDAP groups below, one per line
  require ldap-group CN=acl_secure_exchange,OU=Global Groups,OU=User,DC=frank4dd,DC=com
  require ldap-group CN=adm_Linux_PRD,OU=Global Groups,OU=User,DC=frank4dd,DC=com

There have been several reports that Apache has trouble with referrals. With the apache module being linked against the libldap client library, some recommend to disable referrals through /etc/openldap/ldap.conf, setting the line "REFERRALS off". The problem seems to go away also if secure LDAP is enabled, see the next section.

Configuring secure LDAP: LDAPS

After the Active Directory LDAP has been configured for LDAPS using a certificate, small changes are necessary to convert our setup to use LDAPS, securing our connection with SSL. We need specify the location and format of the CA certificate that has been imported into Active Directory. BASE64_FILE defines the widespread PEM format. Let's not forget to change the AuthLDAPURL by adding the 's' to ldap, set the port to 636, then we should be good to go.

AuthLDAPURL "ldaps://,DC=com?sAMAccountName?sub?(objectClass=user)" NONE
LDAPTrustedCA /etc/apache2/ssl.crt/ca-bundle.crt

Links and Credits

See also: