Running a containerized ActiveDirectory for developers

If you develop software for larger organizations one big aspect is integrating it with existing infrastructure. While you may prefer simple deployments of services in docker containers a customer may want you to deploy to their wildfly infrastructure for example.

One common case of infrastructure is an Active Directory (AD) or plain LDAP service used for organization wide authentication and authorization. As a small company we do not have such an infrastructure ourselves and it would not be a great idea to use it for development anyway.

So how do you develop and test your authentication module without an AD being available for you?

Fortunately, nowadays this is relatively easy using tools like Docker and Samba. Let us see how to put such a development infrastructure up and where the pitfalls are.

Running Samba in a Container

Samba cannot only serve windows shares or act as an domain controller for Microsoft Windows based networks but includes a full AD implementation with proper LDAP support. It takes a small amount of work besides installing Samba in a container to set it up, so we have two small shell scripts for setup and launch in a container. I think most of the Dockerfile and scripts should be self-explanatory and straightforward:

Dockerfile:

FROM ubuntu:20.04

RUN DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install samba krb5-config winbind smbclient 
RUN DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install iproute2
RUN DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install openssl
RUN DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install vim

RUN rm /etc/krb5.conf
RUN mkdir -p /opt/ad-scripts

WORKDIR /opt/ad-scripts

CMD chmod +x *.sh && ./samba-ad-setup.sh && ./samba-ad-run.sh

samba-ad-setup.sh:

#!/bin/bash

set -e

info () {
    echo "[INFO] $@"
}

info "Running setup"

# Check if samba is setup
[ -f /var/lib/samba/.setup ] && info "Already setup..." && exit 0

info "Provisioning domain controller..."

info "Given admin password: ${SMB_ADMIN_PASSWORD}"

rm /etc/samba/smb.conf

samba-tool domain provision\
 --server-role=dc\
 --use-rfc2307\
 --dns-backend=SAMBA_INTERNAL\
 --realm=`hostname`\
 --domain=DEV-AD\
 --adminpass=${SMB_ADMIN_PASSWORD}

mv /etc/samba/smb.conf /var/lib/samba/private/smb.conf

touch /var/lib/samba/.setup

Using samba-ad-run.sh we start samba directly instead of running it as a service which you would do outside a container:

#!/bin/bash

set -e

[ -f /var/lib/samba/.setup ] || {
    >&2 echo "[ERROR] Samba is not setup yet, which should happen automatically. Look for errors!"
    exit 127
}

samba -i -s /var/lib/samba/private/smb.conf

With the scripts and the Dockerfile in place you can simply build the container image using a command like

docker build -t dev-ad -f Dockerfile .

We then run it like follows and use the local mounts to preserve the data in the AD we will be using for testing and toying around:

 docker run --name dev-ad --hostname ldap.schneide.dev --privileged -p 636:636 -e SMB_ADMIN_PASSWORD=admin123! -v $PWD/:/opt/ad-scripts -v $PWD/samba-data:/var/lib/samba dev-ad

To have everything running seamlessly you should add the specified hostname – ldap.schneide.dev in our example – to /etc/hosts so that all tools work as expected and like it was a real AD host somewhere.

Testing our setup

Now of course you may want to check if your development AD works as expected and maybe add some groups and users which you need for your implementation to work.

While there are a bunch of tools for working with an AD/LDAP I found the old and sturdy LdapAdmin the easiest and most straightforward to use. It comes as one self-contained executable file (downloadable from Sourceforge) ready to use without installation or other hassles.

After getting the container and LdapAdmin up and running and logging in you should see something like this below:

LdapAdmin Window showing our Samba AD

Then you can browse and edit your active directory to fit your needs allowing you to develop your authentication and authorization module based on LDAP.

I hope you found the above useful for you development setup.

