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:
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:
[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:
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:
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:
#%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:
#!/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:
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:
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:
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.
[…] If you’re looking to try working with LDAP in a Db2 container, start with his blog on LDAP in Containers. That will give you a basis for the rest of the content in this […]