Actions: | Security

AllGoodBits.org

Navigation: Home | Services | Tools | Articles | Other

HTTP Authentication with nginx and LDAP

Currently (mid-2012, that is 1.2.x), nginx does not have stable, built-in support for much in the way of authentication options. There is HTTP Auth Basic, and there are some standard modules for Auth Digest and Auth PAM, and even supposedly a Pubcookie module that seems to have disappeared from the Net. But as is so often the way, I need something else. I need to be able to authenticate against a directory server using LDAP. Well, more or less LDAP; this is actually Active Directory, but for this purpose it's close enough.

There is a 3rd party module, nginx-auth-ldap that does the business, but I found it rather undocumented, so here is some brief material about how to get it working.

Build & Install

The nginx-auth-ldap module is not yet commonly distributed, so there is just a little more to it than just yum install nginx or `` apt-get install nginx`` that you likely used to install nginx itself. In general, it's easy to build additional modules for nginx: pass an additional --with-<nginx_module_name> parameter to the configure script in your build process, for example:

./configure --prefix=/usr/local \
--with-http_stub_status_module \
<more options>

In this case, it's not distributed with the nginx source tarball, so we need to tell it where to find the code. Given that I unpacked the module tarball from the same directory that I unpacked the nginx source, I canjust pass this to configure:

--add-module=../ngx_http_auth_ldap_module-1.0-a3

Modifying a specfile to build a custom rpm

I think that, for most environments, it's good practice to package software that will be installed, as part of the process for making servers as easy to reproduce as possible. Therefore I created a package for the system in question: CentOS6. Modifying the specfile from the distributed in the source rpm at http://nginx.org/packages was almost trivial. First I needed to do was to append the --add-module to the configure line as above, and tell the specfile how to get the additional source:

Source8: https://nginx-auth-ldap.googlecode.com/files/ngx_http_auth_ldap_module-1.0-a3.tar.gz
 %setup -D -q -T -b 8

That second line is an additional %setup command, not a replacement. It's specifying that there is additional source to be unpacked (Source8, just because my starting source rpm already had Source0 through Source7) and that the 8th Source should be unpacked before changing directory. This is basically the necessary incantation for everything to be in the right place for a successful build. Details on building rpms from multiple source tarballs are in Maximum RPM, old but still valid.

Just for completeness, here's the patch to my specfile:

 @@ -18,6 +18,7 @@ Source4: nginx.conf
 Source5: nginx.vh.default.conf
 Source6: nginx.vh.example_ssl.conf
 Source7: nginx.suse.init
+Source8: https://nginx-auth-ldap.googlecode.com/files/ngx_http_auth_ldap_module-1.0-a3.tar.gz

 License: 2-clause BSD-like license
 %if 0%{?suse_version}
@@ -54,6 +55,7 @@ not stripped version of nginx build with

 %prep
 %setup -q
+%setup -D -q -T -b 8

 %build
 ./configure \
@@ -88,6 +90,7 @@ not stripped version of nginx build with
         --with-ipv6 \
         --with-debug \
         --with-cc-opt="%{optflags} $(pcre-config --cflags)" \
+       --add-module=../ngx_http_auth_ldap_module-1.0-a3 \
         $*
 make %{?_smp_mflags}
 %{__mv} %{_builddir}/%{name}-%{version}/objs/nginx \
@@ -123,6 +126,7 @@ make %{?_smp_mflags}
         --with-file-aio \
         --with-ipv6 \
         --with-cc-opt="%{optflags} $(pcre-config --cflags)" \
+       --add-module=../ngx_http_auth_ldap_module-1.0-a3 \
         $*
 make %{?_smp_mflags}

Determining LDAP configuration

I had a little trouble working out what the exact search filter should look like since I do not run the Active Directory server. The parameters required will likely be slightly dependent on your LDAP Directory Server or Active Directory implementation, but it should be broadly similar. I started by experimenting with a direct connection using the commandline tool. This command was sufficient to get a response from AD, in LDIF, that described my account:

ldapsearch  -v -h 10.8.0.238 -b 'DC=example,DC=com' -D 'ldap@example.com'  -x -W '(&(objectCategory=person)(objectClass=user) (sAMAccountName=myUserName))'

So my initial attempt was to use a value for auth_ldap_url that looked like:

auth_ldap_url "ldap://10.8.0.238/dc=example,dc=com/?samaccountname?sub?(&(objectCategory=person)(objectClass) (sAMAccountName=myUserName))";

But that wasn't good enough; I was getting bad search filter errors. So I turned on some debugging.

Debugging nginx

If you're in a RedHat-based environment like CentOS and you have built rpms, you will have a package called nginx-debug. It is necessary to install this (which installs an nginx binary to /usr/sbin/nginx.debug) to get debugging information out of nginx, which is sometimes just what you need. There's nothing like looking at what your software is up to for determining where it's not doing what you want. Then you just need to tell it to log harder:

server {
  server_name my.example.com;
  error_log /var/log/nginx/my.example.com debug;
  <snip>
  ....
}

This gave me enough information to come up with the correct incantation to have nginx authenticate against this Active Directory server.

Configuring nginx to authenticate against LDAP

And finally, here's the nginx configuration necessary to authenticate/authorize against LDAP:

server {
  server_name test.example.com;
  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log debug;
  root  /srv/test.example.com;

  auth_ldap_url "ldap://10.8.0.238/dc=example,dc=com?samaccountname?sub?(objectClass=user)";
  auth_ldap_binddn "ldap@example.com";
  auth_ldap_binddn_passwd "mySpecialPassword";

  location /ldapprotectedspace {
         auth_ldap "Restricted Space";
         auth_ldap_require valid_user;
         auth_ldap_satisfy any;
  }
}

You can use various other restriction types:

auth_ldap_require group 'cn=admins,ou=group,dc=example,dc=com';
auth_ldap_require group 'ou=HR,dc=example,dc=com';
auth_ldap_satisfy all;