LDAP Server Administration - thebaulab/onramp GitHub Wiki
LDAP Server Administration
This page documents the LDAP server that provides centralized authentication for the baulab.us cluster.
Quick Reference
| Property | Value |
|---|---|
| Server | baunames (10.200.205.143) |
| Base DN | dc=baulab,dc=us |
| Admin DN | cn=admin,dc=baulab,dc=us |
| phpLDAPadmin Port | 8877 (via SSH tunnel) |
What is LDAP and Why Do We Use It?
The Problem: Managing Users Across Many Machines
The cluster has 25+ GPU workstations. Without LDAP, adding a new lab member would require SSHing into each machine, running adduser, and setting the password - 25 times. Password changes and account deletions would require the same.
The Solution: One Central User Database
LDAP (Lightweight Directory Access Protocol) is essentially a specialized database optimized for storing organizational data like user accounts, groups, and contact information. Think of it as a shared phonebook that all computers in the cluster can read.
With LDAP:
- Add a user once → they can log into any machine
- Change a password once → it works everywhere immediately
- Delete a user once → they're locked out of everything
Our LDAP server runs on baunames (the "names" server for the baulab cluster).
How It's Different from a Regular Database
You might wonder: why not just use MySQL or PostgreSQL? LDAP is designed specifically for:
- Read-heavy workloads: User lookups happen constantly (every login, every
ls -l), but changes are rare - Hierarchical data: Users belong to groups, which belong to organizational units
- Standardized protocol: Any Linux system can query LDAP out of the box
Understanding the Login Process
When someone types ssh arnab@karakuri, here's what happens behind the scenes:
┌─────────────────────────────────────────────────────────────────────────┐
│ USER'S LAPTOP │
│ │
│ $ ssh arnab@karakuri │
│ │
└───────────────────────────────┬─────────────────────────────────────────┘
│ SSH connection
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ KARAKURI (GPU Workstation) │
│ │
│ 1. "Who is 'arnab'?" → NSS checks /etc/passwd │
│ Not found locally (arnab isn't in /etc/passwd) │
│ │
│ 2. NSS asks SSSD → SSSD queries LDAP server │
│ │ │
└────────────────────────────────┼────────────────────────────────────────┘
│ LDAP query: "Give me info for uid=arnab"
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ BAUNAMES (LDAP Server) │
│ │
│ OpenLDAP looks up arnab in its database: │
│ - uidNumber: 1007 │
│ - gidNumber: 1025 (research group) │
│ - homeDirectory: /share/u/arnab │
│ - userPassword: {SSHA}encrypted... │
│ │
└────────────────────────────────┬────────────────────────────────────────┘
│ Returns user info
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ KARAKURI (continued) │
│ │
│ 3. SSSD receives: uid=1007, gid=1025, home=/share/u/arnab │
│ │
│ 4. PAM (Pluggable Auth Module) asks SSSD to verify password │
│ → SSSD tries to "bind" to LDAP as arnab with the password │
│ → LDAP checks password hash, returns success/failure │
│ │
│ 5. If password correct: │
│ - Create session with uid 1007, gid 1025 │
│ - Change to home directory /share/u/arnab │
│ - If home doesn't exist, pam_mkhomedir creates it │
│ │
│ $ whoami │
│ arnab │
│ │
└─────────────────────────────────────────────────────────────────────────┘
The Key Players
| Component | What It Does | Where It Runs |
|---|---|---|
| OpenLDAP (slapd) | The actual database server storing user/group info | baunames only |
| SSSD | Daemon that queries LDAP and caches results | Every client machine |
| NSS | System that maps usernames ↔ user IDs | Every client machine |
| PAM | System that handles authentication (password checking) | Every client machine |
Why SSSD caches results: If baunames goes down briefly, users who recently logged in can still work because SSSD remembers their info. New users or password changes won't work until LDAP is back.
The Directory Tree: How Data is Organized
LDAP stores data in a tree structure (called a DIT - Directory Information Tree). Each entry has a unique path called a Distinguished Name (DN):
dc=baulab,dc=us ← Root (like / in filesystem)
│
├── cn=admin ← The super-admin account
│
├── ou=people ← "Organizational Unit" for users
│ │
│ ├── uid=davidbau ← A user entry
│ │ ├── cn: David Bau ← Full name
│ │ ├── uidNumber: 1000 ← Unix user ID
│ │ ├── gidNumber: 1025 ← Primary group (research)
│ │ ├── homeDirectory: /share/u/davidbau
│ │ ├── loginShell: /bin/bash
│ │ └── userPassword: {SSHA}... ← Hashed password
│ │
│ ├── uid=arnab
│ │ └── ... (same attributes)
│ │
│ └── uid=newstudent
│ └── ... (~23 users total)
│
└── ou=groups ← Organizational Unit for groups
│
├── cn=research ← Default group for all users
│ ├── gidNumber: 1025
│ ├── memberUid: davidbau
│ ├── memberUid: arnab
│ └── memberUid: ...
│
└── cn=admin ← Admin group (special privileges)
└── memberUid: davidbau
Understanding Distinguished Names (DNs)
The DN is like a file path, but read from leaf to root:
uid=arnab,ou=people,dc=baulab,dc=usmeans "the arnab entry, in the people unit, in baulab.us"
The prefixes mean:
dc= Domain Component (baulab.us becomes dc=baulab,dc=us)ou= Organizational Unit (a folder/container)uid= User ID (the username)cn= Common Name (human-readable name)
Schema: What Makes Up a User or Group
User Entries
Each user entry uses several "object classes" that define what attributes it can have:
| Object Class | Purpose | Required Attributes |
|---|---|---|
posixAccount |
Unix user info | uid, uidNumber, gidNumber, homeDirectory |
shadowAccount |
Password aging | (optional shadow* attributes) |
account |
Basic account | uid |
top |
Base class | (inherited by all) |
Here's what a complete user entry looks like in LDIF format (LDAP Data Interchange Format):
dn: uid=arnab,ou=people,dc=baulab,dc=us
objectClass: account
objectClass: posixAccount
objectClass: shadowAccount
objectClass: top
uid: arnab
cn: Arnab Sen Sharma
uidNumber: 1007
gidNumber: 1025
homeDirectory: /share/u/arnab
loginShell: /bin/bash
userPassword: {SSHA}xxxxxxxxxxxx
shadowLastChange: 19658
shadowMax: 99999
shadowWarning: 7
Important fields when adding users:
| Attribute | What to Set | Notes |
|---|---|---|
uid |
username | Lowercase, no spaces |
cn |
"First Last" | User's real name |
uidNumber |
next available | Look at last user, add 1 |
gidNumber |
1025 | Always use research group |
homeDirectory |
/share/u/USERNAME | Must match uid |
userPassword |
initial password | phpLDAPadmin will hash it |
Group Entries
Groups are simpler - they just list their members:
dn: cn=research,ou=groups,dc=baulab,dc=us
objectClass: posixGroup
objectClass: top
cn: research
gidNumber: 1025
memberUid: davidbau
memberUid: arnab
memberUid: newstudent
Current groups:
| Group | GID | Purpose |
|---|---|---|
| research | 1025 | Default group for all lab members |
| admin | - | Administrative privileges |
| lighting | - | Lighting research project |
Accessing phpLDAPadmin
phpLDAPadmin is a web interface for managing LDAP. It makes adding users and changing passwords much easier than command-line tools.
Why SSH Tunneling is Required
phpLDAPadmin isn't exposed to the internet for security reasons. Instead, you create an SSH tunnel that forwards a port from your laptop to baunames:
Your Laptop Khoury Jump Host baunames
localhost:8877 ──────────► login.khoury ──────────► localhost:8877
│ (relay) │
└─────────────── SSH tunnel connection ──────────────────┘
Setting Up the SSH Tunnel
Add this to your laptop's ~/.ssh/config:
Host baunames
ProxyJump login.khoury.northeastern.edu
User localdavidbau
LocalForward 8877 localhost:8877
Host login.khoury.northeastern.edu
User YOUR_KHOURY_USERNAME
What this means:
ProxyJump: Connect through Khoury's jump host first (required - baunames isn't directly reachable)User localdavidbau: Log in as the local admin (not your LDAP user)LocalForward 8877 localhost:8877: Make baunames port 8877 appear as your localhost:8877
Connecting to phpLDAPadmin
-
Open a terminal and run:
ssh baunames- Keep this terminal open (the tunnel only works while connected)
-
Open your browser to:
http://localhost:8877/ -
Click "login" in the left sidebar
-
Enter credentials:
- Login DN:
cn=admin,dc=baulab,dc=us - Password: Ask David or Arnab
- Login DN:
You'll see a tree view of the directory on the left. Navigate by clicking the triangles to expand sections.
Common Administrative Tasks
Adding a New Lab Member
Before you start: Collect their preferred username and full name.
-
In phpLDAPadmin, expand
dc=baulab,dc=us→ou=people -
Click on the last user that was added (to see the highest uidNumber)
- Note their
uidNumber- you'll use the next number
- Note their
-
Click "Copy or move this entry" (at the top of the entry view)
-
Set the new DN:
uid=NEWUSERNAME,ou=people,dc=baulab,dc=us- Replace NEWUSERNAME with their actual username
-
Click "Copy"
-
Now edit the copied entry - change these fields:
uid: their username (should already be correct from the DN)cn: their full name ("Jane Smith")uidNumber: increment by 1 from the user you copiedhomeDirectory:/share/u/NEWUSERNAMEuserPassword: click to set their initial password- Leave
gidNumberas 1025 (research group)
-
Click "Update Object"
-
Important: Also add the same user to the baukit.org cluster (David's home network uses the same user list)
Changing Someone's Password
- Navigate to the user in
ou=people - Click on the
userPasswordattribute value - Enter the new password
- Select "ssha" as the hash type (Salted SHA - most secure option)
- Click "Update Object"
Best practice: Set both baulab.us and baukit.org passwords at the same time so they match.
Adding a User to a Group
Why would you do this? Maybe you have a shared project directory that only certain people should access.
- Navigate to
ou=groups→ click on the group (e.g.,cn=lighting) - Click "Add new attribute"
- Select
memberUidfrom the dropdown - Enter the username (just
arnab, not the full DN) - Click "Update Object"
Command-Line Administration
Sometimes the command line is faster, especially for quick queries.
Searching the Directory
# List all users (shows just uid and cn)
ldapsearch -x -H ldap://localhost -b "ou=people,dc=baulab,dc=us" uid cn
# Find a specific user (shows all their attributes)
ldapsearch -x -H ldap://localhost -b "dc=baulab,dc=us" "(uid=arnab)"
# List all groups
ldapsearch -x -H ldap://localhost -b "ou=groups,dc=baulab,dc=us" cn memberUid
# Find highest uidNumber (useful when adding users)
ldapsearch -x -H ldap://localhost -b "ou=people,dc=baulab,dc=us" uidNumber | grep uidNumber | sort -t: -k2 -n | tail -1
What the flags mean:
-x: Use simple authentication (not SASL)-H ldap://localhost: Connect to LDAP on this machine-b "...": Search starting from this DN (the "base")
Changing a Password via Command Line
ldappasswd -x -D "cn=admin,dc=baulab,dc=us" -W -S "uid=arnab,ou=people,dc=baulab,dc=us"
-D "cn=admin,...": Authenticate as admin-W: Prompt for admin password-S: Prompt for new user password
Backup Procedures
WARNING: As of this writing, no automated backups are configured! This should be fixed.
Why Backups Matter
The LDAP database contains all user accounts. If it's corrupted or lost:
- Nobody can log into any machine
- All user IDs become "unknown"
- File permissions become meaningless (files owned by uid 1007 instead of "arnab")
Manual Backup
Run these commands on baunames:
# Backup the server configuration (schemas, access rules, etc.)
sudo slapcat -n 0 -l /share/backup/config/ldap-config-$(date +%Y%m%d).ldif
# Backup the actual user/group data
sudo slapcat -n 1 -l /share/backup/config/ldap-data-$(date +%Y%m%d).ldif
# Backup TLS certificates
sudo cp /etc/ldap/baunames_slapd_*.pem /share/backup/config/
What slapcat does: Exports the LDAP database to a text file (LDIF format) that can be reimported on a new server.
Setting Up Automated Backups
Add to root's crontab on baunames:
sudo crontab -e
# Add this line:
0 3 * * 0 /home/localdavidbau/admin/lab/scripts/backup_ldap_full.sh >> /var/log/ldap-backup.log 2>&1
This runs the backup script every Sunday at 3 AM.
Troubleshooting
User Can't Log In
Start from the user's machine and work backward:
Step 1: Check if the system knows the user exists
getent passwd username
If this returns nothing, the machine can't reach LDAP or the user doesn't exist.
Step 2: Check SSSD (the LDAP client daemon)
sudo systemctl status sssd
If it's not running, start it: sudo systemctl start sssd
Step 3: Test LDAP connectivity directly
ldapsearch -x -H ldap://baunames -b "dc=baulab,dc=us" "(uid=username)"
If this fails, either the network is down or slapd isn't running on baunames.
Step 4: Clear the SSSD cache and retry
sudo sss_cache -E
sudo systemctl restart sssd
Sometimes SSSD caches stale data.
slapd (LDAP Server) Not Running
On baunames:
# Check status
sudo systemctl status slapd
# View recent logs
sudo journalctl -u slapd -n 50
# Start if stopped
sudo systemctl start slapd
Common causes:
- Database corruption (rare) - restore from backup
- Config error after manual changes - check journalctl logs
- Disk full - check
df -h
phpLDAPadmin Issues
| Problem | Likely Cause | Solution |
|---|---|---|
| Browser can't connect to localhost:8877 | SSH tunnel not running | Make sure ssh baunames is connected |
| "Unable to connect to LDAP server" | slapd not running | sudo systemctl start slapd on baunames |
| Login fails with correct password | Typo in DN | Use exactly: cn=admin,dc=baulab,dc=us |
Server Technical Details
Key Files on baunames
| Path | Purpose |
|---|---|
/etc/ldap/slapd.d/ |
Server configuration (cn=config format) |
/var/lib/ldap/ |
Database files (LMDB format, ~220KB) |
/etc/ldap/baunames_slapd_cert.pem |
TLS certificate |
/etc/ldap/baunames_slapd_key.pem |
TLS private key (readable only by openldap user) |
/etc/phpldapadmin/config.php |
phpLDAPadmin configuration |
/etc/ldap/ldap.conf |
LDAP client configuration |
Services
# LDAP server daemon
sudo systemctl {start|stop|restart|status} slapd
# Apache (serves phpLDAPadmin)
sudo systemctl {start|stop|restart|status} apache2
Local Admin Accounts
baunames (like all machines) has local admin accounts that work even if LDAP is broken:
localdavidbaulocalarnab
These are in /etc/passwd and have passwordless sudo. Always use these for admin work.
Why Two Clusters (baulab.us and baukit.org)?
You may have noticed references to two LDAP servers. The lab maintains parallel infrastructure:
| Aspect | baulab.us (Khoury) | baukit.org (David's home) |
|---|---|---|
| LDAP Server | baunames (10.200.205.143) | names (192.168.0.2) |
| Base DN | dc=baulab,dc=us | dc=thevisible,dc=net |
| phpLDAPadmin | Port 8877 | Port 8876 |
| Purpose | Lab GPU cluster | Personal development |
Important: When adding users, add them to both clusters so they have consistent access everywhere.
Related Pages
- Server-Administration-Notes - General admin overview
- Network-Architecture - Network diagram and host inventory
- Ansible-Playbook-Reference - How client machines are configured
- Server-Migration-Guide - Moving to new hardware
Further Reading
- Medium: Understanding NSS and PAM - Great explanation of the login process
- Detailed documentation:
/home/localdavidbau/admin/lab/openldap-documentation.md