Skip to content

OpenLDAP

Important

Development of this recipe is sponsored by The Common Observatory. Thanks guys!

Common Observatory

LDAP is probably the most ubiquitous authentication backend, before the current era of "stupid social sign-ons". Many of the recipes featured in the cookbook (NextCloud, Kanboard, Gitlab, etc) offer LDAP integration.

Big deal, who cares?

If you're the only user of your tools, it probably doesn't bother you too much to setup new user accounts for every tool. As soon as you start sharing tools with collaborators (think 10 staff using NextCloud), you suddenly feel the pain of managing a growing collection of local user accounts per-service.

Enter OpenLDAP - the most crusty, PITA, fiddly platform to setup (yes, I'm a little bitter, dynamic configuration backend!), but hugely useful for one job - a Lightweight Protocol for managing a Directory used for Access (see what I did there?)

The nice thing about OpenLDAP is, like MySQL, once you've setup the server, you probably never have to interact directly with it. There are many tools which will let you interact with your LDAP database via a(n ugly) UI.

This recipe combines the raw power of OpenLDAP with the flexibility and featureset of LDAP Account Manager.

OpenLDAP Screenshot

What's the takeaway?

What you'll end up with is a directory structure which will allow integration with popular tools (NextCloud, Kanboard, Gitlab, etc), as well as with KeyCloak (an upcoming recipe), for true SSO.

Ingredients

  1. Docker swarm cluster with persistent shared storage
  2. Traefik configured per design
  3. DNS entry for the hostname (i.e. "lam.your-domain.com") you intend to use for LDAP Account Manager, pointed to your keepalived IP

Preparation

Setup data locations

We'll need several directories to bind-mount into our container, so create them in /var/data/openldap:

1
2
mkdir /var/data/openldap/openldap
mkdir /var/data/runtime/openldap/

Why 2 directories?

For rationale, see my data layout explanation

Prepare environment

Create /var/data/openldap/openldap.env, and populate with the following variables, customized for your own domain structure. Take care with LDAP_DOMAIN, this is core to your directory structure, and can't easily be changed later.

1
2
3
4
5
6
7
8
9
LDAP_DOMAIN=batcave.gotham
LDAP_ORGANISATION=BatCave Inc
LDAP_ADMIN_PASSWORD=supermansucks
LDAP_TLS=false

# Use these if you plan to protect the LDAP Account Manager webUI with an oauth_proxy
OAUTH2_PROXY_CLIENT_ID=
OAUTH2_PROXY_CLIENT_SECRET=
OAUTH2_PROXY_COOKIE_SECRET=

Note

I use an OAuth proxy to protect access to the web UI, when the sensitivity of the protected data (i.e. my authentication store) warrants it, or if I don't necessarily trust the security of the webUI.

Create authenticated-emails.txt, and populate with the email addresses (matched to GitHub user accounts, in my case) to which you want grant access, using OAuth2.

Create config.cfg

The Dockerized version of LDAP Account Manager is a little fiddly. In order to maintain a config file which persists across container restarts, we need to present the container with a copy of /var/www/html/config/lam.conf, tweaked for our own requirements.

Create /var/data/openldap/lam/config/config.cfg as follows:

Much scroll, very text. Click here to collapse it for better readability
 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# password to add/delete/rename configuration profiles (default: lam)
password: {SSHA}54haBZN/kfgNVJ+W3YJrI2dCic4= iCXkNA==

# default profile, without ".conf"
default: batcave

# log level
logLevel: 4

# log destination
logDestination: SYSLOG

# session timeout in minutes
sessionTimeout: 30

# list of hosts which may access LAM
allowedHosts:

# list of hosts which may access LAM Pro self service
allowedHostsSelfService:

# encrypt session data
encryptSession: true

# Password: minimum password length
passwordMinLength: 0

# Password: minimum uppercase characters
passwordMinUpper: 0

# Password: minimum lowercase characters
passwordMinLower: 0

# Password: minimum numeric characters
passwordMinNumeric: 0

# Password: minimum symbolic characters
passwordMinSymbol: 0

# Password: minimum character classes (0-4)
passwordMinClasses: 0

# Password: checked rules
checkedRulesCount: -1

# Password: must not contain part of user name
passwordMustNotContain3Chars: false

# Password: must not contain user name
passwordMustNotContainUser: false

# Email format (default/unix)
mailEOL: default

# PHP error reporting (default/system)
errorReporting: default

# License
license:

Create <profile>.cfg

While config.cfg (above) defines application-level configuration, <profile>.cfg is used to configure "domain-specific" configuration. You probably only need a single profile, but LAM could theoretically be used to administer several totally unrelated LDAP servers, ergo the concept of "profiles".

Create yours profile (you chose a default profile in config.cfg above, remember?) by creating /var/data/openldap/lam/config/<profile>.conf, as follows:

Much scroll, very text. Click here to collapse it for better readability
  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
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# LDAP Account Manager configuration
#
# Please do not modify this file manually. The configuration can be done completely by the LAM GUI.
#
###################################################################################################

# server address (e.g. ldap://localhost:389 or ldaps://localhost:636)
ServerURL: ldap://openldap:389

# list of users who are allowed to use LDAP Account Manager
# names have to be separated by semicolons
# e.g. admins: cn=admin,dc=yourdomain,dc=org;cn=root,dc=yourdomain,dc=org
Admins: cn=admin,dc=batcave,dc=gotham

# password to change these preferences via webfrontend (default: lam)
Passwd: {SSHA}h39N9+gg/Qf1K/986VkKrjWlkcI= S/IAUQ==

# suffix of tree view
# e.g. dc=yourdomain,dc=org
treesuffix: dc=batcave,dc=gotham

# default language (a line from config/language)
defaultLanguage: en_GB.utf8

# Path to external Script
scriptPath:

# Server of external Script
scriptServer:

# Access rights for home directories
scriptRights: 750

# Number of minutes LAM caches LDAP searches.
cachetimeout: 5

# LDAP search limit.
searchLimit: 0

# Module settings

modules: posixAccount_user_minUID: 10000
modules: posixAccount_user_maxUID: 30000
modules: posixAccount_host_minMachine: 50000
modules: posixAccount_host_maxMachine: 60000
modules: posixGroup_group_minGID: 10000
modules: posixGroup_group_maxGID: 20000
modules: posixGroup_pwdHash: SSHA
modules: posixAccount_pwdHash: SSHA

# List of active account types.
activeTypes: user,group


types: suffix_user: ou=People,dc=batcave,dc=gotham
types: attr_user: #uid;#givenName;#sn;#uidNumber;#gidNumber
types: modules_user: inetOrgPerson,posixAccount,shadowAccount

types: suffix_group: ou=Groups,dc=batcave,dc=gotham
types: attr_group: #cn;#gidNumber;#memberUID;#description
types: modules_group: posixGroup

# Password mail subject
lamProMailSubject: Your password was reset

# Password mail text
lamProMailText: Dear @@givenName@@ @@sn@@,+::++::+your password was reset to: @@newPassword@@+::++::++::+Best regards+::++::+deskside support+::+



serverDisplayName:


# enable TLS encryption
useTLS: no


# follow referrals
followReferrals: false


# paged results
pagedResults: false

referentialIntegrityOverlay: false


# time zone
timeZone: Europe/London

scriptUserName:

scriptSSHKey:

scriptSSHKeyPassword:


# Access level for this profile.
accessLevel: 100


# Login method.
loginMethod: list


# Search suffix for LAM login.
loginSearchSuffix: dc=batcave,dc=gotham


# Search filter for LAM login.
loginSearchFilter: uid=%USER%


# Bind DN for login search.
loginSearchDN:


# Bind password for login search.
loginSearchPassword:


# HTTP authentication for LAM login.
httpAuthentication: false


# Password mail from
lamProMailFrom:


# Password mail reply-to
lamProMailReplyTo:


# Password mail is HTML
lamProMailIsHTML: false


# Allow alternate address
lamProMailAllowAlternateAddress: true

jobsBindPassword:

jobsBindUser:

jobsDatabase:

jobsDBHost:

jobsDBPort:

jobsDBUser:

jobsDBPassword:

jobsDBName:

jobToken: 190339140545

pwdResetAllowSpecificPassword: true

pwdResetAllowScreenPassword: true

pwdResetForcePasswordChange: true

pwdResetDefaultPasswordOutput: 2

twoFactorAuthentication: none

twoFactorAuthenticationURL: https://localhost

twoFactorAuthenticationInsecure:

twoFactorAuthenticationLabel:

twoFactorAuthenticationOptional:

twoFactorAuthenticationCaption:
tools: tool_hide_toolOUEditor: false
tools: tool_hide_toolProfileEditor: false
tools: tool_hide_toolSchemaBrowser: false
tools: tool_hide_toolServerInformation: false
tools: tool_hide_toolTests: false
tools: tool_hide_toolPDFEditor: false
tools: tool_hide_toolFileUpload: false
tools: tool_hide_toolMultiEdit: false

Setup Docker Swarm

Create a docker swarm config file in docker-compose syntax (v3), something like this, at (/var/data/config/openldap/openldap.yml)

Tip

I share (with my patreon patrons) a private "premix" git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just a git pull and a docker stack deploy 👍

 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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
version: '3'

services:
  openldap:
    image: osixia/openldap
    env_file: /var/data/config/openldap/openldap.env
    networks:
    - traefik_public
    - auth_internal
    volumes:
    - /var/data/runtime/openldap/:/var/lib/ldap
    - /var/data/openldap/openldap/:/etc/ldap/slapd.d

  lam:
    image: jacksgt/ldap-account-manager
    networks:
    - auth_internal
    volumes:
    - /var/data/openldap/lam/config/config.cfg:/var/www/html/config/config.cfg
    - /var/data/openldap/lam/config/batcave.conf:/var/www/html/config/batcave.conf

  lam-proxy:
    image: funkypenguin/oauth2_proxy
    env_file: /var/data/config/openldap/openldap.env
    networks:
      - traefik_public
      - auth_internal
    deploy:
      labels:
        - traefik.frontend.rule=Host:lam.batcave.com
        - traefik.docker.network=traefik_public
        - traefik.port=4180
    command: |
      -cookie-secure=false
      -upstream=http://lam:80
      -redirect-url=https://lam.batcave.com
      -http-address=http://0.0.0.0:4180
      -email-domain=batcave.com
      -provider=github


networks:
  # Used to expose lam-proxy to external access, and openldap to keycloak
  traefik_public:
    external: true

  # Used to expose openldap to other apps which want to talk to LDAP, including LAM
  auth_internal:
    external: true    

Warning

Normally, we set unique static subnets for every stack you deploy, and put the non-public facing components (like databases) in an dedicated <stack>_internal network. This avoids IP/gateway conflicts which can otherwise occur when you're creating/removing stacks a lot. See my list here.

However, you're likely to want to use OpenLdap with KeyCloak, whose JBOSS startup script assumes a single interface, and will crash in a ball of 🔥 if you try to assign multiple interfaces to the container.

Since we're going to want KeyCloak to be able to talk to OpenLDAP, we have no choice but to leave the OpenLDAP container on the "traefik_public" network. We can, however, create another overlay network (auth_internal, see below), add it to the openldap container, and use it to provide OpenLDAP access to our other stacks.

Create another stack config file (/var/data/config/openldap/auth.yml) containing just the auth_internal network, and a dummy container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
version: '3'

services:
  helloworld:
    image: hello-world
    networks:
      - internal

networks:
  internal:
    driver: overlay
    ipam:
      config:
        - subnet: 172.16.39.0/24

Serving

Launch OpenLDAP stack

Create the auth_internal overlay network, by running docker stack deploy auth -c /var/data/config/openldap/auth.yml`, then launch the OpenLDAP stack by runningdocker stack deploy openldap -c /var/data/config/openldap/openldap.yml```

Log into your new LAM instance at https://YOUR-FQDN.

On first login, you'll be prompted to create the "ou=People" and "ou=Group" elements. Proceed to create these.

You've now setup your OpenLDAP directory structure, and your administration interface, and hopefully won't have to interact with the "special" LDAP Account Manager interface much again!

Create your users using the "New User" button.

Important

Development of this recipe is sponsored by The Common Observatory. Thanks guys!

Common Observatory

Chef's Notes 📓

  1. The KeyCloak](/recipes/keycloak/authenticate-against-openldap/) recipe illustrates how to integrate KeyCloak with your LDAP directory, giving you a cleaner interface to manage users, and a raft of SSO / OAuth features.

Tip your waiter (support me) 👏

Did you receive excellent service? Want to make your waiter happy? (..and support development of current and future recipes!) See the support page for (free or paid) ways to say thank you! 👏

Flirt with waiter (subscribe) 💌

Want to know now when this recipe gets updated, or when future recipes are added? Subscribe to the RSS feed, or leave your email address below, and we'll keep you updated. (double-opt-in, no monkey business, no spam either - check the archive for proof!)

Your comments? 💬