왜 커스텀 스키마가 필요한가
표준 LDAP 스키마는 일반적인 디렉토리 서비스를 위해 설계되어 있습니다.inetOrgPerson으로 이름·이메일·전화번호를 담을 수 있고, posixAccount로 Linux 계정 정보를 담을 수 있습니다.
하지만 HR 관리 시스템으로 LDAP을 쓰려면 표준 스키마만으로는 부족합니다.
- 부서 코드 (단순 텍스트와 다른 구조화된 코드 체계)
- 재직 상태 (재직 / 휴직 / 퇴직)
- 고용 형태 (정규직 / 계약직 / 프리랜서)
이것들을 기존 속성(description, ou 등)에 우겨넣을 수도 있지만, 그러면 검색 필터 작성이 복잡해지고 의미가 불명확해집니다.
명시적인 속성 이름을 가진 커스텀 스키마를 정의하는 것이 훨씬 낫습니다.
그리고 또 하나의 문제가 있습니다 — 그룹 설계에서 발생하는 STRUCTURAL 클래스 충돌입니다.
STRUCTURAL 충돌: groupOfNames + posixGroup
OpenLDAP에서 그룹을 만들 때 두 가지 요구사항이 충돌합니다.
| 요구사항 | objectClass | 필요한 속성 |
|---|---|---|
| 멤버를 DN으로 관리 (RFC 2256) | groupOfNames | member (DN) |
| Linux/NAS 그룹 ID 관리 (RFC 2307) | posixGroup | gidNumber, memberUid |
Synology NAS는 memberUid(uid 문자열)로 그룹 멤버를 확인합니다.
그룹웨어는 member(full DN)로 멤버를 확인합니다.
둘 다 필요합니다.
그래서 자연스럽게 이렇게 쓰고 싶어집니다.
# 하고 싶었던 구성 (오류 발생)
dn: cn=dept-dev,ou=Groups,dc=ldap,dc=example,dc=com
objectClass: top
objectClass: groupOfNames # STRUCTURAL
objectClass: posixGroup # STRUCTURAL ← 충돌!
cn: dept-dev
gidNumber: 4013
member: uid=john,ou=people,dc=ldap,dc=example,dc=com
memberUid: john
실행하면 오류가 납니다.
ldap_add: Object class violation (65)
additional info: invalid structural object class chain (posixGroup/groupOfNames)
왜 충돌하는가
LDAP 스키마에서 objectClass는 세 종류입니다.
| 타입 | 설명 | 예시 |
|---|---|---|
| STRUCTURAL | 엔트리의 기본 구조를 정의. 하나만 허용 | groupOfNames, posixGroup, person |
| AUXILIARY | 기존 엔트리에 속성을 추가. 여러 개 허용 | posixAccount(일부), 커스텀 클래스 |
| ABSTRACT | 추상 기반 클래스. 직접 사용 불가 | top |
groupOfNames와 posixGroup은 둘 다 STRUCTURAL입니다.
RFC는 하나의 엔트리에 STRUCTURAL 클래스를 하나만 허용합니다(상속 관계가 아니면).
해결책: groupPosixAux AUXILIARY 클래스
해결 방법은 posixGroup의 속성(gidNumber, memberUid)만 AUXILIARY 클래스로 분리하는 것입니다.
groupOfNames (STRUCTURAL) + groupPosixAux (AUXILIARY)
↓ ↓
member (DN) gidNumber, memberUid
AUXILIARY 클래스는 STRUCTURAL 클래스와 충돌 없이 함께 쓸 수 있습니다.
커스텀 스키마 정의
두 클래스를 하나의 스키마 파일에 정의합니다.
# 이 스키마는 cn=config OLC 방식으로 런타임에 로드합니다
dn: cn=custom-hr,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: custom-hr
# ─── 속성 정의 ───────────────────────────────────────────────
# 부서 코드 (예: DEPT001)
olcAttributeTypes: ( 1.3.6.1.4.1.99999.1.1.1
NAME 'hrDepartmentCode'
DESC 'HR department code'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# 재직 상태: active / on-leave / resigned
olcAttributeTypes: ( 1.3.6.1.4.1.99999.1.1.2
NAME 'hrEmploymentStatus'
DESC 'Employment status: active, on-leave, resigned'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# 고용 형태: regular / contract / freelance
olcAttributeTypes: ( 1.3.6.1.4.1.99999.1.1.3
NAME 'hrEmploymentType'
DESC 'Employment type: regular, contract, freelance'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# ─── objectClass 정의 ─────────────────────────────────────────
# HR 사용자 확장 (AUXILIARY — inetOrgPerson에 추가)
olcObjectClasses: ( 1.3.6.1.4.1.99999.2.1.1
NAME 'customHRPerson'
DESC 'Auxiliary class for HR person attributes'
AUXILIARY
MAY ( hrDepartmentCode $ hrEmploymentStatus $ hrEmploymentType ) )
# groupOfNames에 POSIX 그룹 속성 추가 (AUXILIARY)
olcObjectClasses: ( 1.3.6.1.4.1.99999.2.1.2
NAME 'groupPosixAux'
DESC 'Auxiliary class to add POSIX group attributes to groupOfNames'
AUXILIARY
MAY ( gidNumber $ memberUid ) )
OID 선택에 대해
OID(1.3.6.1.4.1.99999.x.x.x)에서 99999는 예시용 Private Enterprise Number입니다.
실제 운영 환경에서는 IANA에 자신의 번호를 등록하거나, 내부 서비스 전용이라면 99999처럼 충돌 없는 번호를 정해서 사용합니다.
중요한 것은 다른 스키마의 OID와 겹치지 않아야 한다는 점입니다.
스키마 로드 방법
osixia/openldap 컨테이너는 cn=config(OLC) 방식으로 런타임에 스키마를 추가할 수 있습니다.
컨테이너를 재시작할 필요 없습니다.
처음 스키마 추가
# 스키마 LDIF를 컨테이너로 복사
docker cp custom-hr-schema.ldif openldap:/tmp/
# 컨테이너 내부에서 cn=config에 로드
docker exec openldap ldapadd -Y EXTERNAL -H ldapi:/// -f /tmp/custom-hr-schema.ldif
로드 확인
docker exec openldap ldapsearch -Y EXTERNAL -H ldapi:/// \
-b "cn=schema,cn=config" \
"(cn=*custom-hr*)" cn
결과에 cn: {9}custom-hr 같은 항목이 나오면 성공입니다.
groupPosixAux만 추가 (기존 스키마 수정)
이미 스키마가 로드된 상태에서 groupPosixAux만 추가하려면 modify 방식을 씁니다.
# add-posixaux.ldif
dn: cn={9}custom-hr,cn=schema,cn=config
changetype: modify
add: olcObjectClasses
olcObjectClasses: ( 1.3.6.1.4.1.99999.2.1.2
NAME 'groupPosixAux'
DESC 'Auxiliary class to add POSIX group attributes to groupOfNames'
AUXILIARY
MAY ( gidNumber $ memberUid ) )
docker exec -i openldap ldapmodify -Y EXTERNAL -H ldapi:/// < add-posixaux.ldif
실제 사용 예시
사용자 엔트리
customHRPerson을 추가하면 HR 속성을 쓸 수 있습니다.
dn: uid=john,ou=people,dc=ldap,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: customHRPerson # ← HR 속성 추가
objectClass: sambaSamAccount
uid: john
cn: John Doe
sn: Doe
mail: john@example.com
uidNumber: 10001
gidNumber: 4013
homeDirectory: /home/john
loginShell: /bin/bash
hrDepartmentCode: DEPT013 # ← 커스텀 속성
hrEmploymentStatus: active # ← 커스텀 속성
hrEmploymentType: regular # ← 커스텀 속성
그룹 엔트리
groupPosixAux를 추가하면 groupOfNames에 gidNumber와 memberUid를 같이 쓸 수 있습니다.
dn: cn=dept-dev,ou=Groups,dc=ldap,dc=example,dc=com
objectClass: top
objectClass: groupOfNames # STRUCTURAL
objectClass: groupPosixAux # AUXILIARY ← 충돌 없음
cn: dept-dev
gidNumber: 4013
member: uid=john,ou=people,dc=ldap,dc=example,dc=com
memberUid: john
검색 예시
커스텀 속성으로 필터링할 수 있습니다.
ADM="cn=admin,dc=ldap,dc=example,dc=com"
PW="your_admin_password"
BASE="dc=ldap,dc=example,dc=com"
# 재직 중인 사용자만 조회
docker exec openldap ldapsearch -x -H ldap://localhost:389 \
-D "$ADM" -w "$PW" \
-b "ou=people,$BASE" \
"(hrEmploymentStatus=active)" uid cn hrEmploymentStatus
# 특정 부서 코드 사용자 조회
docker exec openldap ldapsearch -x -H ldap://localhost:389 \
-D "$ADM" -w "$PW" \
-b "ou=people,$BASE" \
"(hrDepartmentCode=DEPT013)" uid cn
# groupPosixAux가 있는 그룹 (Synology용 필터)
docker exec openldap ldapsearch -x -H ldap://localhost:389 \
-D "$ADM" -w "$PW" \
-b "ou=Groups,$BASE" \
"(objectClass=groupPosixAux)" cn gidNumber memberUid
스키마 롤백
스키마를 잘못 추가했을 때 제거하는 방법입니다.
# rollback-schema.ldif
dn: cn={9}custom-hr,cn=schema,cn=config
changetype: delete
docker exec -i openldap ldapmodify -Y EXTERNAL -H ldapi:/// < rollback-schema.ldif
단, 해당 스키마를 사용하는 엔트리가 있으면 삭제되지 않습니다.
먼저 해당 objectClass를 쓰는 모든 엔트리에서 속성을 제거한 뒤 스키마를 삭제해야 합니다.
여기까지의 상태
customHRPerson: HR 관리용 사용자 속성 (부서코드, 재직상태, 고용형태)groupPosixAux:groupOfNames에 POSIX 그룹 속성 추가, STRUCTURAL 충돌 해결- 사용자와 그룹 엔트리 모두 커스텀 스키마 적용 완료
다음 편에서는 실제 사용자와 그룹을 생성하고, Samba SMB 파일 서버 연동에 필요한 sambaSamAccount 설정과 NT 해시 생성 문제(Node.js 20의 MD4 비활성화)를 다룹니다.