
Provision an OpenLDAP User and Add Them to Groups With a Script
Before you run this
This script creates one LDAP user entry (an inetOrgPerson / posixAccount) under your people container and adds that user's uid as a memberUid to one or more existing groups. Its purpose is to make user provisioning repeatable instead of hand-crafting LDIF every time.
- Privileges: You do not need root to run it. You do need the directory's rootDN (admin) bind credentials — by default
cn=admin,dc=example,dc=comon a Debian/Ubuntuslapdinstall — because writing to the directory requires an authenticated bind. The script prompts for that password with-W; it never puts it on the command line. - Tooling:
ldapmodify,ldapsearch, andldapdeletecome from theldap-utilspackage.slappasswdships with theslapdserver package, so if you run this from a client machine without the server installed, generate the password hash on the LDAP server instead, or install accordingly. - Test first. Read the script, then run it with
DRY_RUN=1(the default) so it only prints the LDIF it would apply. Try it against a test directory or a throwaway user before you point it at production. Never paste-and-run this blind on a live directory. - What it changes: It adds an entry and modifies existing groups. Adding is reversible (delete the entry, remove the
memberUid— shown at the end). But the entry is written to your live directory the momentDRY_RUN=0, so treat it with the same care asuseraddon a shared box. If a target group does not exist, the group modification fails with "No such object" — the script adds users to groups, it does not create them.
Assumptions
I'm assuming a standard Debian 12 / Ubuntu slapd install with:
- Base DN
dc=example,dc=com, people underou=people, groups underou=groups(both containers already exist). - POSIX login groups modelled as
posixGroupwithmemberUidattributes. If instead you usegroupOfNameswith full-DNmemberattributes, the group-modification part differs — you'd add amemberDN, not amemberUid. That's the other common model; I'm writing formemberUid. - You're on the server or a machine that can reach it, using simple bind (
-x).
Replace every example.com / dc=example,dc=com and the sample user details with your own.
The script
#!/usr/bin/env bash
set -euo pipefail
# ---- Directory settings (edit for your environment) ----
BASE_DN="dc=example,dc=com"
PEOPLE_OU="ou=people,${BASE_DN}"
GROUP_OU="ou=groups,${BASE_DN}"
ADMIN_DN="cn=admin,${BASE_DN}"
LDAP_URI="ldap://localhost" # use ldaps://ldap.example.com over a network
LOGIN_SHELL="/bin/bash"
DRY_RUN=1 # 1 = print the LDIF only; 0 = actually apply it
# ---- Details for THIS user (edit each run, or wire up to args) ----
USERNAME="jdoe"
GIVEN_NAME="John"
SURNAME="Doe"
UID_NUMBER="10001" # must be unique in your directory
GID_NUMBER="10001" # the user's primary group id
MAIL="jdoe@example.com"
GROUPS=("developers" "vpn-users") # existing posixGroup cn values
# ---- Generate the password hash (prompts twice, prints {SSHA}...) ----
HASH="$(slappasswd)"
# ---- Build one LDIF: add the user, then modify each group ----
LDIF="$(mktemp)"
trap 'rm -f "$LDIF"' EXIT # never leave the hash lying in /tmp
cat > "$LDIF" <<EOF
dn: uid=${USERNAME},${PEOPLE_OU}
changetype: add
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: ${USERNAME}
cn: ${GIVEN_NAME} ${SURNAME}
sn: ${SURNAME}
givenName: ${GIVEN_NAME}
mail: ${MAIL}
uidNumber: ${UID_NUMBER}
gidNumber: ${GID_NUMBER}
homeDirectory: /home/${USERNAME}
loginShell: ${LOGIN_SHELL}
userPassword: ${HASH}
EOF
# One modify record per group. The blank line before each dn separates records.
for g in "${GROUPS[@]}"; do
cat >> "$LDIF" <<EOF
dn: cn=${g},${GROUP_OU}
changetype: modify
add: memberUid
memberUid: ${USERNAME}
EOF
done
# ---- Apply or preview ----
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "----- DRY RUN: this is the LDIF that would be applied -----"
cat "$LDIF"
echo "----- Set DRY_RUN=0 to apply. Nothing was changed. -----"
exit 0
fi
ldapmodify -x -H "$LDAP_URI" -D "$ADMIN_DN" -W -f "$LDIF"
echo "Done. Verify below."
A few notes on the non-obvious parts:
- I use a single
ldapmodifywith explicitchangetype:lines rather than separateldapaddandldapmodifycalls. That means one bind, one password prompt, and the whole change is described in one LDIF you can read in the dry run.ldapaddis simplyldapmodify -a; usingldapmodifywithchangetype: addis the same operation. - LDIF records are separated by a blank line. That's why each group heredoc starts with an empty line.
slappasswdwith no arguments prompts for the new user's password and prints a salted{SSHA}hash. It does not touch the directory — it only produces the hash string that goes intouserPassword.set -euo pipefailstops the script on the first error, so a failed group add (e.g. a group that doesn't exist) doesn't get silently ignored. Note that the user entry is added first; if a later group modify fails, the user still exists — check the undo section.
Run it:
chmod +x provision-ldap-user.sh
./provision-ldap-user.sh # DRY_RUN=1: prints the LDIF, changes nothing
Read the LDIF. When it's right, set DRY_RUN=0 and run again.
One caution on transport
With -x (simple bind), the admin password and the entry travel to the server as sent. Over ldap://localhost that stays on the box. Over a network, point LDAP_URI at ldaps://ldap.example.com (or use StartTLS) so credentials aren't sent in the clear.
Verify it worked
Confirm the entry exists and has the attributes you expect:
ldapsearch -x -H ldap://localhost \
-b "uid=jdoe,ou=people,dc=example,dc=com"
Confirm the group memberships took:
ldapsearch -x -H ldap://localhost \
-b "ou=groups,dc=example,dc=com" \
"(memberUid=jdoe)" cn
That second search returns the cn of every group jdoe now belongs to. If NSS/PAM on your clients is configured to read from LDAP, id jdoe and getent passwd jdoe on a bound client are a good end-to-end check — but that's client configuration, outside this script.
Undo
Removing a user is a delete plus removing them from any groups. Do the group removals first, then delete the entry.
Remove the memberUid from each group (repeat per group, or script the loop the same way):
ldapmodify -x -H ldap://localhost -D "cn=admin,dc=example,dc=com" -W <<'EOF'
dn: cn=developers,ou=groups,dc=example,dc=com
changetype: modify
delete: memberUid
memberUid: jdoe
EOF
Then delete the user entry:
ldapdelete -x -H ldap://localhost -D "cn=admin,dc=example,dc=com" -W \
"uid=jdoe,ou=people,dc=example,dc=com"
After that, re-run the two ldapsearch checks above — the entry lookup should return "No such object" and the memberUid search should return nothing.
For the exact attribute requirements of each objectClass and the full option list for these tools, see the OpenLDAP Software Administrator's Guide and the ldapmodify, ldapsearch, ldapdelete, and slappasswd man pages on your system.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
