패키지 설치

root:# apt install slapd ldap-utils
root:# vi /etc/hosts 

/etc/hosts 파일을 열어서 OpenLDAP이 resolving할 호스트명을 적어준다

 127.0.0.1 localhost ldap ldap.abc.com

/etc/ldap/ldap.conf 를 열고 BASE DN과 URI를 적어준다.

BASE    dc=abc,dc=com
URI     ldap://ldap.abc.com:389/

패키지를 재설정

root:# dpkg-reconfigure slapd

선택사항

omit OpenLDAP server configuration: No
DNS domain name: abc.com
Organization name: dc=abc,dc=com
Database backend to use: MDB
Do you want the database to be removed when slapd is purged? No
Move old database? Yes
Allow LDAPv2 protocol? No

제대로 됐는지 확인

root# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config dn

출력

dn: cn=config
dn: cn=module{0},cn=config
dn: cn=schema,cn=config
dn: cn={0}core,cn=schema,cn=config
dn: cn={1}cosine,cn=schema,cn=config
dn: cn={2}nis,cn=schema,cn=config
dn: cn={3}inetorgperson,cn=schema,cn=config
dn: olcBackend={0}mdb,cn=config
dn: olcDatabase={-1}frontend,cn=config
dn: olcDatabase={0}config,cn=config
dn: olcDatabase={1}mdb,cn=config
root# ldapsearch -x -LLL -H ldap:/// -b dc=abc,dc=com dn

출력

dn: dc=abc,dc=com
dn: cn=admin,dc=abc,dc=com

TLS / LDAPS 설정

root:# apt install apparmor-utils sasl2-bin gnutls-bin ssl-cert
root:# cd certs
root:# chown root:ssl-cert *
root:# cp -a abc.com_ca.pem abc.com.crt /etc/ssl/certs/
root:# cp -a abc.com.key /etc/ssl/private/
root:# adduser openldap ssl-cert
root:# adduser openldap sasl
root:# usermod -a -G ssl-cert openldap
root:# aa-complain /usr/sbin/slapd

/etc/ldap/ldap.conf 파일을 열고 TLS 항목을 추가한다.

TLS_REQCERT          demand

만약 사용자의 패스워드 인증을 상위 기관 등에서 할 경우 SASL을 설정해준다.
/etc/saslauthd.conf 파일을 생성하고 필요한 항목을 넣는다.

ldap_servers: ldaps://ldap.abc.com
ldap_version: 3
ldap_start_tls: no
ldap_search_base: ou=people,dc=abc,dc=com
ldap_filter: (uid=%u)
ldap_bind_dn: cn=cn_name,ou=your_department,dc=abc,dc=com
ldap_bind_pw: PASSWORD
ldap_verbose: on

/etc/ldap/sasl2/slapd.conf 파일을 열고 패스워드 인증 매커니즘을 LDAP으로 바꿔준다.

START=yes
MECHANISMS="ldap"

/etc/default/slapd 파일을 열고 slapd.conf 파일 정의 및 서비스 프로토콜을 지정한다.

SLAPD_CONF=/etc/ldap/slapd.conf
SLAPD_SERVICES="ldap:/// ldaps:/// ldapi:///"

재구축인 경우 기존의 mdb 파일과 스키마를 가져온다.

root:# mkdir -p /var/lib/ldap/abc
root:# cp *.mdb /var/lib/ldap/abc/
root:# chown openldap: -R /var/lib/ldap/abc
root:# cp -i *.schema /etc/ldap/schema/

/etc/ldap/slapd.conf 파일을 아래와 같이 생성한다.

include         "/etc/ldap/schema/core.schema"
include         "/etc/ldap/schema/cosine.schema"
include         "/etc/ldap/schema/inetorgperson.schema"
include         "/etc/ldap/schema/nis.schema"
include         "/etc/ldap/schema/pykota.schema"
include         "/etc/ldap/schema/autofs.schema"
include         "/etc/ldap/schema/ppolicy.schema"
include         "/etc/ldap/schema/ldapns.schema"
include         "/etc/ldap/schema/dyngroup.schema"
include         "/etc/ldap/schema/eduperson2.schema"
include         "/etc/ldap/schema/dhcp2.schema"
include         "/etc/ldap/schema/abc.schema"
include         "/etc/ldap/schema/publications.schema"
include         "/etc/ldap/schema/sudo.schema"
include         "/etc/ldap/schema/quota.schema"
include         "/etc/ldap/schema/puppet.schema"
include         "/etc/ldap/schema/encryptionObject.schema"
include         "/etc/ldap/schema/dlz.schema"

