Sunday, December 14, 2008

Apache Authentication and Authorization using LDAP

The Lightweight Directory Access Protocol (LDAP) is frequently used to implement a centralized directory server. There are two popular open source LDAP solutions, OpenLDAP and Red Hat Directory Server. According to the Apache documentation, Novell LDAP and iPlanet Directory Server are also supported. This article focuses on OpenLDAP, but the concepts and examples should be applicable to the others.

note: originally published October 31, 2007 on linux.com

Information in OpenLDAP

LDAP was designed as a simplified, or "lightweight", version of the ITU-T X.500 directory specification. The default set of schemas contain all of the information you would find in traditional Linux system files like /etc/passwd and /etc/group, or Sun's Network Information System (NIS). The schemas are malleable and are often extended to contain additional demographic information or customized for specific applications.

Following is an example of a typical LDAP user record in LDAP Data Interchange Format (LDIF):

dn: uid=keithw,ou=People,dc=company,dc=com
uid: keithw
cn: Keith Winston
objectClass: account
objectClass: posixAccount
objectClass: top
objectClass: shadowAccount
userPassword: {crypt}$1$M/PZEwdp$KHjSay8JILX01YAHxjfc91
shadowLastChange: 13402
shadowMax: 99999
shadowWarning: 7
loginShell: /bin/bash
uidNumber: 2741
gidNumber: 420
homeDirectory: /home/keithw
gecos: Keith Winston

The LDAP database can be queried with a number of tools, including the command line ldapsearch program, one of the standard OpenLDAP utilities. If you are new to LDAP, the terminology and syntax may be difficult at first. Taking the time to learn the LDAP search syntax will pay off later if you want to craft advanced policies using non-standard attributes.

Configuring Apache 2.2

Apache modules have been available for LDAP since at least version 1.3. If you have used mod_auth_ldap in the past, you should be aware that the bundled authentication and authorization modules have been refactored in version 2.2. The latest LDAP modules are loaded with these directives, usually in the httpd.conf file:

LoadModule ldap_module /path/to/mod_ldap.so LoadModule authnz_ldap_module /path/to/mod_authnz_ldap.so

Once the modules are loaded, you can control access by querying the directory for particular attributes. The key directive to point Apache at the LDAP server is AuthLDAPUrl. A generic AuthLDAPUrl directive looks like this:

AuthLDAPUrl ldap://ldap.company.com/ou=People,dc=company,dc=com?uid

It defines the LDAP server, the base distinguished name (DN), the attribute to use in the search (usually Uid within the People organizational unit). For complex policies, you may need extra search filters.

The next few sections show working examples of directives to enforce common policies. Each set of directives can be placed in the main Apache configuration file or in .htaccess files.

Any valid user

This set of directives allows access to the current directory to all valid users in the LDAP directory. Apache will ask the browser for a user ID and password and check that against the directory. If you are familiar with Apache Basic Authentication, there are only a few new directives to learn.

Order deny,allow
Deny from All
AuthName "Company.com Intranet"
AuthType Basic
AuthBasicProvider ldap
AuthzLDAPAuthoritative off
AuthLDAPUrl ldap://ldap.company.com/ou=People,dc=company,dc=com?uid
Require valid-user
Satisfy any

AuthBasicProvider ldap is necessary so Apache knows to query an LDAP directory instead of a local file.

AuthzLDAPAuthoritative off must be explicitly set because the default setting is "on" and authentication attempts for valid-user will fail otherwise. This is a tricky setting because other policies, like Require ldap-user, need the setting to be "on". Setting this value off also allows other authentication methods to mixed with LDAP.

The Satisfy any directive is not strictly required in this case because we are only testing one condition.

List of users

This set of directives allows access to the current directory to the users listed in the Require ldap-user directive.

Order deny,allow
Deny from All
AuthName "Company.com Intranet"
AuthType Basic
AuthBasicProvider ldap
AuthzLDAPAuthoritative on
AuthLDAPUrl ldap://ldap.company.com/ou=People,dc=company,dc=com?uid
Require ldap-user keithw joeuser
Satisfy any

AuthzLDAPAuthoritative on could be omitted since the default setting is "on", but left here for clarity.

Note the AuthLDAPUrl setting does not change. As in previous examples, it searches the directory for a matching Uid.

Member of a group

This set of directives allows access to the current directory to users who are either primary or secondary members of the group specified in the Require ldap-group directive.

The group configuration may be the most difficult due to the schema design of directories that were converted from NIS (as mine was). Referring back to the user LDIF record, notice the gidNumber attribute has a value of 420, the number assigned to the infosys group in my directory. It corresponds to the primary group of the user. However, the LDAP entry for each group only lists users who are secondary members of the group, using the memberUid attribute. See below for a snippet of a group record:

dn: cn=infosys,ou=Group,dc=company,dc=com
objectClass: posixGroup
gidNumber: 420
memberUid: user1
memberUid: user2
memberUid: user3
...
We need another test, Require ldap-attribute, to pick up the primary users of the group because they are not listed with the group itself. Here are the Apache directives:
Order deny,allow
Deny from All
AuthName "Company.com Intranet"
AuthType Basic
AuthBasicProvider ldap
AuthzLDAPAuthoritative on
AuthLDAPUrl ldap://ldap.company.com/ou=People,dc=company,dc=com?uid
AuthLDAPGroupAttribute memberUid
AuthLDAPGroupAttributeIsDN off
Require ldap-group cn=infosys,ou=Group,dc=company,dc=com
Require ldap-attribute gidNumber=420
Satisfy any

AuthzLDAPAuthoritative on could be omitted since the default setting is "on", but left here for clarity.

AuthLDAPGroupAttribute memberUid indicates which attibute in the LDAP group record to match with the Uid. In this case, memberUid. A group record contains one memberUid attribute for each (non primary) member of the group.

AuthLDAPGroupAttributeIsDN off tells Apache to use the distinguished name of the client when checking for group membership. Otherwise, the username will be used. In my OpenLDAP directory, only the username was from NIS. The default setting is "on" so setting it off was required. An LDAP directory may store the entire distinguished name, so this setting may be change based on your directory.

Require ldap-group grants access to members of the "infosys" group. For multiple groups, add an additional directive for each.

Require ldap-attribute gidNumber=420 handles the primary users of group 420, the "infosys" group. Without this condition, primary users would be denied access. For multiple groups, add an additional directive for each.

The Satisfy any directive is required because we are testing multiple conditions and want the successful test of any condition to grant access.

Combination of users and groups

This example is a union of the user and group directives, but otherwise, there is nothing new.

Order deny,allow
Deny from All
AuthName "Company.com Intranet"
AuthType Basic
AuthBasicProvider ldap
AuthzLDAPAuthoritative on
AuthLDAPUrl ldap://ldap.company.com/ou=People,dc=company,dc=com?uid
AuthLDAPGroupAttribute memberUid
AuthLDAPGroupAttributeIsDN off
Require ldap-group cn=infosys,ou=Group,dc=company,dc=com
Require ldap-attribute gidNumber=420
Require ldap-user keithw joeuser
Satisfy any

Debug and deploy

Testing LDAP authentication from a web browser can be frustrating because the only thing you know is that access was granted or not. You don't get any kind of feedback on why something did not work. For verbose information on each step in the process, set the "LogLevel debug" option in Apache.

With debugging active, Apache will record the connection status to the LDAP server, what attributes and values were requested, what was returned, and why conditions were met or not met. It can be invaluable in fine tuning LDAP access controls.