Shore Up
A blank staff badge being filled in with a name and slotted into two labelled pigeonholes on a wall of pigeonholes.
LinuxSecurity

Provision an OpenLDAP User and Add Them to Groups With a Script

Ketan Aagja8 min read
No ratings yet

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=com on a Debian/Ubuntu slapd install — 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, and ldapdelete come from the ldap-utils package. slappasswd ships with the slapd server 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 moment DRY_RUN=0, so treat it with the same care as useradd on 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 under ou=people, groups under ou=groups (both containers already exist).
  • POSIX login groups modelled as posixGroup with memberUid attributes. If instead you use groupOfNames with full-DN member attributes, the group-modification part differs — you'd add a member DN, not a memberUid. That's the other common model; I'm writing for memberUid.
  • 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 ldapmodify with explicit changetype: lines rather than separate ldapadd and ldapmodify calls. That means one bind, one password prompt, and the whole change is described in one LDIF you can read in the dry run. ldapadd is simply ldapmodify -a; using ldapmodify with changetype: add is the same operation.
  • LDIF records are separated by a blank line. That's why each group heredoc starts with an empty line.
  • slappasswd with 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 into userPassword.
  • set -euo pipefail stops 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.

Written by
Ketan Aagja

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.