Securing Apache HTTPD with Microsoft Active Directory

Recently, I was building a website with documentation for one of the projects I’m involved with. I wanted to protect access to that website to a specific set of people inside my company. Here’s how I did it.

Context

The website is built with Hugo, which generates static content. Initially, nginx (a lightweight webserver) made the content browseable.

Problem

The website was secured through a firewall to restrict outside access to only project members, based on membership of an Active Directory (AD) group. But internally, it was visible to everyone, because the firewall only protects traffic from the outside. This didn’t work well, so I went for a different solution: place the authentication and authorization in the webserver. Unfortunately, nginx can’t do this for you, unless you’re willing to spin up some extra Python applications.

Solution

I didn’t feel too familiar with this approach, so decided to look for another solution. It turns out that Apache HTTPD supports LDAP out of the box, and lets you authenticate and authorize users with it.

Guide

This guide assumes the Alpine-based Apache HTTPD 2.4 Docker-container. If you’re running HTTPD outside Docker, or in another operating system or distribution, some file paths may be a little different.

The main configuration file for HTTPD is httpd.conf. The first thing you need to do is tweak httpd.conf in two things:

Allow .htaccess files to override authentication and authorization methods for a directory

To do this, look for <Directory "/usr/local/apache2/htdocs>. It designates a set of configuration directives that apply to this particular folder. A few lines lower, you’ll find AllowOverride none; change that to read AllowOverride AuthConfig.

Enable LDAP integration

Create a new file called ldap.conf in /usr/local/apache2/conf/ with the following content:

LoadModule ldap_module modules/mod_ldap.so
LoadModule authnz_ldap_module modules/mod_authnz_ldap.so

As long as things don’t work (yet) or whenever you want to troubleshoot, you could add the following line:

LDAPLibraryDebug 7

This will generate a lot of debugging output from the LDAP library.

Restart HTTPD before continuing.

Protecting a directory

As I said, I wanted to restrict access to a static website. During build of the Hugo website, the generated website is copied into /usr/local/apache2/htdocs/. Inside that directory, create a .htaccess file with the following content:

AuthType               Basic
AuthName               "Login with your Company ID"
AuthBasicProvider      ldap

The next part is configuring which AD server to connect to. Add the following:

AuthLDAPURL            "ldap://your.company.ad/DC=users,DC=company,DC=ad?userPrincipalName?sub"
AuthLDAPCompareAsUser  on
LDAPReferrals          off

Of course, the server address is different, and the part after the slash is the Base DN. This is the branch of the directory tree where searches should start from. The userPrincipalName is the attribute that will be searched using the username that the user enters. In my setup, the company has userPrincipalName mapped to the username@domain format. When people want to access the website, that is what they’ll need to enter, together with their password.

If your AD server doesn’t allow an anonymous lookup, you also need:

AuthLDAPBindDN         "website-service-account@company.ad"
AuthLDAPBindPassword   "y0ur-p4$$w0rd"

If you don’t feel happy placing your password in a configuration file which is readible to everyone, the AuthLDAPBindPassword also allows to inject it dynamically. If the password starts with exec:, the command that follows will be executed, and the first line of output is used as the password. I had some troubles using this, as it seems this command has a different shell environment than I expected. For example, I needed to give the full path to each executable (maybe $PATH was not what I expected) and some environment variables were not accessible. I ended up by adding a injecting one file in the container and having my AuthLDAPBindPassword read exec:/bin/cat /usr/local/apache2/conf/AD_BIND_PASSWORD.

Now, this still doesn’t do anything yet. We need to add a requirement to users who want to access this directory:

Require valid-user

Which just requires a user to be authenticated using AD credentials (username and password). Now you can go ahead and test whether everything is setup succesfully. As long as you log on with your AD credentials, you should see the webpage, otherwise you should get a 401 Unauthorized page.

If you get 500 Internal Server Error, you should check the HTTPD error log.

Enforcing AD group membership

Until now, security hasn’t improved much: we are still in the situation where any company employee could access the website. To change this, we edit .htaccess once more, and replace the line that reads Require valid-user:

<RequireAny>
Require ldap-group CN=ProjectMembers,OU=IT,DC=users,DC=company,DC=ad
</RequireAny>

Here, the name of the AD group is something you might need to look up. I put the requirement inside , which makes it easier to grant access to multiple groups later on. There are also <RequireAll> and <RequireNone> directives. And the best thing: you can nest all of them them to build complex authorization logic.

Enable TLS-based communication with the AD server

Finally I wanted the communication between the webserver and the AD server to be secured. Our AD server uses an server certificate that is signed by an internal CA, so it will not be trusted by a default Linux installation. To fix this, I took the CA certificate that signed the AD server certificate, base64-encoded it and copied it to /usr/local/apache2/conf/ca.crt.

In ldap.conf, you need to add the following:

LDAPTrustedGlobalCert CA_BASE64 /usr/local/apache2/conf/ca.crt

And in .htaccess, change the AD server address to start with ldaps instead of ldap. This will automatically enable TLS server certificate validation.

Conclusion

In a few simple steps, we can use Apache HTTPD to authenticate and authorize users based on AD group membership. By leveraging the central user management of AD, we don’t need to worry about usernames or passwords in our website anymore.