기업 인증 연동 · LDAP 과 조직도 · 이론
LDAP 트리와 검색 필터, 그리고 조직도의 함정
한 줄 요약
LDAP 은 트리이고, 이 트리에서 그룹이 사람을 가리키는지 사람이 그룹을 가리키는지가 조회 방향을 정한다 — 이 방향을 잘못 잡으면 로그인이 사용자 수에 비례해 느려진다.
왜 이게 필요한가
"사내 계정 붙여 주세요" 는 결국 LDAP 조회로 끝난다. 그런데 필터를 대충 쓰면 비활성 계정과 서비스 계정까지 사용자 목록에 들어오고, 반환 속성을 지정하지 않으면 10 만 명 디렉터리에서 전 속성을 끌어와 서버를 괴롭힌다.
더 조용한 문제는 방향이다. 이 디렉터리는 그룹이 member 로 사람을 가리키므로, 사용자 엔트리를 아무리 봐도 소속 그룹은 나오지 않는다. 이걸 모르고 로그인마다 전체 그룹을 훑는 코드가 실제로 만들어진다. 사용자 100 명일 땐 아무도 모르다가 1 만 명이 되면 로그인이 3 초가 된다.
DIT — 디렉터리는 트리다
LDAP 의 데이터는 DIT(Directory Information Tree) 라는 트리에 담긴다.
각 노드(엔트리)는 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 ← 퇴사자 격리 구역DN 은 아래에서 위로 읽는다. uid=hong,ou=People,dc=labhub,dc=co,dc=kr 은
"labhub.co.kr 조직의 People 부서의 hong" 이다. 파일 경로와 반대 방향이라
처음에 헷갈린다.
주요 속성 약어는 이 정도만 알면 시작할 수 있다.
| 약어 | 뜻 | 예 |
| --- | --- | --- |
| dc | domainComponent | dc=labhub,dc=co,dc=kr |
| ou | organizationalUnit | ou=People |
| cn | commonName | cn=홍길동, cn=dev-team |
| uid | userid | uid=hong |
| sn / givenName | 성 / 이름 | sn=홍, givenName=길동 |
| mail | 메일 | hong@labhub.co.kr |
| title | 직급/직책 | title=과장 |
| member | 그룹 구성원 (DN) | member=uid=hong,ou=People,... |
검색 필터 — RFC 4515 의 전위 표기법
LDAP 필터는 연산자가 앞에 오는 괄호 표기다. 처음엔 낯설지만 규칙은 단순하다.
(uid=hong) 단순 일치(cn=홍*) 앞부분 일치 (와일드카드)(&(A)(B)) A 그리고 B(|(A)(B)) A 또는 B(!(A)) A 가 아님(objectClass=*) 해당 속성이 존재조합하면 이렇게 된다.
(&(objectClass=inetOrgPerson)(ou=개발팀)(title=과장))(&(objectClass=inetOrgPerson)(|(title=부장)(title=차장))(!(ou=총무팀)))ldapsearch 로는 이렇게 쓴다.
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-x단순 인증(SASL 대신),-H서버 URI,-b검색 시작점(Base)-D/-wbind DN 과 비밀번호.-w는 프로세스 목록에 비밀번호가 노출된다.- 마지막 인자들은 반환받을 속성 목록. 안 적으면 전부 온다.
운영 스크립트에서는 -y 파일 을 써야 한다. 이건 보안 점검 단골이다
10만 명 디렉터리에서 전 속성을 받으면 서버와 네트워크가 함께 아프다
검색 범위(-s)도 알아 두면 좋다: base(자기만), one(직계 자식만),sub(하위 전체, 기본값). 큰 디렉터리에서 sub 를 남발하면 느려진다.
그룹 — 두 가지 방향이 있다
멤버십을 표현하는 방법이 두 개다. 그리고 이 차이가 성능과 동기화를 좌우한다.
1. 그룹이 사람을 가리킨다 (groupOfNames 의 member 속성)
- 그룹 엔트리에 구성원 DN 목록이 들어 있다
- "이 그룹의 멤버는 누구인가"는 빠르다
- "이 사람이 속한 그룹은?"은 전체 그룹을 뒤져야 한다
- 사용자 엔트리에 소속 그룹 DN 목록이 있다
- AD 는 이걸 자동으로 관리한다. OpenLDAP 은 오버레이를 켜야 한다
- "이 사람의 그룹은?"이 빠르다 — 로그인 시 권한 판정에 유리하다
2. 사람이 그룹을 가리킨다 (memberOf 속성)
실무 함정: member 는 있는데 memberOf 가 없는 디렉터리에서
로그인마다 전체 그룹을 스캔하는 코드가 만들어진다. 사용자 100명일 땐 모르다가
1만 명이 되면 로그인이 3초가 된다.
AD 와 OpenLDAP 의 실무적 차이
| 항목 | Active Directory | OpenLDAP |
| --- | --- | --- |
| 로그인 ID 속성 | sAMAccountName (또는 userPrincipalName) | 보통 uid |
| 불변 식별자 | objectGUID | entryUUID |
| 그룹 역참조 | memberOf 기본 제공 | memberof 오버레이 필요 |
| 비밀번호 속성 | unicodePwd (평문 LDAP 로 변경 불가) | userPassword |
| 계정 상태 | userAccountControl 비트 | 별도 규약 |
불변 식별자가 왜 중요한가. 사람은 결혼해서 이름이 바뀌고, 부서를 옮겨 DN 이 바뀐다.uid 나 DN 을 키로 동기화하면 같은 사람이 신규 사용자로 두 번 생긴다.
반드시 objectGUID / entryUUID 같은 불변 값을 연결 키로 쓴다.
이 설계를 잘못하면 계정 중복 생성 사고가 나고, 그건 조용히 몇 달간 쌓인다.
userAccountControl 은 비트 플래그다. AD 를 다룬다면 이 셋은 외워 두면 좋다.
0x0002 ACCOUNTDISABLE 계정 비활성0x0010 LOCKOUT 잠김0x10000 DONT_EXPIRE_PASSWD 비밀번호 만료 없음512 = 정상 계정514 = 정상 + 비활성 (512 + 2)66048 = 정상 + 비밀번호 만료 없음 (512 + 65536)그래서 "활성 사용자만" 뽑는 AD 필터는 이렇게 생겼다.
(&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))가운데 OID 는 "비트 AND" 매칭 규칙이다. 처음 보면 암호 같지만
"userAccountControl 에 2 비트가 켜진 것을 제외"라는 뜻이다.
이 필터를 안 쓰면 비활성 계정과 컴퓨터 계정까지 사용자 목록에 들어온다.
동기화의 두 가지 함정
디렉터리에서 우리 시스템(또는 IdP)으로 사용자를 주기 동기화할 때 반드시 만나는 문제다.
1. 변경분 동기화는 삭제를 감지하지 못한다.
"최근 변경된 엔트리"를 가져오는 방식은, 사라진 엔트리를 볼 방법이 없다.
그래서 퇴사자 계정이 우리 쪽에 살아남는다. 이것이 유령 계정의 단골 원인이다.
→ 변경분 동기화는 자주(1시간), 전체 동기화는 주기적으로(주 1회) 함께 돌린다.
2. 변경 시각은 디렉터리 서버의 시계다.
NTP 가 어긋난 서버 한 대가 있으면, 그 구간의 변경이 조용히 누락된다.
증상은 "가끔 한 명씩 반영이 안 돼요"이고, 원인 파악이 매우 어렵다.
평문 389 은 감사 지적 대상이다
ldap:// 평문 연결로 bind 하면 비밀번호가 네트워크를 평문으로 지나간다.
운영 환경에서는 ldaps://(636) 또는 StartTLS 를 써야 하고,
평문 구성은 보안 감사에서 즉시 지적된다.
(이 실습 환경은 capability 제약으로 1024 미만 포트를 못 열어 1389 평문을 쓴다.
학습용이라 그렇게 한 것이지, 운영 구성이 아니라는 점을 기억하자.)