Tuesday, December 28, 2010

LDAP authentication for Apache HTTP Server

As part of my OpenLDAP under Ubuntu Linux project, this post documents configuring the Apache HTTPD Server to use LDAP for authentication and authorization. The Apache HTTPD Server will simply be referred to as "Apache" for the remainder of this post. As with my previous post on phpLDAPadmin, this post was written against Apache 2.2.16, and Ubuntu Linux's latest release, 10.10 ("Maverick Meerkat").

Before configuring Apache to accept credentials, ensure that sensitive pages and the prompts for credentials will only be accepted on SSL/TLS-protected sites, otherwise users' credentials will be passed over the network in clear-text where they may easily be compromised. (It doesn't make much sense to use SSL/TLS encryption between Apache and the LDAP server, if the communication between the web browser and the web server isn't protected.) Documentation for using SSL/TLS encryption for the Apache web server is readily available elsewhere (e.g. the "HTTPS Configuration" section under "HTTPD" in the Ubuntu Server Guide), and is beyond the scope of this post.

At least under Ubuntu Maverick, Apache is installed with the mod_authnz_ldap module, but it is not enabled by default. To enable, use "sudo a2enmod authnz_ldap", or manually create the symbolic links in "/etc/apache2/mods-enabled". (a2enmod is a Debian-specific script shared with Ubuntu.)

Next, add a <Location> directive into the Apache configuration that contains configuration directives as documented in mod_authnz_ldap. Assuming that only one LDAP directory will be used for the Apache instance, I just place most of the configuration under a root Location, like this:

<Location "/">
    AuthBasicProvider ldap
    AuthType Basic
    AuthName "example.com"
    AuthzLDAPAuthoritative off
    AuthLDAPURL "ldaps://ldap.example.com/dc=example,dc=com?uid?sub"
    AuthLDAPBindDN "cn=ldap.proxy,ou=serviceAccounts,dc=example,dc=com"
    AuthLDAPBindPassword "secret123"
</Location>

I need the bind distinguished name (DN) and password, as I have OpenLDAP configured to not allow anonymous binds, which is what Apache 2.2 defaults to if these are not specified. I don't particularly like the need for the proxy account, however, for security and maintenance reasons. I would think that ideally, Apache could just use the username and password of the authenticating user to perform the bind. If the user's bind fails, then access should be denied anyway. However, this would require Apache to know how to convert a username (e.g. "mark") into a DN (e.g. "cn=mark,ou=people,dc=example,dc=com") that is acceptable for the bind. As configured above, Apache currently determines this with a query performed using the specified bind credentials, searching for an LDAP entry where the uid (user identifier) attribute matches the user's entered username. Interestingly, it appears that "binding as the user" will be available in Apache 2.3 (currently in alpha), as seen in the 2.3 documents for AuthLDAPInitialBindAsUser and AuthLDAPInitialBindPattern. However, while sometimes beneficial, this method of translating the username is limited to string matching and substitution only (using regular expressions) as LDAP access is not yet available, so continuing to use a configured bind DN and password will still be more flexible for several scenarios.

Securing the LDAP queries with SSL/TLS is necessary to protect the credentials being passed over the network, as accomplished with the "ldaps://" AuthLDAPURL above. For testing or troubleshooting during initial setup, it can be temporarily replaced with simply "ldap://" to use unencrypted connections. Alternatively, if the LDAP server is operating on the same host as Apache, "ldapi:///" can be used (with the hostname ommitted) to use *NIX sockets, bypassing the IP layer entirely. Interestingly, while I can't find any documentation to support this (only Apache Bug # 44302 which reports that it wasn't working at least as of Apache 2.2.8 on 2008-01-27), "ldapi:///" is working for me as of this writing.

For SSL communications to be successful, the LDAP server's SSL certificate must be trusted by the calling server. This is currently accomplished by providing one or more trusted cerver certificates, and setting LDAPTrustedGlobalCert in the Apache configuration. If multiple certificates must be supported, they can simply be concatenated into a single file when using the Base64 / PEM (CA_BASE64) certificate format. Currently, this must be configured globally per Apache instance, not per directory or location. Alternatively, this can be set globally per server through the TLS_CACERT option in /etc/ldap/ldap.conf. Setting "TLS_REQCERT never" should also work, but this is not recommended for security reasons, nor does it appear to work currently.

Now, just add require directives as appropriate. Available options for LDAP include ldap-user, ldap-group, ldap-dn, ldap-attribute, and ldap-filter. ldap-user should be avoided for reasons of maintainability and security best practices. One of these directives may be placed at the same root <Location> block as above, or in child locations. (Per the Apache Configuration Sections documentation, <Directory> directives should be used over <Location> directives for access control of filesystem-based resources, as improper configurations may otherwise easily result in circumventions.) Either Location or Directory directives will inherit the above-configured Auth* directives from a parent.

For example, to protect phpLDAPadmin as previously configured:

  <Location "/phpldapadmin">
    Options +ExecCGI
    AddHandler fcgid-script .php
    FCGIWrapper /usr/lib/cgi-bin/php5 .php

    require ldap-group cn=ldap-admins,ou=groups,dc=example,dc=com
  </Location>

Finally, reload the Apache configuration ("sudo /etc/init.d/apache2 reload"), and test away!

1 comment:

hip0 said...

Thanks for article man. It really shed some light in my head. I was struggling here with LDAP auth from Apache for few hours :)