LDAP in Containers

Most of the time, connecting to LDAP is pretty straightforward and is just a matter of applying the right configuration to your application. Or maybe it isn’t even something you need to think about; it could be abstracted away behind an API call. This wasn’t always the case though. In several of my previous jobs, authentication wasn’t just a matter of submitting a username and password; I needed to setup and maintain the system that made that work, both for the server and its clients. Thankfully there was a ton of documentation and guides for making Linux work with LDAP. But what about LDAP in containers?

Times have changed and now we’re building containers, not really needing to worry about a lot of the details of Linux configuration. For the most part, we don’t need much from PAM (and even less from sssd) in containers. That said, sometimes you encounter software that just has to rely on your OS for authentication where LDAP sure comes in handy. Here I describe how to configure your Docker container to leverage LDAP via sssd for users and groups.

The LDAP Server

There are lots of options when choosing an LDAP server but most follow open standards. It shouldn’t matter a ton which you use, at least to experiment with LDAP in containers. Active Directory is a fairly obvious exception, though even that is pretty similar. I chose OpenLDAP, both because it is common and because I’ve crafted a demo Docker image out on Dockerhub for it. I think this should suffice as a stand-in for your company’s LDAP solution. I’ll be launching it as a part of a docker-compose file with a mostly-default configuration.

I’ll try to explain the various configuration options as I go, but I’ll be using the RFC2307 LDAP schema (also known as the “NIS” schema) and I’ll assume a pretty standard configuration for the LDAP server (normal port, STARTTLS, typical objectClasses, etc.). If your scenario is different and you get stuck, feel free to ask questions or leave comments.

Here’s a snippet from the docker-compose.yml for starting up the server:

The LDAP Client

While I’m certain that most of these steps have equivalents for other Linux distributions, I’m going to use CentOS 7 (based on the centos:7 Docker image). Beyond this, I’ll be using a pretty common package for wiring up Linux with LDAP: sssd. Most of the work for configuring this Docker image will be done on your machine.

The Problem

For the most part, the configuration of the LDAP client system will follow what you’ll find on typical guides like this one. In fact, this guide will be pretty simple in comparison. The main difference is that these guides are written from the perspective of a Linux VM or physical host, not for containers.

Linux, like every operating system, needs some mechanism to start and manage services, such as during startup or shutdown. For Linux and other Unix-like operating systems, this is called the init system. What the init system does and how it works has evolved quite a bit over the years, from the rc script (or collection of scripts), to the SysV init process with its runlevels, to systemd. Linux containers, however, work differently. Containers shouldn’t be thought of as full VMs; they are really wrappers around either a single or a very small number of processes. If the process(es) a container contains die, the container itself should die.

Because of this, containers don’t tend to have an init system (with the exception of things like tini but that is really getting into the weeds). Every guide I’ve been able to find for making Linux work with LDAP relies on managing sssd via your init system. I’ll demonstrate how to get sssd working within your container without the need for a proper init system (much less systemd, which most modern guides rely on).

What Is SSSD Anyway?

I’m not going to attempt a full rundown of how authentication works on Linux or all of SSSD’s capabilities, but it is worth describing why sssd exists and why we need it. Software that leverages PAM defers to it to authenticate users. PAM in turn can either leverage its own libraries for talking to LDAP (like the pam_ldap module) or it can work with an intermediary. Providing authentication is usually only one piece of the puzzle though, as most software also needs information about the user, such as full name, group information, and so on. This is what Name Service Switch (NSS) does. NSS can pull this information from LDAP through its own library (such as the nss_ldap module) or it too can work with an intermediary. There is often even a need for PAM to talk to NSS to authorize users based on their group memberships.

The sssd daemon provides that intermediary layer for both PAM and NSS. It can also provide things like caching, multiple domains/sources, supports kerberos, and is pretty flexible. Using sssd is the right way to get Linux working with LDAP, plus it’s actually a lot easier.

The Solution

We need sssd (and now we know why) and we can’t use an init script to run it. So how do we run it? This actually isn’t very difficult; assuming your configuration is in the default location, your container startup process just needs to include sssd before you run your normal code. Let’s build a working example.