TLSCACertificateFile    "/etc/ssl/certs/abc.com_ca.pem"
TLSCertificateFile      "/etc/ssl/certs/abc.com.crt"
TLSCertificateKeyFile   "/etc/ssl/private/abc.com.key"

pidfile                 "/var/run/slapd/slapd.pid"
argsfile                "/var/run/slapd/slapd.args"

modulepath      "/usr/lib/ldap"
moduleload      back_mdb.la
moduleload      memberof.la
moduleload      syncprov.la
moduleload      accesslog.la
moduleload      ppolicy.la
moduleload      refint.la
moduleload      dynlist.la
moduleload      back_monitor.la

loglevel stats sync
sizelimit -1

serverID 2
sortvals member pubContributor documentAuthor

access to *
         by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
         by * break
access to dn.base=""
         by * read
access to dn.base="cn=subschema"
         by * read

database  config
rootdn    "cn=admin,dc=abc,dc=com"

database  mdb
suffix    "dc=abc,dc=com"
rootdn    "cn=admin,dc=abc,dc=com"

limits dn.exact=cn=syncuser,dc=abc,dc=com size=unlimited

index   default         eq
index   objectClass
index   cn eq,pres,sub
index   uidNumber
index   gidNumber
index   loginShell
index   uid eq,pres,sub
index   memberUid eq,pres,sub
index   uniqueMember eq,pres
index   member eq,pres
index   entryCSN
index   entryUUID
index   sudoUser eq,sub
index   departmentNumber
index   abcChairDept
index   documentAuthor
index   pubOwner
index   memberOf
index   dhcpHWAddress
index   givenName
index   sn

directory  "/var/lib/ldap/abc"
maxsize    34359738368

access to *
         by dn="cn=syncuser,dc=abc,dc=com" read
         by * break
access to attrs=userPassword
         by anonymous auth
         by self none
         by dn="cn=manager,dc=abc,dc=com" write stop
         by * none
access to attrs=shadowLastChange
         by self write
         by * none
access to dn.one="ou=people,dc=abc,dc=com"
           filter=(objectClass=abcPerson)
           attrs=jpegPhoto,loginShell
         by dn="cn=web,dc=abc,dc=com" write stop
         by * break
access to dn.one="ou=people,dc=abc,dc=com"
           filter=(objectClass=abcPerson)
             attrs=@abcPerson,roomNumber,telephoneNumber,physicalDeliveryOfficeName,facsimileTelephoneNumber,displayName
         by self write stop
         by dnattr=owner write
         by set="this/memberOf/owner & user" write
         by dn="cn=manager,dc=abc,dc=com" write
         by dn="cn=cloud,dc=abc,dc=com" read stop
         by users read
access to dn.sub="ou=people,dc=abc,dc=com"
         by self write stop
         by dn="cn=manager,dc=abc,dc=com" write stop
         by dn="cn=cloud,dc=abc,dc=com" read stop
         by dn="cn=stack,dc=abc,dc=com" read stop
         by * read
access to dn.sub="ou=named,dc=abc,dc=com"
         by dn="cn=manager,dc=abc,dc=com" write stop
         by dn="cn=bind,dc=abc,dc=com" read stop
         by * none
access to dn.sub="ou=roles,dc=abc,dc=com"
         by dn="cn=manager,dc=abc,dc=com" write stop
         by dn="cn=cloud,dc=abc,dc=com" manage stop
         by dn="cn=stack,dc=abc,dc=com" break
         by * read
access to dn.sub="ou=openstack,ou=roles,dc=abc,dc=com"
         by dn="cn=stack,dc=abc,dc=com" write stop
         by * none
access to dn.sub="ou=openstack-groups,ou=accessGroups,dc=abc,dc=com"
         by dn="cn=stack,dc=abc,dc=com" manage stop
         by * none
access to dn="cn=openstack,ou=accessGroups,dc=abc,dc=com"
         by dn="cn=stack,dc=abc,dc=com" write stop
         by * none
access to dn="cn=enabled-projects,ou=tenants,dc=abc,dc=com"
         by dn="cn=stack,dc=abc,dc=com" write stop
         by * none
access to dn.sub="ou=tenants,dc=abc,dc=com"
         by dn="cn=stack,dc=abc,dc=com" manage stop
         by * none
access to dn.sub="cn=heimdall.abc.com,dc=abc,dc=com"
         by dn="cn=dhcp-user,dc=abc,dc=com" read
         by * none
access to dn.sub="ou=pending,dc=abc,dc=com"
         by dn="cn=register,dc=abc,dc=com" write stop
         by dn="cn=manager,dc=abc,dc=com" write stop
         by * break
