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.yml file with LDAP cluster configuration
## Step 1: Create Configuration File Create a 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) 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. 1 Open http://localhost:5173 in your browser
  2. 2 Click on your cluster name
  3. 3 Enter the bind DN password when prompted
  4. 4 Password is encrypted and cached securely with Fernet (AES-128-CBC)
  5. 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