OpenLDAP 구축 가이드입니다. OpenLDAP 2.6의 동적 설정인 cn=config와 MDB backend를 기준으로 TLS, 최소 권한 ACL, 인덱스, 백업·복구와 syncrepl 고가용성을 순서대로 설명합니다. 사용자 인증 기반 서비스이므로 설치 성공보다 암호화, 권한, 정합성과 복구 가능성을 먼저 검증해야 합니다.

Rocky Linux 9에서는 서버 패키지의 제공처와 디렉터리 배치가 선택한 vendor·빌드 방식에 따라 달라질 수 있습니다. 따라서 이 글은 출처가 검증된 OpenLDAP 2.6 패키지 설치를 전제로 하고, 패키지별 경로는 먼저 확인한 뒤 cn=config에 LDAP 연산으로 변경합니다. 호스트 로그인 연동은 nss-pam-ldapd 대신 SSSD의 LDAP provider를 검토하십시오.

OpenLDAP 구축 구조: TLS 클라이언트, 로드밸런서, 2노드 syncrepl과 암호화 백업
TLS 클라이언트, 단일 활성 쓰기 경로, 두 provider의 syncrepl과 클러스터 외부 백업을 분리한 OpenLDAP 운영 구조

OpenLDAP 구축 구성 요소와 설계

구성 요소 역할 운영 기준
slapd LDAP 요청 처리 비특권 서비스 계정, systemd, TLS listener
cn=config 실행 중인 동적 설정 DIT LDAPI + SASL EXTERNAL로 변경
MDB directory 데이터 저장 map size, checkpoint, filesystem 용량
ACL attribute·DN별 권한 구체 규칙 우선, 기본 거부
TLS bind·검색·복제 암호화 CA 신뢰, hostname, 만료 관리
syncrepl provider 간 변경 동기화 고유 serverID·rid, 시간 동기화
slapcat/slapadd 논리 백업·복구 config와 data를 함께 보관
LDAP은 범용 관계형 데이터베이스가 아닙니다. 읽기가 많고 계층·스키마가 안정적인 identity·directory 데이터에 적합합니다. 트랜잭션성 업무 데이터, 자유로운 ad-hoc 관계와 잦은 스키마 변경이 핵심이라면 RDBMS가 더 적합합니다.

OpenLDAP 구축 전 패키지와 경로 확인

패키지 이름을 추측하지 말고 서비스가 사용하는 binary, 설정 DIT, 데이터 디렉터리와 인증서 경로를 기록합니다. OpenLDAP Project의 source build를 선택했다면 configure·test·install 결과와 의존성 버전을 보관하고, vendor 패키지는 GPG 서명과 지원 정책을 확인합니다.

slapd -VVV 2>&1 | head -n 40
ldapsearch -VV 2>&1 | head -n 20

command -v slapd ldapsearch ldapmodify slapcat slapadd slaptest
systemctl cat slapd
systemctl show slapd -p User -p Group -p ExecStart

findmnt -T /var/lib/ldap
df -hT /var/lib/ldap
df -i /var/lib/ldap
인터넷의 임의 저장소를 추가하거나 검증하지 않은 설치 스크립트를 shell에 연결하지 마십시오. production은 OpenLDAP 2.6의 유지보수 버전, TLS library, LMDB, Cyrus SASL과 systemd 구성을 함께 패치하고 staging에서 LDIF 호환성을 확인합니다.

OpenLDAP 구축: cn=config와 MDB 확인

LDAPI 로컬 관리자 연결

sudo ldapwhoami -Q -Y EXTERNAL -H ldapi:///

sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:///   -b cn=config -s base dn cn

sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:///   -b cn=config   '(&(objectClass=olcMdbConfig)(olcSuffix=*))'   dn olcSuffix olcRootDN olcDbDirectory olcDbMaxSize olcDbIndex

-Q -Y EXTERNAL은 Unix socket의 peer credential을 사용하므로 cn=config 관리 비밀번호를 command line에 남기지 않습니다. 원격에서 cn=config를 노출하지 말고, 변경 전 현재 entry를 LDIF로 백업합니다. database DN의 {번호}는 설치마다 다르므로 고정값을 추측하지 않습니다.