access to dn.sub="ou=groups,dc=abc,dc=com" attrs=memberUid
         by dn="cn=register,dc=abc,dc=com" write stop
         by dn="cn=manager,dc=abc,dc=com" write stop
         by dn="cn=web,dc=abc,dc=com" read break
         by * break
access to dn.regex="cn=.+-editor,ou=groups,dc=abc,dc=com" attrs=memberUid
         by dn="cn=web,dc=abc,dc=com" write stop
         by * break
access to dn.sub="ou=departments,dc=abc,dc=com"
         by dn="cn=register,dc=abc,dc=com" read stop
         by dn="cn=manager,dc=abc,dc=com" write stop
         by * break
access to dn.sub="ou=inactive,dc=abc,dc=com"
         by dn="cn=directory,dc=abc,dc=com" none
         by dn="cn=register,dc=abc,dc=com" read stop
         by dn="cn=manager,dc=abc,dc=com" write stop
         by * break
access to dn.sub="ou=people,dc=abc,dc=com"
           attrs=userPassword,gidNumber,uidNumber,homeDirectory,employeeNumber,loginShell,objectClass
         by dn="cn=directory,dc=abc,dc=com" none stop
         by * break
access to dn.sub="ou=people,dc=abc,dc=com"
         by dn="cn=manager,dc=abc,dc=com" write stop
         by dn="cn=cloud,dc=abc,dc=com" read stop
         by * break
access to dn.sub="ou=accessGroups,dc=abc,dc=com"
         by dn="cn=manager,dc=abc,dc=com" write stop
         by * read
access to dn.sub="ou=groups,dc=abc,dc=com"
         by dn="cn=cloud,dc=abc,dc=com" read stop
         by * break
access to dn.sub="ou=publications,dc=abc,dc=com"
         by dn.sub="ou=people,dc=abc,dc=com" write stop
         by dn="cn=abcPubEditor,ou=roles,dc=abc,dc=com" write stop
         by dn="cn=manager,dc=abc,dc=com" write stop
         by * read break
access to dn.sub="ou=groups,dc=abc,dc=com" attrs=gidNumber
         by dn="cn=directory,dc=abc,dc=com" none break
         by dn="cn=register,dc=abc,dc=com" read stop
         by * break
access to dn.one="" filter=(objectClass=dcObject)
         by * read
access to *
         by users read
         by * none

# This server will retrieve data from these servers below.
syncrepl        rid=0
                provider=ldap://ldap2.abc.com
                searchbase=dc=abc,dc=com
                type=refreshAndPersist
                retry="30 +"
                bindmethod=simple
                binddn=cn=syncuser,dc=abc,dc=com
                credentials=PASSWORD
                tls_cacert=/etc/ssl/certs/abc.com_ca.pem
                tls_reqcert=demand
                starttls=critical
                syncdata=default

overlay ppolicy
ppolicy_default "cn=default,ou=ppolicy,dc=abc,dc=com"

overlay refint
refint_attributes memberOf member manager owner seeAlso roleOccupant pubOwner documentAuthor

overlay memberof

overlay dynlist
dynlist-attrset groupOfURLs memberURL

overlay syncprov
syncprov-checkpoint 1000 60
mirrormode     on

database        monitor
access to *
         by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
         by dn.exact="cn=admin,dc=abc,dc=com" read
         by dn.exact="cn=monitoring,dc=abc,dc=com" read
         by * none

saslauthd와 slapd를 재시작해준다. 보통 데비안/우분투 계열에서는 systemctl restart/stop 혹은 service restart/stop 명령어로는 프로세스가 죽지않는 증상이 있으므로, 이 경우 직접 PID를 찾아서 kill을 해줘야한다.

root:# systemctl restart saslauthd
root:# systemctl restart slapd

트러블 슈팅

ldapwhoami 명령어가 Can’t contact LDAP server (-1)라는 에러를 낼 경우

conn=1000 op=0 BIND dn="uid=seowon,ou=people,dc=abc,dc=com" method=128
audit: type=1400 audit(1507160608.094:34): apparmor="ALLOWED" operation="connect" profile="/usr/sbin/slapd" name="/run/saslauthd/mux" pid=10291 comm="slapd" requested_mask="wr" denied_mask="wr" fsuid=111 ouid=0

audit: type=1400 audit(1507160608.094:35): apparmor="ALLOWED" operation="file_perm" profile="/usr/sbin/slapd" name="/run/saslauthd/mux" pid=10291 comm="slapd" requested_mask="w" denied_mask="w" fsuid=111 ouid=0

aa-complain으로 slapd를 apparmor의 예외항목에 넣어서 재시작해보고, 그래도 안될 경우 MDB 파일을 삭제하고 기존의 LDIF 파일을 통째로 넣는다.

