Enterprise Authentication Integration
The LDAP Tree, Search Filters, and the Org-Chart Trap
Summary
LDAP is a tree, and in this tree whether the group points to people or people point to the group decides the direction of lookups — if you get this direction wrong, login gets slower in proportion to the number of users.
Why this is needed
"Please connect the company accounts" ends up as an LDAP lookup. But if you write the filter carelessly, inactive accounts and service accounts get into the user list too, and if you do not specify the returned attributes, you pull all attributes from a 100,000-person directory and torment the server.
The quieter problem is direction. In this directory, groups point to people through member, so no matter how much you look at a user entry, the groups it belongs to do not appear. Code that scans all groups on every login, without knowing this, really does get written. With 100 users nobody notices, and at 10,000 users login takes 3 seconds.
The DIT — a directory is a tree
LDAP data is held in a tree called the DIT (Directory Information Tree). Each node (entry) is uniquely identified by a DN (Distinguished Name).
dc=labhub,dc=co,dc=kr ← 루트 (Base DN)
├── ou=People
│ ├── uid=hong,ou=People,dc=labhub,dc=co,dc=kr
│ └── uid=kim,ou=People,dc=labhub,dc=co,dc=kr
├── ou=Groups
│ ├── cn=dev-team,ou=Groups,...
│ └── cn=role-admin,ou=Groups,...
└── ou=Disabled ← 퇴사자 격리 구역
A DN is read from bottom to top. uid=hong,ou=People,dc=labhub,dc=co,dc=kr means
"hong in the People unit of the labhub.co.kr organization." It runs in the opposite direction
from a file path, so it is confusing at first.
If you know these main attribute abbreviations, you can get started.
| Abbreviation | Meaning | Example |
|---|---|---|
dc |
domainComponent | dc=labhub,dc=co,dc=kr |
ou |
organizationalUnit | ou=People |
cn |
commonName | cn=홍길동, cn=dev-team |
uid |
userid | uid=hong |
sn / givenName |
Surname / given name | sn=홍, givenName=길동 |
mail |
hong@labhub.co.kr |
|
title |
Rank/position | title=과장 |
member |
Group member (DN) | member=uid=hong,ou=People,... |
Search filters — the prefix notation of RFC 4515
An LDAP filter is a parenthesized notation where the operator comes first. It is unfamiliar at first, but the rules are simple.
(uid=hong) 단순 일치
(cn=홍*) 앞부분 일치 (와일드카드)
(&(A)(B)) A 그리고 B
(|(A)(B)) A 또는 B
(!(A)) A 가 아님
(objectClass=*) 해당 속성이 존재
Combined, it looks like this.
(&(objectClass=inetOrgPerson)(ou=개발팀)(title=과장))
(&(objectClass=inetOrgPerson)(|(title=부장)(title=차장))(!(ou=총무팀)))
With ldapsearch you write it like this.
ldapsearch -x -H ldap://127.0.0.1:1389 \
-D "cn=admin,dc=labhub,dc=co,dc=kr" -w labhub123 \
-b "dc=labhub,dc=co,dc=kr" \
"(&(objectClass=inetOrgPerson)(title=과장))" uid cn ou
-xsimple authentication (instead of SASL),-Hserver URI,-bsearch starting point (Base)-D/-wthe bind DN and password.-wexposes the password in the process list. In production scripts you should use-y 파일(a file). This is a regular item in security audits- The last arguments are the list of attributes to return. If omitted, all come back. If you receive all attributes from a 100,000-person directory, both the server and the network suffer
The search scope (-s) is also good to know: base (the entry itself only), one (direct children only),
and sub (the whole subtree, the default). Overusing sub on a large directory makes it slow.
Groups — there are two directions
There are two ways to express membership. And this difference decides performance and synchronization.
- The group points to people (the
groupOfNamesmemberattribute)- The group entry holds a list of member DNs
- "Who are the members of this group?" is fast
- "Which groups does this person belong to?" requires searching through all groups
- The person points to groups (the
memberOfattribute)- The user entry holds a list of the DNs of groups it belongs to
- AD manages this automatically. OpenLDAP requires enabling an overlay
- "What are this person's groups?" is fast — advantageous for permission decisions at login
A practical pitfall: in a directory that has member but no memberOf,
code that scans all groups on every login gets written. With 100 users nobody notices, and
at 10,000 users login takes 3 seconds.
Practical differences between AD and OpenLDAP
| Item | Active Directory | OpenLDAP |
|---|---|---|
| Login ID attribute | sAMAccountName (or userPrincipalName) |
Usually uid |
| Immutable identifier | objectGUID |
entryUUID |
| Group back-reference | memberOf provided by default |
Needs the memberof overlay |
| Password attribute | unicodePwd (cannot be changed over plaintext LDAP) |
userPassword |
| Account state | userAccountControl bits |
Separate convention |
Why immutable identifiers matter. People change names when they marry and change DNs when they move departments.
If you synchronize with uid or the DN as the key, the same person is created twice as a new user.
Always use an immutable value such as objectGUID / entryUUID as the link key.
If you get this design wrong, duplicate account creation incidents happen, and they pile up silently for months.
userAccountControl is a bit flag. If you work with AD, it is good to memorize these three.
0x0002 ACCOUNTDISABLE 계정 비활성
0x0010 LOCKOUT 잠김
0x10000 DONT_EXPIRE_PASSWD 비밀번호 만료 없음
512 = 정상 계정
514 = 정상 + 비활성 (512 + 2)
66048 = 정상 + 비밀번호 만료 없음 (512 + 65536)
So an AD filter that selects "active users only" looks like this.
(&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
The OID in the middle is the "bitwise AND" matching rule. It looks like a cipher at first, but it means "exclude those with the 2 bit set in userAccountControl." If you do not use this filter, inactive accounts and computer accounts get into the user list too.
Two pitfalls of synchronization
These are problems you inevitably meet when periodically synchronizing users from the directory to our system (or the IdP).
- Incremental synchronization cannot detect deletions. An approach that fetches "recently changed entries" has no way to see entries that have vanished. So accounts of departed employees survive on our side. This is the usual cause of ghost accounts. → Run incremental synchronization often (hourly) and run a full synchronization periodically (weekly) alongside it.
- The change time is the directory server's clock. If there is a single server with drifted NTP, changes in that window are silently missed. The symptom is "every so often one person is not reflected," and the cause is very hard to find.
Plaintext 389 is an audit finding
If you bind over a plaintext ldap:// connection, the password crosses the network in plaintext.
In production you must use ldaps:// (636) or StartTLS,
and a plaintext configuration is flagged immediately in a security audit.
(This lab environment cannot open ports below 1024 due to capability constraints, so it uses plaintext 1389.
Remember that this was done for learning and is not a production configuration.)