Using LDAP in Docker with caching

2021/03/22 :: #ldap #pam #ubuntu #docker


LDAP and Active Directory are both directory services. These are used extensively in corporate environments as authentication and authorization solutions. In a modern infrastructure, applications quite often run in a containerized environment, and sometimes these environments need to have access to LDAP or AD. In this article, we investigate how a Docker container can efficiently access these directory services.

Creating a base container

Ubuntu 18.04.5 LTS (Bionic Beaver) is a long-term release, and this is what we are going to use going forward. There must be a local user other than root for tasks like compiling code or installing NPM packages if necessary.

FROM ubuntu:bionic-20210222

RUN apt-get clean && apt-get update

RUN apt-get install --reinstall ca-certificates -y

RUN apt-get install sudo apt-transport-https ca-certificates -y

RUN useradd -m -u 5000 app || :
RUN echo 'app ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

RUN apt-get clean

USER app

Creating a new tag is simple:

sudo docker build . -t local/aloha-base:2021.03.23 --file docker/0/Dockerfile

Sending build context to Docker daemon  229.9kB
Step 1/9 : FROM ubuntu:bionic-20210222
 ---> 329ed837d508
Step 2/9 : RUN apt-get clean && apt-get update
 ---> Using cache
 ---> 3fd8e7b8ccdc
Step 3/9 : RUN apt-get install --reinstall ca-certificates -y
 ---> Using cache
 ---> d1d2a538daf2
Step 4/9 : RUN apt-get install sudo apt-transport-https ca-certificates -y
 ---> Using cache
 ---> bc8142001e3e
Step 5/9 : RUN useradd -m -u 5000 admin || :
 ---> Running in 9939670ed4aa
Removing intermediate container 9939670ed4aa
 ---> ca9a0f390db3
Step 6/9 : RUN useradd -m -u 5001 app || :
 ---> Running in 68cd99b566b5
Removing intermediate container 68cd99b566b5
 ---> 37c5b5146df9
Step 7/9 : RUN echo 'admin ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
 ---> Running in a82a060ba035
Removing intermediate container a82a060ba035
 ---> d3e527406739
Step 8/9 : RUN apt-get clean
 ---> Running in 9e4d7b7ac071
Removing intermediate container 9e4d7b7ac071
 ---> 117422e5341e
Step 9/9 : USER app
 ---> Running in b28f471b4c5d
Removing intermediate container b28f471b4c5d
 ---> 5f025336ed65
Successfully built 5f025336ed65
Successfully tagged local/aloha-base:2021.03.23

This image has the two users that we need. One is an app that will be used to run the application process and admin that can be used to execute further modifications (installing packages, changing config, etc.) if necessary. I usually split Docker container building into multiple separate Dockerfiles, because quite often, these steps take long enough to have a real impact on the overall build time. In some cases, I cannot rely on Docker caching, so I have a few steps, and the last step is application-specific, reducing the CI/CD time to the absolute minimum.

Creating the S6 container (based on base)

S6 is a lightweight process supervision suite for managing long-lived processes in a UNIX-like operating system or in a container. The question if multi-process containers are a good idea or not is not in scope here.

FROM local/aloha-base:2021.03.23

USER admin

RUN sudo apt update
RUN sudo DEBIAN_FRONTEND=noninteractive apt install libpam-ldap unscd software-properties-common -y

RUN sudo add-apt-repository ppa:deadsnakes/ppa
RUN sudo apt update
RUN sudo apt install python3.9 -y

ADD /tmp/
RUN sudo chmod +x /tmp/s6-overlay-amd64-installer && sudo /tmp/s6-overlay-amd64-installer /
sudo docker build . -t local/aloha-base:2021.03.23 --file docker/0/Dockerfile
Sending build context to Docker daemon  239.1kB
Step 1/9 : FROM local/aloha-base:2021.03.23
 ---> 5f025336ed65
Step 2/9 : USER admin
 ---> Running in d987f02390a0
