Simple LDAP Authentication for NGINX

Written by Dominik Pantůček on 2024-08-15

ldapnginxpuppet

Apache Web Server users can setup HTTP authentication against LDAP by installing and configuring appropritate module. For NGINX users the life can get more complicated when they need such feature. However, there is an easy solution.


Although NGINX does not have the LDAP authentication module like nginx does not have LDAP auth module like Apache does, there is an easy way of achieving the same functionality. As NGINX can delegate authorization requests that may include user authentication to other services, all that is needed is a simple HTTP responder, that can parse the required HTTP request headers and provide appropriate HTTP response headers.

It is even possible to implement such service as very simple standalone TCP server written in POSIX.1 shell. Of course, such solution needs at least ldapsearch from the ldap-utils package of OpenLDAP. An example of a script processing single auth request can look like this:

#!/bin/sh
AUTHORIZATION=""
while read line ; do
  cline=$(echo "$line" | sed 's/[\r\n\x0a]//g')
  key=${cline%%: *}
  value=${cline#*: }
  if [ "$key" = "Authorization" ] ; then
    AUTHORIZATION=${value}
  fi
  if [ -z "$cline" ] ; then
    break
  fi
done
atype=${AUTHORIZATION%% *}
avalue=${AUTHORIZATION#* }
dvalue=$(echo "$avalue" | base64 -d)
username=${dvalue%%:*}
password=${dvalue#*:}
BASEDN="ou=Users,dc=example,dc=com"
if ldapsearch -H ldap://ldap.example.com:389 -b $BASEDN -D "uid=$username,$BASEDN" -w "$password" "uid=$username" >/dev/null 2>&1 ; then
  echo $username Auth OK >&2
  printf "HTTP/1.0 200 OK\r\n"
else
  echo $username Auth NOT OK >&2
  printf "HTTP/1.0 401 Unauthorized\r\n"
  printf "WWW-Authenticate: Basic realm=Example"
fi
printf "Content-Type: text/plain\r\n"
printf "\r\n"
printf "Hello World!\r\n"

For clarity the printf program from GNU Coreutils is used.

This script expects the HTTP request headers on its standard input and produces the HTTP response (headers and perhaps a body) as its standard output. The only question that remains is - how to turn this into a standalone service?

Let's assume the aforementioned script is available at /usr/local/bin/ldap-auth-cgi.sh.

With nc (netcat) version that can fork and execute scripts while keeping the original listen socket, well, listening, it is just a single line of another shell script:

#!/bin/sh
ncat -l 127.0.0.1 8888 -e /usr/local/bin/ldap-auth-cgi.sh -k

This was tested with OpenBSD's version of nc.

The script may be stored for example in /usr/loca/bin/ldap-auth-http.sh and run as SystemD service. With Puppet, such service is really easy to configure:

  'ldap-auth-http':
    exec: '/usr/local/bin/ldap-auth-http.sh'
    user: 'nobody'

And with this service up and running, configuring NGINX to delegate the authorization requests to perform authentication is (again with Puppet) very easy:

    locations:
      'ldap-auth':
        location: '/auth'
        location_cfg_append:
          proxy_pass: 'http://127.0.0.1:8888/'
          proxy_pass_request_body: 'off'
    location_cfg_append:
      auth_request: '/auth'

Yes, virtual location /auth proxying the requests to the tiny auth service and then just delegating all auth_requests to this virtual location. That is all what is needed and as they say: security requires simplicity - otherwise it is hard to tell what even the attack surface area may be.

Although today it was a simple one, hopefully someone will find this information useful when they need to quickly secure access to NGINX websites. See you next time!