20 thoughts on “Running a containerized ActiveDirectory for developers”

  1. I have followed all the steps, but I’m getting an error message “/usr/sbin/samba_dnsupdate: ERROR: Record already exist; record could not be added. zone[ldap.act.local] name[_ldap._tcp.Default-First-Site-Name._sites.ForestDnsZones] dnsupdate_nameupdate_done: Failed DNS update with exit code 29”

      1. I’m trying to login through the LdapAdmin tool but I keep getting an error message.

        LDAP error! Invalid Credentials: 80090308: LdapErr: DSID-0C0903A9, commnet: AcceptSecurityContext error, data 52e, v1db1.
        The token supplied to the function is invalid.

        In the Base filed I have: DC=ldap,DC=schneide,DC=dev
        In the Username field I have: CN=Administrator,DC=ldap,DC=schneide,DC=dev

        Also, when I try to run the samba-ad-run.sh I get the following error: “[ERROR] Samba is not setup yet, which should happen automatically. Look for errors!”.

  2. Is there a way to get rid of the warning “The server you are trying to connect to is using a certificate which could not be verified! – Issuer certificate not found”?

    1. In development I tend to avoid the certificate hassle by connecting unencrypted to the LDAP. Usually you have to allow unencrypted authentication using samba explicitly by setting

      [global]
      ldap server require strong auth = no

      in /var/lib/samba/private/smb.conf of the container/samba. Do NOT use this setting for your production environment unless you are really knowing what you are doing.

  3. Is there a way to connect to the LDAP tool using the domain name, instead of the full base DN path DC=ldap,DC=schneide,DC=dev?

  4. I am able to add users to my LDAP server using LDAP Admin by using the Edit > New > User wizard. But when I try to create a new group using the Edit > New > Group wizard, I get the following error:

    LDAP error! Object Class Violation: no structural object class provided

    How can I create new groups in LDAP Admin?

      1. I tried right-click on a folder -> New -> Group and also the shortcut is ctrl+G.

        LDAP error! Unwilling to perform: Failed to find a structural class for CN=testgroup,CN=Users,DC=test,DC=ad,DC=dev.

      2. I tried creating a group using the shortcut ctrl+G, but I’m still getting an error message: LDAP error! Unwilling to perform: Failed to find a structural class for CN=testgroup,CN=Users,DC=test,DC=ad,DC=dev.

  5. I ran step by step and landed in the following error

    ERROR(): Provision failed – ProvisioningError: Your filesystem or build does not support posix ACLs, which s3fs requires. Try the mounting the filesystem with the ‘acl’ option.
    File “/usr/lib/python3/dist-packages/samba/netcmd/domain.py”, line 487, in run
    result = provision(self.logger,
    File “/usr/lib/python3/dist-packages/samba/provision/__init__.py”, line 2341, in provision
    provision_fill(samdb, secrets_ldb, logger, names, paths,
    File “/usr/lib/python3/dist-packages/samba/provision/__init__.py”, line 1979, in provision_fill
    setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.root_uid,
    File “/usr/lib/python3/dist-packages/samba/provision/__init__.py”, line 1694, in setsysvolacl
    raise ProvisioningError(“Your filesystem or build does not support posix ACLs, which s3fs requires.

  6. A new issue. When starting the docker container you get the following error messages:

    stream_setup_socket: Failed to listen on :::135 – NT_STATUS_ADDRESS_ALREADY_ASSOCIATED
    service_setup_stream_socket(address=::,port=135) for epmapper mgmt failed – NT_STATUS_ADDRESS_ALREADY_ASSOCIATED
    stream_setup_socket: Failed to listen on 0.0.0.0:135 – NT_STATUS_ADDRESS_ALREADY_ASSOCIATED
    service_setup_stream_socket(address=0.0.0.0,port=135) for epmapper mgmt failed – NT_STATUS_ADDRESS_ALREADY_ASSOCIATED

    Port 135 is used for “RPC client-server communication” on Windows.

    1. I can also confirm this issue. It worked for some time but now crashes because the Windows Host System (win11 on my Side) already blocks this port.

      Is it possible to configure samba to not use 135 or map it to another high port?

      1. Definitely a show stopper. Can’t do much as it’s not usable anymore. I’m trying to find a way to disable port 135 using a firewall on the docker image but don’t know if that will work. There must be a way to fix this issue.

    2. The problem is strange, because the samba binds the ports *in* the container and not on the host interface. To expose the ports on the host you have to run the container with -p 135:135…
      Unfortunately, I am currently working on some other projects and do not have time to investigate the issue but I would be glad if someone who finds a solution could drop a comment here to help out the others. Sorry…

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.