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:
1 2 3 4 5 6 7 8 9 |
services: ldap: image: jgnagy/testldap:latest mem_limit: 536870912 # 512M privileged: true ports: - "389:389" - "636:636" |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
[sssd] domains = default config_file_version = 2 services = nss, pam [domain/default] id_provider = ldap access_provider = simple ldap_schema = rfc2307 ldap_search_base = dc=planetexpress,dc=com ldap_id_use_start_tls = True ldap_tls_reqcert = allow ldap_uri = ldap://ldap:389 ldap_default_bind_dn = cn=admin,dc=planetexpress,dc=com ldap_default_authtok_type = password ldap_default_authtok = GoodNewsEveryone cache_credentials = False entry_cache_timeout = 0 enumerate = True ldap_user_search_base = ou=people,dc=planetexpress,dc=com ldap_group_search_base = ou=unixgroups,dc=planetexpress,dc=com ldap_user_name = uid ldap_user_home_directory = homeDirectory ldap_user_object_class = posixAccount ldap_group_object_class = posixGroup ldap_group_member = memberUid [nss] filter_users = root, ldap, named, avahi, haldaemon, dbus, radiusd, news, nscd |
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. Bothrfc2307
andrfc2307bis
rely onposixAccount
for users, but for groups,rfc2307
uses thememberUid
attribute withuid
values (meaning the usernames are values). This differs fromrfc2307bis
which uses themember
attribute and userdn
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 todemand
, 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 theldap_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:
1 2 3 4 5 6 7 8 9 10 11 12 |
FROM centos:7 RUN yum install -y sssd && yum clean all COPY sssd.conf /etc/sssd/sssd.conf RUN chmod 600 /etc/sssd/sssd.conf COPY startup.sh /startup.sh RUN chmod 744 /startup.sh CMD ["/startup.sh"] |
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:
1 2 3 |
sed -i '6iauth sufficient pam_sss.so use_first_pass' /etc/pam.d/system-auth sed -i '6iauth sufficient pam_sss.so use_first_pass' /etc/pam.d/password-auth |
Afterwards, the first 7 lines of our file should look like:
1 2 3 4 5 6 7 8 |
#%PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authconfig is run. auth required pam_env.so auth sufficient pam_unix.so try_first_pass nullok auth sufficient pam_sss.so use_first_pass auth required pam_deny.so |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/bin/bash # Wait a sec for OpenLDAP to start sleep 1 # Run sssd rm -f /var/run/sssd.pid sssd # Allow LDAP users to authenticate sed -i '6iauth sufficient pam_sss.so use_first_pass' /etc/pam.d/system-auth sed -i '6iauth sufficient pam_sss.so use_first_pass' /etc/pam.d/password-auth # ... continue on with whatever the container was going to do run_my_awesome_app.sh |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
version: '2' services: ldap: image: jgnagy/testldap:latest mem_limit: 536870912 # 512M privileged: true # Uncomment the port to access LDAP from your machine # ports: # - "389:389" testbox: build: context: ./ dockerfile: ./Dockerfile depends_on: - ldap links: - ldap: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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/spool/mail:/sbin/nologin operator:x:11:0:operator:/root:/sbin/nologin games:x:12:100:games:/usr/games:/sbin/nologin ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin nobody:x:99:99:Nobody:/:/sbin/nologin systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin dbus:x:81:81:System message bus:/:/sbin/nologin sssd:x:999:997:User for sssd:/:/sbin/nologin zoidberg:*:11107:100:John A. Zoidberg:/home/zoidberg:/bin/bash fry:*:11103:100:Philip J. Fry:/home/fry:/bin/bash professor:*:11106:100:Hubert J. Farnsworth:/home/professor:/bin/bash bender:*:11102:100:Bender Bending Rodríguez:/home/bender:/bin/bash hermes:*:11104:100:Hermes Conrad:/home/hermes:/bin/bash amy:*:11101:100:Amy Wong:/home/amy:/bin/bash leela:*:11105:100:Turanga Leela:/home/leela:/bin/bash |
You can confirm that LDAP groups work too with getent group
, which should include output containing:
1 2 |
linuxusers:*:1101:fry,leela,professor |
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.
One thought on “LDAP in Containers”
Comments are closed.