Removing intermediate container d987f02390a0
 ---> d3d934f11021
Step 3/9 : RUN sudo apt update
 ---> Running in 0b4e504d1985


Removing intermediate container 0a047b2e0caf
 ---> 9f8947529cba
Successfully built 9f8947529cba
Successfully tagged local/aloha-s6:2021.03.23

This image has S6 and Python 3.9, and we can use these to add our application and NSCD as well.

Creating the application container (based on s6)

This repository contains all the configurations for ldap, nss, pam and nscd. S6 uses simple files that are organized in a tree where the folder name is the service name. There are two files in each of the service folders:

More details about service directories: link.

Example run file for NSCD:

#!/usr/bin/with-contenv sh
echo >&2 "Starting unscd..."
exec /usr/sbin/nscd -d -f /etc/nscd.conf

This folder is then copied to the container, and S6 will pick it up once the container is starting.

FROM local/aloha-s6:2021.03.23

USER admin

ADD ldap.conf /etc/ldap.conf
ADD nsswitch.conf /etc/nsswitch.conf
ADD login /etc/pam.d/login
ADD nscd.conf  /etc/nscd.conf

RUN sudo mkdir -p /var/run/nscd/
RUN sudo chown unscd:unscd /var/run/nscd/

COPY ./services /etc/services.d/

ADD sudo-init /sudo-init

ENTRYPOINT [ "/sudo-init" ]
sudo docker build . -t local/aloha-app:2021.03.23 --file Dockerfile
Sending build context to Docker daemon  243.7kB
Step 1/10 : FROM local/aloha-s6:2021.03.23
 ---> 9f8947529cba
Step 2/10 : USER admin
 ---> Using cache
 ---> df4413ee8a64
Step 3/10 : ADD ldap.conf /etc/ldap.conf
 ---> Using cache
 ---> 8bc9d31a124b
Step 4/10 : ADD nsswitch.conf /etc/nsswitch.conf
 ---> Using cache
 ---> 0dcf1793bd20
Step 5/10 : ADD login /etc/pam.d/login
 ---> Using cache
 ---> 9aa90594b708
Step 6/10 : ADD nscd.conf  /etc/nscd.conf
 ---> Using cache
 ---> 343ff90841bd
Step 7/10 : RUN sudo mkdir -p /var/run/nscd/
 ---> Using cache
 ---> 5ca5e87d0a72
Step 8/10 : RUN sudo chown unscd:unscd /var/run/nscd/
 ---> Using cache
 ---> 168dbcecba14
Step 9/10 : COPY ./services /etc/services.d/
 ---> Using cache
 ---> a6ad71fd377f
Step 10/10 : ADD sudo-init /sudo-init
 ---> Using cache
 ---> d4dbc8afb7a7
Successfully built d4dbc8afb7a7
Successfully tagged local/aloha-app:2021.03.23

Running the container

Finally we can run the container. I usually use Nomad for production workloads but it is possible to spin up the application locally and just try out if everything works.

 sudo docker run --rm -ti local/aloha-app:2021.03.23
[s6-init] making user provided files available at /var/run/s6/etc...exited 0.
[s6-init] ensuring user provided files have correct perms...exited 0.
[fix-attrs.d] applying ownership & permissions fixes...
[fix-attrs.d] done.
[cont-init.d] executing container initialization scripts...
[cont-init.d] done.
[services.d] starting services
Starting web app...
Starting unscd...
[services.d] done.
unscd v0.52, debug level 0x1
aged cache, freed:0, remain:0
service 2 is disabled, dropping
Serving HTTP on port 8000 ( ...

I have enabled nscd debug, so it prints the debug messages on the console. It can be either entirely disabled or redirected into a log file. There are other ways of having LDAP enabled containers, some people use SSSD. Because I was already familiar with NSCD I went down this path.

If you know a better way of cached LDAP lookups in a container, let me know in the HN thread.