사용자 정보에 이런 주소가 적혀 있다고 해보자.
CN=Jane,OU=Finance,DC=example,DC=com
Jane은 Finance 그룹의 멤버일까?
처음 LDAP를 보면 거의 자동으로 “그렇다”고 답하게 된다. Finance라는 폴더 아래 Jane이 있으니 Finance 팀 사람처럼 보이기 때문이다.
그런데 LDAP에서는 이 한 줄만 보고 그룹 멤버라고 결론 내릴 수 없다.
OU는 주소이고, Group은 명단이다.
이 차이를 놓치면 LDAP 연동 화면에서 Search Filter를 바꾸고, Base DN을 옮기고, 동기화 버튼을 다시 눌러도 계속 같은 자리에서 헤매게 된다.
오늘은 LDAP를 어려운 인증 프로토콜이 아니라 회사 전화번호부로 읽어보자.
LDAP는 조직도가 아니라 검색 가능한 전화번호부다
LDAP는 Lightweight Directory Access Protocol의 약자다.
이름부터 가볍지 않다. 하지만 역할은 생각보다 친숙하다.
회사에는 직원 이름, 이메일, 부서, 계정 상태, 그룹 같은 정보가 있다. LDAP와 Active Directory는 이 정보를 계층적으로 정리하고 검색할 수 있게 해준다.
전화번호부로 비유하면 이렇다.
Bind DN + Password 전화번호부를 열람하는 사서 계정
Base DN 어느 책장부터 찾을지 정하는 시작점
Search Filter 어떤 조건의 카드를 찾을지 정하는 검색식
Entry 직원이나 그룹 정보가 적힌 카드 한 장
DN 그 카드가 꽂힌 정확한 주소
Attribute 카드 안에 적힌 이름, 이메일, 소속 정보
LDAP 연동에서 벌어지는 일도 크게 다르지 않다.
- 서비스 계정으로 전화번호부를 연다.
- 어느 책장부터 찾을지 정한다.
- 검색 조건으로 사용자나 그룹을 찾는다.
- 찾은 카드의 속성을 읽는다.
- 사용자와 그룹 관계를 애플리케이션에 반영한다.
복잡해 보였던 설정 화면이 갑자기 조금 인간적으로 보인다.
Entry는 카드 한 장이다
LDAP의 객체 하나를 entry라고 부른다.
사용자 entry는 이렇게 생길 수 있다.
dn: CN=Jane,OU=Finance,DC=example,DC=com
objectClass: user
cn: Jane
mail: jane@example.com
memberOf: CN=Finance-Users,OU=Groups,DC=example,DC=com
그룹 entry는 이렇게 생길 수 있다.
dn: CN=Finance-Users,OU=Groups,DC=example,DC=com
objectClass: group
cn: Finance-Users
member: CN=Jane,OU=Finance,DC=example,DC=com
OU entry도 따로 있다.
dn: OU=Finance,DC=example,DC=com
objectClass: organizationalUnit
ou: Finance
세 가지를 나란히 놓으면 차이가 보인다.
- Jane은 사람이다.
- Finance-Users는 사람을 묶는 그룹이다.
- Finance OU는 객체를 정리하는 디렉터리 위치다.
파일 시스템으로 비유하면 OU는 폴더에 가깝고 Group은 공유 명단에 가깝다.
/company/finance/jane이라는 경로에 파일이 있다고 해서 그 파일이 자동으로 finance-admins 권한 그룹에 들어가는 건 아니다.
LDAP도 마찬가지다.
DN은 소속 증명서가 아니라 주소다
DN은 Distinguished Name의 약자다. LDAP entry의 전체 주소다.
CN=Jane,OU=Finance,DC=example,DC=com
오른쪽부터 읽으면 편하다.
example.com 디렉터리의
Finance OU 안에 있는
Jane 사용자
여기서 DN이 알려주는 것은 “Jane이 어디에 저장되어 있는가”다.
“Jane에게 어떤 권한이 있는가”, “어떤 그룹의 멤버인가”까지 자동으로 말해주지는 않는다.
이 구분은 단순한 용어 문제가 아니다.
기업용 애플리케이션은 그룹을 기준으로 역할과 접근 권한을 연결하는 경우가 많다. OU 경로를 그룹 membership으로 착각하면 엉뚱한 사용자가 빠지거나, 반대로 원하지 않은 사용자가 포함될 수 있다.
주소와 권한을 섞는 순간 디렉터리 연동은 위험해진다.
진짜 소속 증거는 membership attribute다
그룹 소속은 보통 attribute로 표현된다.
대표적인 방식은 두 가지다.
사용자가 소속 그룹을 들고 있는 방식
memberOf: CN=Finance-Users,OU=Groups,DC=example,DC=com
Jane의 카드에 “나는 Finance-Users 그룹에 속해 있다”고 적혀 있다.
그룹이 사용자 명단을 들고 있는 방식
member: CN=Jane,OU=Finance,DC=example,DC=com
Finance-Users 그룹 카드에 Jane의 DN이 적혀 있다.
LDAP 종류와 schema에 따라 memberOf, member, uniqueMember, memberUid, gidNumber 같은 속성을 사용할 수 있다.
중요한 건 이름을 전부 외우는 게 아니다.
다음 질문 하나를 기억하면 된다.
사용자 카드가 그룹 목록을 들고 있는가, 그룹 카드가 사용자 목록을 들고 있는가?
이 방향을 먼저 확인하면 Membership Type 설정도 훨씬 쉽게 이해된다.
Base DN은 책장을 고르는 일이다
Base DN은 검색 시작 위치다.
User Base DN: OU=Employees,DC=example,DC=com
Group Base DN: OU=Groups,DC=example,DC=com
사용자는 Employees 책장에서 찾고, 그룹은 Groups 책장에서 찾는다는 뜻이다.
Base DN이 너무 좁으면 일부 사용자가 검색되지 않는다.
OU=Seoul,OU=Employees,DC=example,DC=com
이렇게 잡으면 Busan OU의 직원은 아무리 정확한 Search Filter를 써도 검색 대상에 들어오지 않는다.
반대로 Base DN을 너무 넓게 잡으면 필요 없는 서비스 계정과 장비 객체까지 검색될 수 있다.
Base DN은 “필터보다 앞에 있는 범위”다.
검색 조건을 의심하기 전에 책장을 제대로 골랐는지 먼저 봐야 한다.
Search Filter는 검색창에 넣는 조건이다
Search Filter는 Base DN 아래에서 어떤 entry를 가져올지 정한다.
(objectClass=user)
(objectClass=person)
(objectClass=group)
(objectClass=posixGroup)
여러 조건을 묶을 수도 있다.
(&(objectClass=user)(mail=*))
뜻은 이렇다.
user 객체이면서
mail 속성이 있는 entry를 찾아줘
여기서 흔한 실수가 있다.
OU를 그룹처럼 가져오고 싶어서 Group Search Filter를 organizationalUnit으로 바꾸는 것이다.
검색에는 성공할 수 있다. 하지만 검색된 객체가 실제 Group으로 동작한다는 뜻은 아니다.
그룹 이름으로 쓸 속성이 있는지, 안정적인 Group ID가 있는지, 사용자 membership은 어디에 적혀 있는지까지 맞아야 한다.
검색 성공과 그룹 동기화 성공은 다른 문제다.
중첩 OU와 중첩 Group도 다르다
이름이 비슷해서 자주 섞이는 개념이 하나 더 있다.
여러 단계 OU 아래에 실제 Group이 있는 경우
OU=Company
OU=Security
OU=Groups
CN=Audit-Team
Audit-Team이 실제 Group 객체이고 검색 범위와 필터가 맞는다면 찾을 수 있다. OU가 여러 단계라는 사실 자체가 문제는 아니다.
Group 안에 다른 Group이 있는 경우
All-Finance
└─ Finance-Users
└─ Jane
이건 nested Group이다.
애플리케이션이 Jane의 직접 그룹만 읽는지, 부모 그룹인 All-Finance까지 재귀적으로 펼치는지는 제품과 설정에 따라 다르다.
그러므로 “중첩 구조를 지원하나요?”라는 질문은 너무 넓다.
아래처럼 다시 물어야 한다.
OU 아래의 실제 Group을 검색하려는가?
OU 자체를 Group으로 쓰려는가?
Group 안의 Group을 재귀적으로 펼치려는가?
세 문장은 완전히 다른 기능을 묻고 있다.
LDAP와 LDAPS는 전화 통화와 암호화 통화의 차이다
LDAP는 보통 389 포트, LDAPS는 보통 636 포트를 사용한다.
ldap://ldap.example.com:389
ldaps://ldap.example.com:636
LDAPS는 TLS로 암호화한 LDAP 통신이다.
그래서 포트가 열려 있어도 인증에 실패할 수 있다.
- Bind DN이나 비밀번호가 잘못됨
- 서비스 계정에 검색 권한이 없음
- 인증서 체인을 신뢰하지 못함
- 인증서의 hostname과 접속 주소가 다름
- 서버가 client certificate을 요구함
네트워크 연결 성공과 LDAP 인증 성공은 다르다.
전화벨이 울린다고 상대방이 신분 확인까지 끝낸 건 아니다.
“동기화가 안 돼요”를 다섯 질문으로 줄이기
LDAP 문제를 만났을 때 처음부터 모든 설정을 바꿀 필요는 없다.
다섯 가지만 순서대로 묻자.
1. 무엇이 실패했나?
연결, Bind, 사용자 검색, 그룹 검색, membership 중 어디인가?
2. 어디서부터 찾고 있나?
User Base DN과 Group Base DN이 실제 객체 위치를 포함하는가?
3. 무엇을 찾고 있나?
Search Filter가 실제 objectClass와 맞는가?
4. 어떻게 묶고 있나?
사용자 또는 그룹 중 누가 membership attribute를 들고 있는가?
5. 기대한 결과가 무엇인가?
OU 아래 사용자만 필요한가, 실제 Group이 필요한가, OU 자체를 Group으로 만들고 싶은가?
특히 마지막 질문이 중요하다.
설정을 열심히 고쳤는데도 해결되지 않는 이유는 설정이 틀려서가 아니라, 제품이 해결하는 문제와 사용자가 원하는 문제가 다르기 때문일 수 있다.
LDAP를 읽는 가장 짧은 공식
LDAP를 처음 볼 때는 이 순서면 충분하다.
어디서 찾나? → Base DN
무엇을 찾나? → Search Filter
무엇을 찾았나? → Entry와 objectClass
주소가 뭔가? → DN
어떻게 묶나? → Membership attribute
그리고 한 줄만 더 기억하자.
OU는 주소이고, Group은 명단이다.
다음에 CN=Jane,OU=Finance,...를 보게 되면 Jane의 부서를 짐작하는 데서 멈추지 말고, memberOf나 Group의 member를 찾아보자.
LDAP가 외계어에서 전화번호부로 바뀌는 순간이다.