Getting Started
Quick start guide to get LDAP Manager up and running in minutes.Project Files
ldap-manager
Explorer
config.example.yml
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# --------------------------------------------------------------------------- # Authentication (optional) # --------------------------------------------------------------------------- # Choose how people sign in to LDAP Manager itself. Three modes: # # none No login. Use this when nginx / oauth2-proxy / your SSO already # authenticates users before they reach the app. Visitors get # `default_role` below, which is READONLY unless you change it, so a # direct hit on the app cannot modify anything. # # local Built-in accounts, no external integration. On first start the UI # shows a one-time wizard to create them; passwords are hashed with # scrypt and stored encrypted in the container. # # ldap One of the clusters below authenticates your users; every other # cluster stays a cluster to manage. Users sign in with their own # directory password, so no cluster-wide bind secret is needed to log # in, and their role comes from group membership. # # If you omit this whole block the app runs in `none` mode with the `readonly` # default role, i.e. view-only. auth: mode: none # none | local | ldap # Role for a visitor who is not signed in. Only meaningful for mode: none. # Set to "admin" when you trust your reverse proxy and want full access. default_role: readonly # readonly | readwrite | admin session: lifetime_hours: 12 # how long a sign-in lasts # ---- only used when mode: ldap -------------------------------------------- # Uncomment this block and set mode: ldap above, so one of your clusters # authenticates users while the rest stay clusters to manage. # # ldap: # cluster: "example-cluster" # which cluster authenticates # # # Option A: build the user's DN from the username # user_dn_template: "uid={username},ou=People,dc=example,dc=com" # # # Where to search for group membership. Defaults to the cluster's base_dn. # user_base_dn: "dc=example,dc=com" # # # NOTE: authentication binds as the user's own DN, built only from # # user_dn_template (or `uid=<username>,<user_base_dn>` when it is unset). # # `user_filter` is accepted in the schema but is NOT used by the login # # path, so a config relying on it will silently fall back to the uid= form. # # role_map: # # Role for anyone whose groups match nothing below. # default: readonly # # LDAP group DN -> role. member, uniqueMember and memberUid are all # # supported, so groupOfNames, groupOfUniqueNames and posixGroup work. # groups: # "cn=ldap-admins,ou=Group,dc=example,dc=com": admin # "cn=ldap-ops,ou=Group,dc=example,dc=com": readwrite # --------------------------------------------------------------------------- # Clusters # --------------------------------------------------------------------------- clusters: - name: "example-cluster" host: "ldap.example.com" port: 389 bind_dn: "cn=admin,dc=example,dc=com" base_dn: "dc=example,dc=com" description: "Example LDAP cluster" readonly: false # How the bind password for THIS cluster is supplied. Your choice per cluster. # LDAP Manager never stores a password: it reads the secret from one of these # sources, and there is no UI or API that writes one. # # env Read from an environment variable, so the secret never touches # disk. Defaults to LDAP_MANAGER_CLUSTER_<NAME>_PASSWORD. # file Read from a mounted secret file - a Docker/Kubernetes secret. # config Plaintext `bind_password` right here. Fine for a lab; the app # logs a warning because the secret sits in the config file. # # Omit this block entirely and the cluster uses `env`. credential: source: env env: LDAP_MANAGER_CLUSTER_EXAMPLE_PASSWORD # only for source: env # file: /run/secrets/example-cluster # only for source: file # bind_password: "change-me" # only for source: config # Optional: TLS for this cluster's connections. # none (default) plain ldap:// # ldaps TLS from the first byte (ldaps://). With no explicit port, a # configured port of 389 is raised to 636. # starttls connect on ldap:// then upgrade with StartTLS. # # ca_file/cert_file/key_file are paths INSIDE the ldap-manager container, so # mount the certificates there (e.g. -v ./certs:/certs:ro). # # verify: true (default) checks the server certificate against ca_file. # false accepts any certificate - lab use only. # # Omit this block entirely and the cluster uses plaintext. # tls: # mode: ldaps # ca_file: /certs/ca.crt # verify: true # # cert_file: /certs/client.crt # mutual TLS # # key_file: /certs/client.key # Optional: cn=config connection, needed by the Schema and ACI editors. # The data bind DN usually cannot read cn=config, so give the config admin # its own DN and credential. # config: # bind_dn: "cn=config" # credential: # source: env # LDAP_MANAGER_CONFIG_EXAMPLE_PASSWORD # Optional: Define user creation form (no-code form builder) # See https://vibhuvioio.com/ldap-manager/configuration for details user_creation_form: base_ou: "ou=People,dc=example,dc=com" object_classes: - inetOrgPerson - posixAccount - top - shadowAccount fields: - name: uid label: Username type: text required: true placeholder: "e.g., jdoe" - name: cn label: First Name type: text required: true - name: sn label: Last Name type: text required: true - name: mail label: Email type: email required: true auto_generate: "${uid}@example.com" # Template: ${uid} replaced with username - name: userPassword label: Password type: password required: true - name: uidNumber label: UID Number type: number required: true auto_generate: "next_uid" # Auto-generates next available UID starting at 2000 readonly: true - name: gidNumber label: Primary Group ID type: number required: true default: 100 - name: homeDirectory label: Home Directory type: text required: true auto_generate: "/home/${uid}" # Template: ${uid} replaced with username - name: loginShell label: Login Shell type: text required: true default: "/bin/bash" - name: shadowLastChange label: Shadow Last Change type: number required: false auto_generate: "days_since_epoch" # Auto-generates days since 1970-01-01 readonly: true - name: shadowMax label: Shadow Max type: number required: false default: 99999 - name: shadowWarning label: Shadow Warning type: number required: false default: 7 # Multi-master cluster example - name: "multi-master-cluster" nodes: - host: "ldap1.example.com" port: 389 - host: "ldap2.example.com" port: 389 - host: "ldap3.example.com" port: 389 bind_dn: "cn=admin,dc=example,dc=com" base_dn: "dc=example,dc=com" description: "Multi-master LDAP cluster" readonly: false # Same env-sourced secret every node shares. credential: source: env env: LDAP_MANAGER_CLUSTER_MULTI_MASTER_PASSWORD # Optional: Customize table columns # Control which columns appear in Users/Groups/OUs tables # Users can show/hide columns via the "Columns" button in the UI # See https://vibhuvioio.com/ldap-manager/configuration table_columns: users: - name: uid label: Username default_visible: true # Show by default - name: cn label: Full Name default_visible: true - name: mail label: Email default_visible: true - name: uidNumber label: UID default_visible: false # Hidden by default (technical field) - name: gidNumber label: GID default_visible: false # Hidden by default (technical field) - name: homeDirectory label: Home Directory default_visible: false # Hidden by default (technical field) - name: loginShell label: Shell default_visible: false # Hidden by default (technical field) - name: objectClass label: Type default_visible: true # Show user type badge groups: - name: cn label: Group Name default_visible: true - name: description label: Description default_visible: true - name: gidNumber label: GID default_visible: false - name: members label: Members default_visible: true - name: dn label: DN default_visible: false ous: - name: ou label: OU Name default_visible: true - name: description label: Description default_visible: true - name: dn label: DN default_visible: true
YAMLUTF-8
Ln 2724 files
Download All saves every file. Copy
config.example.yml to config.yml - Compose
mounts ./config.yml.
Prerequisites
- Docker 20.10+
- Docker Compose 2.0+ (optional)
- OpenLDAP server (local or remote)
-
config.ymlfile with LDAP cluster configuration
config.yml file with your LDAP cluster details:
Single Node Example
clusters:
- name: "Production LDAP"
host: "ldap.example.com"
port: 389
bind_dn: "cn=Manager,dc=example,dc=com"
base_dn: "dc=example,dc=com" # Optional - auto-discovered if omitted
Multi-Master Cluster Example
clusters:
- name: "LDAP Cluster"
nodes:
- host: "ldap1.example.com"
port: 389
- host: "ldap2.example.com"
port: 389
- host: "ldap3.example.com"
port: 389
bind_dn: "cn=Manager,dc=example,dc=com"
Docker Host Connection
For connecting to LDAP running on Docker host:
clusters:
- name: "Local Docker LDAP"
host: "host.docker.internal" # Docker Desktop (Mac/Windows)
# host: "172.17.0.1" # Linux Docker
port: 389
bind_dn: "cn=Manager,dc=example,dc=com"
ℹ️
Note: Tip: config.example.yml is in the Project Files panel above - Download All, then rename it to config.yml. Or from a terminal:
wget https://raw.githubusercontent.com/VibhuviOiO/ldap-manager/refs/heads/main/config.example.yml -O config.yml
Step 2: Run with Docker
Option A: Docker Run (Fastest)
docker run -d \
--name ldap-manager \
-p 5173:5173 \
-p 8000:8000 \
-v $(pwd)/config.yml:/app/config.yml:ro \
vibhuvioio/ldap-manager:latest
Access the UI at http://localhost:5173 (Frontend) or http://localhost:8000 (API)
Option B: Docker Compose (Recommended)
docker-compose.prod.yml is in the Project Files panel above. Or from a terminal:
wget https://raw.githubusercontent.com/VibhuviOiO/ldap-manager/refs/heads/main/docker-compose.prod.yml
Start the application:
docker compose -f docker-compose.prod.yml up -d
Access the UI at http://localhost:5173 (or http://localhost:8000 for API)
Step 3: First Time Setup
-
1
Open
http://localhost:5173in your browser - 2 Click on your cluster name
- 3 Enter the bind DN password when prompted
- 4 Password is encrypted and cached securely with Fernet (AES-128-CBC)
- 5 Cached passwords expire after 1 hour (default)
ℹ️
Note: Security: Passwords are encrypted at rest using Fernet symmetric encryption with automatic TTL expiration. Encryption keys are stored in /app/.secrets/ with 0600 file permissions. Never stored in plaintext. See Security Guide for details.
Local Development
Backend
cd backend
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
Frontend
cd frontend
npm install
npm run dev
Access the development UI at http://localhost:5173
Verify Installation
Check if the services are running:
# Check backend health
curl http://localhost:8000/health
# Check frontend
curl http://localhost:5173
Next Steps
- Configure multiple clusters
- Explore features
- Review security best practices