설계가 전부다
OpenLDAP 설치 자체는 어렵지 않습니다. Docker 이미지 하나 올리면 10분 안에 LDAP 서버가 뜹니다.
진짜 어려운 것은 어떤 구조로 데이터를 담을 것인가입니다.
LDAP 트리 구조는 한 번 결정하고 나면 바꾸기 어렵습니다. 사용자 DN(Distinguished Name)을 변경하면 그 DN을 참조하는 모든 시스템 설정을 같이 바꿔야 합니다. 초기 설계가 잘못되면 Synology를 연동할 때, Samba를 붙일 때, 그룹웨어를 개발할 때마다 예상치 못한 문제가 터집니다.
이 편에서는 실제 운영 중인 구조를 바탕으로 각 설계 결정의 근거를 설명합니다.
전체 디렉토리 트리
먼저 완성된 구조를 한눈에 봅니다.
dc=ldap,dc=example,dc=com
├── cn=readonly # 읽기 전용 서비스 계정
├── sambaDomainName=ldap # Samba 도메인 정보
├── ou=people # 사용자 (38명)
│ ├── uid=user01,...
│ └── uid=user02,...
├── ou=Groups # 그룹 (21개)
│ ├── cn=role-admin # 역할: 관리자
│ ├── cn=role-hr # 역할: 인사담당
│ ├── cn=status-active # 재직상태: 재직
│ ├── cn=status-on-leave # 재직상태: 휴직
│ ├── cn=status-resigned # 재직상태: 퇴직
│ ├── cn=type-regular # 고용형태: 정규직
│ ├── cn=type-contract # 고용형태: 계약직
│ ├── cn=type-freelance # 고용형태: 프리랜서
│ ├── cn=dept-headquarters # 부서: 본사
│ ├── cn=dept-dev # 부서: 개발
│ ├── cn=dept-design # 부서: 디자인
│ ├── cn=dept-marketing # 부서: 마케팅
│ └── ... (총 13개 부서 그룹)
├── ou=services # 서비스 계정
└── ou=organization # 조직 구조 정보
Base DN: 디렉토리의 뿌리
Base DN이란
Base DN(Base Distinguished Name)은 LDAP 트리 전체의 최상위 루트입니다.
모든 엔트리의 DN은 이 Base DN으로 끝납니다.
uid=john,ou=people,dc=ldap,dc=example,dc=com
└─────────────────────────┘
Base DN
형식 선택
Base DN을 정하는 일반적인 방법은 두 가지입니다.
| 방식 | 예시 | 특징 |
|---|---|---|
| 도메인 기반 (DC) | dc=ldap,dc=example,dc=com | RFC 표준, 가장 보편적 |
| 조직 기반 (O) | o=MyCompany | 레거시 방식, 현재는 드묾 |
DC(Domain Component) 방식을 선택했습니다.
이유는 단순합니다 — RFC 2247이 권장하는 표준이고, 모든 LDAP 클라이언트가 이 형식을 기대합니다.
Synology DSM의 LDAP 설정 화면, Samba의 smb.conf, 그룹웨어 연동 코드 모두 DC 방식을 기본으로 가정합니다.
왜 서브도메인(ldap.*)을 썼나
dc=example,dc=com이 아니라 dc=ldap,dc=example,dc=com을 쓴 이유가 있습니다.
조직이 나중에 AD나 다른 LDAP 서버를 병행 운영하게 되면 Base DN이 충돌할 수 있습니다.ldap.example.com처럼 LDAP 서버 전용 서브도메인을 Base DN에 반영하면 네임스페이스가 분리됩니다.
실제로 LDAPS 접속 주소도 ldaps://ldap.example.com:10636으로 Base DN과 일치하게 됩니다.
OU 구조: 무엇을 분리하고 무엇을 합칠 것인가
4개 최상위 OU
Root 아래 4개의 OU로 역할을 분리했습니다.
| OU | 용도 | 비고 |
|---|---|---|
ou=people | 실제 사용자 계정 | LDAP 인증 대상 |
ou=Groups | 역할·부서·재직상태 그룹 | 권한 관리 기반 |
ou=services | 서비스 계정 (읽기 전용 등) | 시스템 연동용 |
ou=organization | 조직 구조 정보 | 참조용 |
ou=people: 완전한 평면 구조
사용자를 부서별로 중첩 OU에 담고 싶은 충동이 있습니다.
# 하고 싶었던 구조 (채택하지 않음)
ou=people
├── ou=dev
│ └── uid=john
├── ou=design
│ └── uid=jane
채택하지 않은 이유가 있습니다.
첫째, LDAP 검색 범위 문제입니다. 일부 시스템은 검색 범위를 one(직계 하위만)으로 고정하거나 탐색 깊이에 제한을 둡니다. ou=people 하나만 지정하면 그 아래 모든 사용자를 sub 스코프 한 번에 찾을 수 있지만, 중첩 구조에서는 시스템에 따라 일부 사용자가 검색되지 않을 수 있습니다.
둘째, 부서 이동 시 DN이 바뀝니다. uid=john,ou=dev,ou=people,...에서 uid=john,ou=design,ou=people,...으로 DN이 변경되면 Synology, Samba, 그룹웨어가 참조하는 DN을 모두 업데이트해야 합니다.
결론: 사용자는 모두 ou=people 직하에 flat하게 배치했습니다. 부서 소속은 그룹 멤버십으로 표현합니다.
# 실제 구조
ou=people
├── uid=user01
├── uid=user02
└── uid=user03
ou=Groups: 네이밍 전략
그룹 설계에서 가장 중요한 결정은 네이밍 규칙입니다.
그룹을 목적에 따라 세 가지 차원으로 구분하고, 각각 접두사로 구별했습니다.
| 접두사 | 차원 | 예시 | gidNumber 범위 |
|---|---|---|---|
role- | 역할 (시스템 권한) | role-admin, role-hr | 3001 ~ 3002 |
status- | 재직 상태 | status-active, status-resigned | 3003 ~ 3005 |
type- | 고용 형태 | type-regular, type-contract | 3006 ~ 3008 |
dept- | 부서 소속 | dept-dev, dept-marketing | 4002 ~ |
이렇게 설계한 이유는 세 가지입니다.
1. 검색 필터 작성이 쉬워집니다.
Synology나 그룹웨어에서 “부서 그룹만” 또는 “재직 중인 사람만” 필터링이 필요할 때,(cn=dept-*), (cn=status-active) 처럼 접두사 패턴으로 바로 걸러낼 수 있습니다.
2. gidNumber 범위로 그룹 성격을 코드화했습니다.
posixAccount는 기본 그룹을 gidNumber 하나로 지정합니다.
gidNumber 범위만 봐도 그 사용자의 소속 부서(4000번대)인지 역할(3000번대)인지 알 수 있습니다.
3. 나중에 그룹이 늘어도 정리된 상태를 유지합니다.
부서가 추가되면 dept-로 시작하는 그룹을 추가하면 됩니다.
접두사 없이 admin, hr, active, dev 처럼 섞어 쓰면 그룹이 30개가 넘어갈 때 관리가 어려워집니다.
검색 범위(Scope)를 이해해야 연동이 된다
LDAP 검색에는 세 가지 범위가 있습니다. 이를 잘못 설정하면 사용자나 그룹이 검색되지 않습니다.
| Scope | 의미 | 사용 예 |
|---|---|---|
base | Base DN 자체만 조회 | RootDSE 확인 |
one | Base DN 직계 하위만 | OU 목록 확인 |
sub | Base DN 아래 전체 재귀 | 사용자·그룹 검색 (일반적) |
실제 연동에서 가장 많이 쓰는 것은 sub입니다.
# sub 스코프로 ou=people 전체 사용자 검색
ldapsearch -x -H ldaps://ldap.example.com:10636 \
-D "cn=readonly,dc=ldap,dc=example,dc=com" \
-w 'readonly_password' \
-b "ou=people,dc=ldap,dc=example,dc=com" \
-s sub \
"(objectClass=posixAccount)"
Synology에서 scope가 문제가 된 경우
Synology DSM LDAP 클라이언트 설정에는 “사용자 검색 기준(Base DN)”과 “그룹 검색 기준”을 각각 지정합니다.
여기서 ou=people,dc=ldap,dc=example,dc=com을 지정하면 sub 스코프로 그 아래 전체를 탐색합니다.
사용자가 중첩 OU 안에 있었다면 Synology가 못 찾는 경우가 발생합니다.
flat 구조를 유지한 덕분에 이 문제는 처음부터 없었습니다.
계층 깊이: 얕게 유지하는 것이 원칙
LDAP 트리의 최대 계층 깊이는 연동할 시스템들의 한계에 맞춰야 합니다.
현재 구조에서 가장 깊은 DN은 사용자 엔트리입니다.
uid=user01,ou=people,dc=ldap,dc=example,dc=com
↑ ↑ ↑ ↑
사용자 OU(1단계) DC(2단계) DC(3단계)
Root(dc=…)부터 세면 3단계입니다. 이것이 대부분의 LDAP 클라이언트가 무리 없이 처리하는 깊이입니다.
ou=people 아래에 ou=dev 같은 추가 계층을 넣으면 4단계가 됩니다. 이론적으로는 문제없지만 실제로 Synology DSM 구버전이나 일부 ERP·그룹웨어의 LDAP 클라이언트가 3~4단계 이상을 안정적으로 탐색하지 못하는 경우가 있습니다.
원칙: 필요 없는 계층은 만들지 않는다.
특수 엔트리: cn=readonly와 sambaDomainName
cn=readonly — 최소 권한 서비스 계정
cn=readonly,dc=ldap,dc=example,dc=com는 읽기 전용 바인드 계정입니다.
LDAP에 연동하는 모든 서비스(Synology, 그룹웨어 등)는 이 계정으로 바인드합니다.
관리자 계정(cn=admin,...)을 서비스에 절대 직접 주지 않습니다.
읽기 전용 계정이 탈취되더라도 데이터 조회만 가능하고 수정·삭제는 불가능합니다.
연동 서비스 → cn=readonly (읽기만)
관리 작업 → cn=admin (전체 권한, 운영자만 사용)
sambaDomainName — Samba 연동을 위한 도메인 정보
sambaDomainName=ldap,dc=ldap,dc=example,dc=com는 Samba SMB 파일 서버 연동에 필요한 도메인 SID와 설정을 담는 엔트리입니다.
Samba를 쓰지 않는다면 없어도 됩니다. 이 내용은 05편(Samba 연동)에서 자세히 다룹니다.
설계 체크리스트
실제 구현 전에 다음을 결정해 두세요.
| 항목 | 결정 사항 | 이 시스템의 선택 |
|---|---|---|
| Base DN 형식 | DC 방식 vs O 방식 | DC 방식 (RFC 표준) |
| 서브도메인 사용 | dc=example,dc=com vs dc=ldap,dc=example,dc=com | 서브도메인 분리 |
| 사용자 OU 구조 | flat vs 부서별 중첩 | flat (ou=people 직하) |
| 부서 표현 방식 | 중첩 OU vs 그룹 멤버십 | 그룹 멤버십 |
| 그룹 네이밍 | 자유 vs 접두사 규칙 | 접두사 규칙 (role-/status-/type-/dept-) |
| gidNumber 범위 | 무작위 vs 범위 설계 | 기능별 범위 분리 |
| 서비스 계정 | admin 공유 vs 전용 계정 | 읽기 전용 전용 계정 |
| 최대 계층 깊이 | 기준 없음 vs 상한 설정 | 사용자 기준 3단계 이하 |
다음 편에서는 이 설계를 실제로 구현하는 과정 — Rootless Docker 환경에서 OpenLDAP 컨테이너를 올리고, docker-compose와 systemd로 서비스를 안정적으로 관리하는 방법을 다룹니다.