LabHub
배우기 러닝패스 코스

企業認証の連携

LDAPツリーと検索フィルタ、そして組織図の罠

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

LDAP 은 트리이고, 이 트리에서 그룹이 사람을 가리키는지 사람이 그룹을 가리키는지가 조회 방향을 정한다 — 이 방향을 잘못 잡으면 로그인이 사용자 수에 비례해 느려진다.

概念マップ: 그룹이 사람을 가리키는지 사람이 그룹을 가리키는지・DIT(Directory Information Tree)・DN(Distinguished Name)・아래에서 위로

왜 이게 필요한가

"사내 계정 붙여 주세요" 는 결국 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

검색 범위(-s)도 알아 두면 좋다: base(자기만), one(직계 자식만), sub(하위 전체, 기본값). 큰 디렉터리에서 sub 를 남발하면 느려진다.

그룹 — 두 가지 방향이 있다

멤버십을 표현하는 방법이 두 개다. 그리고 이 차이가 성능과 동기화를 좌우한다.

  1. 그룹이 사람을 가리킨다 (groupOfNamesmember 속성)
    • 그룹 엔트리에 구성원 DN 목록이 들어 있다
    • "이 그룹의 멤버는 누구인가"는 빠르다
    • "이 사람이 속한 그룹은?"은 전체 그룹을 뒤져야 한다
  2. 사람이 그룹을 가리킨다 (memberOf 속성)
    • 사용자 엔트리에 소속 그룹 DN 목록이 있다
    • AD 는 이걸 자동으로 관리한다. OpenLDAP 은 오버레이를 켜야 한다
    • "이 사람의 그룹은?"이 빠르다 — 로그인 시 권한 판정에 유리하다

실무 함정: 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 평문을 쓴다. 학습용이라 그렇게 한 것이지, 운영 구성이 아니라는 점을 기억하자.)