현재 cn=config 백업

sudo install -d -m 0700 /var/backups/openldap

sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:///   -b cn=config '(objectClass=*)' '*' '+'   | sudo tee "/var/backups/openldap/cn-config-before-$(date +%F-%H%M%S).ldif"   >/dev/null

sudo chmod 0600 /var/backups/openldap/*.ldif
sudo sha256sum /var/backups/openldap/*.ldif

OpenLDAP 구축: suffix와 관리자 DN

예시는 dc=example,dc=com과 cn=admin을 사용합니다. 실제 조직 이름, DNS 소유권과 naming 정책을 먼저 확정하십시오. rootDN은 애플리케이션 계정이 아니며 모든 ACL을 우회하므로 긴급 복구용으로만 사용하고 일상 변경은 위임된 관리자 group으로 수행합니다.

MDB database DN 확인과 rootPW 해시 생성

MDB_DN=$(sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:///   -b cn=config '(&(objectClass=olcMdbConfig)(olcSuffix=*))' dn   | awk '/^dn: / {print substr($0,5); exit}')

printf 'MDB_DN=%s
' "$MDB_DN"
test -n "$MDB_DN"

# 비밀번호는 대화형으로 입력되고 셸 이력에 남지 않습니다.
ROOT_HASH=$(slappasswd)
test -n "$ROOT_HASH"

suffix·rootDN·rootPW 변경

sudo install -m 0600 /dev/null /root/openldap-database.ldif
sudo tee /root/openldap-database.ldif >/dev/null <<EOF
dn: ${MDB_DN}
changetype: modify
replace: olcSuffix
olcSuffix: dc=example,dc=com
-
replace: olcRootDN
olcRootDN: cn=admin,dc=example,dc=com
-
replace: olcRootPW
olcRootPW: ${ROOT_HASH}
EOF

unset ROOT_HASH
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:///   -f /root/openldap-database.ldif
sudo shred -u /root/openldap-database.ldif

OpenLDAP 구축: 기본 DIT와 사용자

base DN·조직 단위 생성

install -m 0600 /dev/null base.ldif
cat > base.ldif <<'LDIF'
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: Example Directory
dc: example

dn: ou=People,dc=example,dc=com
objectClass: organizationalUnit
ou: People

dn: ou=Groups,dc=example,dc=com
objectClass: organizationalUnit
ou: Groups
LDIF

ldapadd -x -W -D 'cn=admin,dc=example,dc=com'   -H ldapi:/// -f base.ldif
shred -u base.ldif

테스트 사용자와 관리자 group

USER_HASH=$(slappasswd)
install -m 0600 /dev/null people.ldif
cat > people.ldif <<EOF
dn: uid=alice,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
uid: alice
cn: Alice Example
sn: Example
mail: alice@example.com
userPassword: ${USER_HASH}

dn: cn=ldap-admins,ou=Groups,dc=example,dc=com
objectClass: groupOfNames
cn: ldap-admins
member: uid=alice,ou=People,dc=example,dc=com
EOF

unset USER_HASH
ldapadd -x -W -D 'cn=admin,dc=example,dc=com'   -H ldapi:/// -f people.ldif
shred -u people.ldif

사용자 비밀번호는 평문으로 LDIF·Git·티켓에 저장하지 않습니다. 애플리케이션 service account를 개인 계정과 분리하고, groupOfNames의 첫 member를 삭제할 때 schema 제약을 고려합니다. uid와 mail uniqueness는 애플리케이션만 믿지 말고 overlay 또는 provisioning workflow에서 보장합니다.

OpenLDAP 구축: TLS 강제와 검증

simple bind는 TLS 없이 사용하면 비밀번호를 보호하지 못합니다. 서버 인증서의 SAN에는 클라이언트가 실제 접속하는 DNS 이름을 넣고, key는 slapd service 계정만 읽게 합니다. 조직 CA 또는 공인 CA의 chain을 모든 LDAP client와 복제 노드에 배포합니다.

인증서 파일 권한과 유효성

sudo install -d -o root -g ldap -m 0750 /etc/openldap/certs
sudo install -o root -g ldap -m 0640 ldap01.key /etc/openldap/certs/
sudo install -o root -g ldap -m 0644 ldap01.crt /etc/openldap/certs/
sudo install -o root -g ldap -m 0644 org-ca.crt /etc/openldap/certs/

openssl x509 -in /etc/openldap/certs/ldap01.crt   -noout -subject -issuer -dates -ext subjectAltName
openssl verify -CAfile /etc/openldap/certs/org-ca.crt   /etc/openldap/certs/ldap01.crt

cn=config에 TLS 경로 적용

sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// <<'LDIF'
dn: cn=config
changetype: modify
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/openldap/certs/org-ca.crt
-
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/openldap/certs/ldap01.crt
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/openldap/certs/ldap01.key
-
replace: olcTLSProtocolMin
olcTLSProtocolMin: 3.3
LDIF

sudo systemctl restart slapd
sudo systemctl status slapd --no-pager

StartTLS와 LDAPS hostname 검증

openssl s_client -connect ldap01.example.com:636   -servername ldap01.example.com   -CAfile /etc/openldap/certs/org-ca.crt -verify_return_error </dev/null

LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapwhoami -x -ZZ -H ldap://ldap01.example.com   -D 'uid=alice,ou=People,dc=example,dc=com' -W

LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -H ldaps://ldap01.example.com   -b 'dc=example,dc=com' '(uid=alice)' dn cn mail

OpenLDAP 구축: 최소 권한 ACL

OpenLDAP ACL은 위에서 아래로 평가되고 첫 번째로 일치한 to와 by 규칙이 결정합니다. 따라서 userPassword 같은 민감 attribute를 먼저 처리하고 일반 규칙을 마지막에 둡니다. 기본 공개 read에 기대지 말고 anonymous는 bind에 필요한 auth만 허용합니다.

sudo tee /root/openldap-acl.ldif >/dev/null <<EOF
dn: ${MDB_DN}
changetype: modify
replace: olcAccess
olcAccess: {0}to attrs=userPassword by self =xw by anonymous auth by * none
olcAccess: {1}to dn.subtree="ou=People,dc=example,dc=com" attrs=entry,children,cn,sn,uid,mail by self write by group.exact="cn=ldap-admins,ou=Groups,dc=example,dc=com" write by users read by * none
olcAccess: {2}to dn.subtree="ou=Groups,dc=example,dc=com" by group.exact="cn=ldap-admins,ou=Groups,dc=example,dc=com" write by users read by * none
olcAccess: {3}to * by users read by * none
EOF

sudo chmod 0600 /root/openldap-acl.ldif
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:///   -f /root/openldap-acl.ldif
sudo shred -u /root/openldap-acl.ldif

ACL 허용·거부 시험

# anonymous가 userPassword를 읽지 못해야 합니다.
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -ZZ -H ldap://ldap01.example.com   -b 'uid=alice,ou=People,dc=example,dc=com' userPassword

# 본인 bind와 일반 attribute 읽기를 확인합니다.
LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -ZZ -H ldap://ldap01.example.com   -D 'uid=alice,ou=People,dc=example,dc=com' -W   -b 'uid=alice,ou=People,dc=example,dc=com' cn mail

sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:///   -b "$MDB_DN" olcAccess

OpenLDAP 구축: MDB 인덱스와 상태

인덱스는 실제 search filter와 bind attribute에서 시작합니다. 모든 attribute에 eq·sub를 붙이면 쓰기 비용과 DB 크기가 증가합니다. 변경 전에 access log나 애플리케이션 query를 분석하고, 기존 데이터가 있으면 유지보수 창에서 slapindex를 실행합니다.

sudo tee /root/openldap-index.ldif >/dev/null <<EOF
dn: ${MDB_DN}
changetype: modify
replace: olcDbIndex
olcDbIndex: objectClass eq
olcDbIndex: entryUUID,entryCSN eq
olcDbIndex: uid,mail eq
olcDbIndex: cn,sn eq,sub
EOF

sudo ldapmodify -Q -Y EXTERNAL -H ldapi:///   -f /root/openldap-index.ldif
sudo shred -u /root/openldap-index.ldif

sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:///   -b "$MDB_DN" olcDbIndex olcDbMaxSize olcDbCheckpoint
기존 데이터에 새 index를 적용하는 절차와 데이터 디렉터리 소유권은 패키지 경로에 따라 다릅니다. slapd를 서비스에서 제외하고 중지한 뒤 slapindex를 실행하며, 재시작 전에 slaptest와 소유권을 확인합니다. production에서 실행 중인 MDB 파일을 임의로 복사하거나 수정하지 마십시오.

OpenLDAP 구축: slapcat 백업과 복구 훈련

백업에는 cn=config와 application suffix가 모두 필요합니다. 단일 노드라면 유지보수 창에 slapd를 중지하고, HA라면 한 replica를 load balancer에서 제외한 뒤 해당 노드에서 일관된 LDIF를 만듭니다. 파일은 암호화해 별도 시스템으로 복사하고 checksum을 기록합니다.

BACKUP_DIR="/var/backups/openldap/$(date +%F-%H%M%S)"
sudo install -d -m 0700 "$BACKUP_DIR"

sudo systemctl stop slapd
sudo slapcat -F /etc/openldap/slapd.d -n 0   | sudo tee "$BACKUP_DIR/cn-config.ldif" >/dev/null
sudo slapcat -F /etc/openldap/slapd.d -b 'dc=example,dc=com'   | sudo tee "$BACKUP_DIR/example-data.ldif" >/dev/null
sudo systemctl start slapd

sudo chmod 0600 "$BACKUP_DIR"/*.ldif
sudo sha256sum "$BACKUP_DIR"/*.ldif   | sudo tee "$BACKUP_DIR/SHA256SUMS"
sudo journalctl -u slapd --since '-5 min' --no-pager

복구 리허설 검증 항목

sudo slaptest -F /etc/openldap/slapd.d -u
sudo slapcat -F /etc/openldap/slapd.d -n 0 | head
sudo slapcat -F /etc/openldap/slapd.d   -b 'dc=example,dc=com' | head

LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -ZZ -H ldap://ldap01.example.com   -D 'cn=admin,dc=example,dc=com' -W   -b 'dc=example,dc=com' '(objectClass=*)' dn | tail
  • 격리 호스트의 빈 slapd.d와 데이터 디렉터리에 config·data 순서로 slapadd합니다.
  • 패키지 service 계정으로 소유권과 SELinux context를 복원합니다.
  • slaptest 후 LDAPI, StartTLS·LDAPS와 hostname 검증을 수행합니다.
  • base DN, entry 수, 주요 group member와 password bind를 샘플 검증합니다.
  • 애플리케이션 계정의 허용 작업과 anonymous 거부를 다시 시험합니다.
  • 복구 시간과 누락 데이터를 기록해 RPO·RTO를 조정합니다.

OpenLDAP 구축: syncrepl과 HA

두 provider의 multiprovider 구성은 두 곳에 무조건 동시에 쓰라는 뜻이 아닙니다. 충돌과 split brain을 피하려면 load balancer가 한 시점에 하나의 활성 쓰기 경로만 선택하고, 읽기 분산·health check·failback을 별도로 설계합니다. 각 serverID와 syncrepl rid는 고유해야 합니다.

syncprov overlay와 checkpoint

sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// <<EOF
dn: olcOverlay=syncprov,${MDB_DN}
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpCheckpoint: 100 10
olcSpSessionlog: 1000
EOF

provider별 syncrepl 구조 예시

dn: cn=config
changetype: modify
replace: olcServerID
olcServerID: 1 ldaps://ldap01.example.com
olcServerID: 2 ldaps://ldap02.example.com

dn: olcDatabase={MDB_NUMBER}mdb,cn=config
changetype: modify
add: olcSyncrepl
olcSyncrepl: rid=001 provider=ldaps://ldap02.example.com bindmethod=simple binddn="uid=replicator,ou=People,dc=example,dc=com" credentials=<REPLICATION_SECRET> searchbase="dc=example,dc=com" type=refreshAndPersist retry="5 5 300 +" timeout=1 tls_reqcert=demand
-
add: olcMultiProvider
olcMultiProvider: TRUE
이 LDIF는 구조 설명용입니다. {MDB_NUMBER}, serverID, rid, provider와 secret을 각 노드에 맞게 바꾸고 보호된 파일로 적용하십시오. simple bind secret은 cn=config에 저장되므로 디렉터리 권한과 백업 암호화를 강화하거나 SASL EXTERNAL client certificate 방식을 검토합니다.

복제 정합성과 failover 확인

LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -H ldaps://ldap01.example.com   -b 'dc=example,dc=com' contextCSN

LDAPTLS_CACERT=/etc/openldap/certs/org-ca.crt ldapsearch -LLL -x -H ldaps://ldap02.example.com   -b 'dc=example,dc=com' contextCSN

sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:///   -b cn=Monitor '(objectClass=monitorOperation)' cn monitorOpInitiated monitorOpCompleted

timedatectl status
chronyc tracking

같은 테스트 entry의 add·modify·delete가 반대편에 도착하는지 확인하고, 한 provider를 load balancer에서 제외한 뒤 읽기·쓰기·재가입을 시험합니다. contextCSN이 다르다는 사실만으로 즉시 장애로 단정하지 말고 로그, pending operation, 연결 상태와 수렴 시간을 함께 봅니다.

OpenLDAP 구축 운영 점검

systemctl --no-pager --full status slapd
ss -lntp | grep -E ':(389|636)\b'

sudo journalctl -u slapd --since '-30 min' --no-pager
sudo ldapsearch -LLL -Q -Y EXTERNAL -H ldapi:///   -b cn=Monitor -s base '*' '+'

df -hT /var/lib/ldap /var/log
df -i /var/lib/ldap /var/log
findmnt -T /var/lib/ldap
  1. systemd exit code와 최초 오류 시각을 확인합니다.
  2. 389·636 listener가 의도한 주소에만 열렸는지 확인합니다.
  3. CA chain, SAN, 유효 기간과 client의 hostname 검증을 확인합니다.
  4. bind DN·base DN·filter·scope와 ACL 순서를 최소 query로 재현합니다.
  5. MDB map size, filesystem·inode, checkpoint와 장기 query를 확인합니다.
  6. syncrepl 연결, serverID·rid, contextCSN과 시간 동기화를 확인합니다.
  7. 최근 slapcat checksum과 격리 복구 훈련 결과를 확인합니다.

자주 하는 실수와 대안

실수 영향 대안
평문 simple bind 비밀번호 노출 StartTLS critical 또는 LDAPS + CA 검증
rootDN을 애플리케이션에 사용 ACL 전체 우회 service DN과 최소 권한 group
anonymous 전체 read 개인정보·조직도 노출 password auth만 허용 후 기본 거부
database DN 번호 고정 다른 설치의 entry 변경 cn=config 검색으로 실제 DN 확인
실행 중 MDB 파일 복사 일관성 없는 백업 slapcat과 복구 시험
두 provider 동시 무제한 쓰기 충돌·split brain 단일 활성 쓰기 경로와 failover 제어
TLS 검증 생략 위조 서버 연결 CA·SAN·hostname·만료 검증

관련 자료

OpenLDAP 구축 정리

OpenLDAP 구축의 완료 조건은 bind 성공 한 번이 아닙니다. cn=config와 MDB 경로를 확인하고, TLS hostname 검증, attribute 단위 ACL, 실제 query 기반 인덱스, config·data 동시 백업과 격리 복구를 검증해야 합니다. 고가용성에서는 고유 serverID·rid, 시간 동기화와 단일 활성 쓰기 경로를 지켜야 인증 기반 서비스를 예측 가능하게 운영할 수 있습니다.