root:# systemctl stop slapd
root:# aa-complain /usr/sbin/slapd
root:# systemctl restart apparmor
root:# systemctl start slapd
root:# systemctl stop slapd
root:# slapadd -v -c -l ~/abc.com.ldif -f /etc/ldap/slapd.conf
root:# systemctl start slapd

그래도 해결되지 않으면 증상을 확인하기 위해 /etc/ldap/ldap.conf 파일을 열고 TLS를 꺼본다.

TLS_REQCERT          never

daemon: bind(13) failed errno=2 (No such file or directory) 라는 에러가 나올시

root:# mkdir /var/run/slapd
root:# chown openldap: -R /var/run/slapd

saslauthd가 상위 LDAP 서버로 접근을 못할 경우

root:# systemctl stop saslauthd
root:# systemctl stop slapd
root:# systemctl stop apparmor
root:# ps ax |grep slapd
root:# kill -9 PID
root:# /etc/apparmor.d/usr.sbin.slapd
root:# Add: /{,var/}run/saslauthd/mux rw,
root:# aa-complain /usr/sbin/slapd
root:# systemctl start apparmor
root:# systemctl start slapd
root:# systemctl start saslauthd

LDAP 백업용 Perl 스크립트 패키지 설치

root:# apt install build-essential libssl-dev libcrypto++-dev zlib1g-dev
root:# cpan
install Net::SSLeay
install IO:Socket::SSL
install Net::LDAP
install Net::LDAP::Entry
install Net::LDAP::LDIF

LDAP 사용자 정보 수정하기

root:# cp /var/tmp/backup/abc.com.ldif.gpg /tmp/
root:# gpg --output abc.com.ldif --decrypt abc.com.ldif.gpg
Password: 

수정을 위한 LDIF 파일 생성

dn: cn=staff-tech,ou=groups,dc=abc,dc=com
changetype: modify
delete: memberUid
add: memberUid
memberUid: ziliox
memberUid: kori
memberUid: giscom
memberUid: myab
memberUid: alevine
memberUid: tanare
memberUid: anguyen
memberUid: sambo

실행

root:# ldapmodify -Y EXTERNAL -H 'ldapi:///' -f mod-group.ldif -vn
root:# ldapsearch -Y EXTERNAL -H ldapi:/// -b 'cn=staff-tech,ou=groups,dc=abc,dc=com' -s 'base' memberUid
root:# ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b 'cn=staff-tech,ou=groups,dc=abc,dc=com' -s 'base' memberUid
root:# ldapmodify -x -D 'cn=admin,dc=abc,dc=com' -W -H 'ldap://ldap.abc.com:389' -ZZ -f mod-group.ldif -v

memberOf 생성하기

기존 서버 쪽에서 memberOf 데이터를 뽑아온다.

$ ldapsearch -x -D 'cn=register,dc=abc,dc=com' -H 'ldap://ldap.abc.com:389' -ZZ -W -b 'ou=people,dc=abc,dc=com' '(uid=*)' memberOf > memberOf.ldif

신규 서버 쪽
기존 서버에서 뽑아낸 memberOf.ldif 파일을 열고 각각의 dn마다 아래의 라인을 삽입한다.

changetype: modify
add: memberOf

그리고나서 ldapmodify로 적용해준다.

$ ldapmodify -x -D "cn=admin,dc=abc,dc=com" -W -H ldap://ldap.abc.com -f memberOf.ldif

유용한 명령어

eduPerson이라는 objectClass가 없는 사용자를 검색
ldapsearch -x -D 'cn=search,dc=abc,dc=com' -H 'ldaps://ldap.abc.com:636' -W -b 'ou=people,dc=abc,dc=com' '(!(objectClass=eduPerson))' cn

사용자 확인
$ ldapwhoami -xWZ -D 'uid=seowon,ou=people,dc=abc,dc=com' -H 'ldap://ldap.abc.com:389/'

그룹멤버 목록 나열
$ ldapsearch -x -D 'cn=register,dc=abc,dc=com' -H 'ldap://ldap.abc.com:389' -ZZ -W -b 'ou=people,dc=abc,dc=com' '(uid=seowon)' | less

$ ldapsearch -xZZ -D 'cn=register,dc=abc,dc=com' -W -H 'ldap://ldap.abc.com:389' -b 'ou=departments,dc=abc,dc=com' -s 'one' '(cn=facstaff-tdp)' member

기존 LDAP 서버 덤프 뜨기
$ ldapsearch -xW -D "cn=admin,dc=abc,dc=com" -b "dc=abc,dc=com" -H ldap://ldap.abc.com -LLL > ~/ldap_dump-09072020-1035pm.ldif