The sssd.conf file

First, we need to prepare an sssd.conf file. This file describes how sssd will communicate with the LDAP server and will describe its behavior for things like caching. Pick a directory to store some files and create sssd.conf, giving it this content:

I’m not going to go through every line of this configuration since the man pages for sssd and sssd-ldap are pretty complete. Plus, this guide isn’t intended to be a complete primer on LDAP. I will talk about a few though:

  • ldap_schema = rfc2307 – This value needs to match the schema you’re using on your LDAP server. For the test LDAP server image I setup, this is what I’m using. Both rfc2307 and rfc2307bis rely on posixAccount for users, but for groups, rfc2307 uses the memberUid attribute with uid values (meaning the usernames are values). This differs from rfc2307bis which uses the member attribute and user dn values (meaning the user LDAP references are values). This difference is pretty critical as without it, you either need to change the schema of your LDAP server or your group memberships won’t work.
  • ldap_tls_reqcert = allow – This value, as described here, is fairly insecure. This works for development environment as it allows TLS to work, but it doesn’t validate the certificate presented by the server. It would be better to set this to demand, which both validates the certificate provided by the server (including its hostname) and requires that one be presented. If you do set this value to something more strict, be sure to set the ldap_tls_cacert value appropriately as well so the certificate can be verified.

Hopefully the rest of this configuration is pretty easy to follow, though it is pretty likely you’ll need to change some of the values you see in your actual environment. The values as listed above will work with the LDAP test server.

The test Dockerfile

Now let’s setup the test image. Create a file called Dockerfile and give it this content:

There isn’t much going on with this. This installs sssd, copies in our sssd.conf file from above, and sets up a startup script.

Configuring PAM

We’ll want to make it so PAM allows SSD users to authenticate. You can do this on an individual service basis through a service-specific PAM config, or you can do it system-wide. In most cases, system-wide is probably what you want. To do this on CentOS/RHEL, you could use authconfig --enablesssdauth --update, though that requires installing another package into our container (and it would make this guide awfully RedHat-specific), so I’d rather avoid it. We really only need to add one line into two files. On CentOS/RHEL, those files are /etc/pam.d/system-auth and /etc/pam.d/password-auth. Both these files require the addition of auth sufficient pam_sss.so use_first_pass at line 6. Thankfully, sed makes this a piece of cake:

Afterwards, the first 7 lines of our file should look like:

We’ll make this happen automatically in our startup wrapper script, which we’ll work on now.

The startup script

This script won’t contain much. The point is to be a wrapper around whatever your Docker container would have done before, with the addition of running the sssd daemon.

Create a file called startup.sh with content like the following:

Obviously replace run_my_awesome_app.sh with whatever command your container was going to run.

As you can see, we need to make sure that sssd is run before your application. The command will automatically run in the background, meaning it won’t get in the way of the rest of your script. A side effect of running sssd this way is that when the container shuts down, it doesn’t properly stop sssd. This means it will leave its “pid file” laying around. Adding the rm -f line just before launching sssd ensures that the file is gone and allows sssd to start.

You can also see our two sed lines there for configuring PAM.

Testing it all out

Now that we have all the pieces, we can bring them all together. Here’s a docker-compose.yml file that can be used to run this test contain and LDAP:

From here, run docker-compose -p ldaptest build, then docker-compose -p ldaptest up and the servers are up and running. This will start the containers in the foreground so you’ll be able to see the logs.

To verify that things are working, open a new terminal and attach to the testbox, then run a few commands to confirm that things are connected. To attach, run docker exec -it ldaptest_testbox_1 /bin/bash. From there, you can run things like getent passwd, which should output something like:

You can confirm that LDAP groups work too with getent group, which should include output containing:

If you see the above output, you’re all connected to the test LDAP instance!

Further Reading

To see the specifics of how the LDAP server is configured, check out the GitHub repo.

To learn more about LDAP, check out the LDAP Wiki.

Leave a Reply