前言

LDAP(Lightweight Directory Access Protocol),轻型目录访问协议, 具体请阅读官方文档,深入了解还需阅读RFC4510
目前主要使用场景为集中账户管理,对于管理的帐号未达到一定数量级时可能对这个的需求并不是很迫切,但当发展到一定层度的时期,就会很迫切的需要有一种集中管理帐号的方案。
如果资源不是特别紧张,建议初期就使用集中管理帐号方案,这样当发展到一定数量级时,只要维护好这套系统即可,若没有发展起来,也不会带来过多的额外工作,总之好处多多。

约定

系统:centos 6.5
软件版本:openldap-2.4.46
官网地址:http://www.openldap.org
源码目录:/usr/local/src/
安装目录:/data/openldap/
配置文件:/data/conf/
证书路径:/etc/openldap/ssl/
数据库:mdb

准备

安装编译依赖,下载、解压源码

yum install gcc gcc-c++ libtool-ltdl-devel openssl-devel cyrus-sasl-devel libgcrypt-devel libicu-devel openslp-devel
cd /usr/local/src
wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/openldap-2.4.46.tgz
tar zxf openldap-2.4.46.tgz

编译、安装

可通过./configure --help 查看编译选项及参数
默认启动bdb、hdb、mdb库,这里关闭bdb、hdb,使用mdb

cd openldap-2.4.46
./configure --prefix=/data/openldap --sysconfdir=/data/conf/ --enable-bdb=no --enable-hdb=no --enable-accesslog --enable-auditlog --enable-syslog --enable-modules --enable-debug --with-tls --enable-overlays=yes --enable-ppolicy=mod --enable-crypt
make depend
make
make install

--prefix:指定安装目录
--enable-bdb=no:关闭bdb 数据库
--enable-hdb=no:关闭hdb 数据库
--enable-modules=yes:启用动态模块
--enable-overlays:激活所有overlay

修改配置文件

此处仅列出需增加或修改的内容
此配置设定了TLS证书、访问权限、ppolicy默认规则、索引等
slapd.conf

include     /DATA/conf/openldap/schema/collective.schema
include     /DATA/conf/openldap/schema/corba.schema
include     /DATA/conf/openldap/schema/cosine.schema
include     /DATA/conf/openldap/schema/duaconf.schema
include     /DATA/conf/openldap/schema/dyngroup.schema
include     /DATA/conf/openldap/schema/inetorgperson.schema
include     /DATA/conf/openldap/schema/java.schema
include     /DATA/conf/openldap/schema/misc.schema
include     /DATA/conf/openldap/schema/nis.schema
include     /DATA/conf/openldap/schema/openldap.schema
include     /DATA/conf/openldap/schema/pmi.schema
include     /DATA/conf/openldap/schema/ppolicy.schema

loglevel    256
logfile     /DATA/logs/openldap/slapd.log

moduleload  ppolicy.la
moduleload  ppolicy.so

# Certificate/SSL Section
TLSVerifyClient never
TLSCipherSuite DEFAULT
TLSCertificateFile /etc/openldap/ssl/CAcert.pem
TLSCertificateKeyFile /etc/openldap/ssl/CAkey.pem

access to attrs=userPassword,givenName,sn
    by self =xw
    by anonymous auth
    by dn.base="cn=admin,dc=example,dc=com" write
    by dn.base="cn=Manager,dc=example,dc=com" write
    by * none

access to *
    by self read
    by dn.base="cn=admin,dc=example,dc=com" write
    by dn.base="cn=Manager,dc=example,dc=com" write
    by * read

# enable on-the-fly configuration (cn=config)
database config
access to *
    by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
    by * none

# enable server status monitoring (cn=monitor)
database monitor
access to *
    by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
    by dn.exact="cn=Manager,dc=example,dc=com" read
    by * none

#######################################################################
# MDB database definitions
#######################################################################

database    mdb
maxsize     1073741824
suffix      "dc=example,dc=com"
rootdn      "cn=Manager,dc=example,dc=com"
# Cleartext passwords, especially for the rootdn, should
# be avoid.  See slappasswd(8) and slapd.conf(5) for details.
# Use of strong authentication encouraged.
rootpw      {SSHA}zrXUepdOBZmP1c4qHQdbaqFJ8nhB++Fk
# The database directory MUST exist prior to running slapd AND 
# should only be accessible by the slapd and slap tools.
# Mode 700 recommended.
directory   /data/opt/openldap/var/openldap-data
#此段一定要加到database之后,否则会报错
overlay     ppolicy
ppolicy_default "cn=default,ou=pwpolicies,dc=example,dc=com"
ppolicy_hash_cleartext
ppolicy_use_lockout

# Indices to maintain
index   objectClass         eq,pres
index   ou,cn,mail,surname,givenname    eq,pres,sub
index   uidNumber,gidNumber,loginShell  eq,pres
index   uid,memberUid                   eq,pres,sub
index   nisMapName,nisMapEntry          eq,pres,sub

检测配置文件

/data/openldap/sbin/slaptest -f /data/conf/openldap/slapd.conf -u
配置文件无误输出下行,如有错误会有具体信息提示
config file testing succeeded

生成动态配置文件

OpenLDAP 2.3及更高版本,使用运行时动态配置,...简而言之就是修改配置后无需重启...
将slapd.conf 配置文件生成为动态配置文件,目录为slapd.d

/data/openldap/sbin/slaptest -f /data/conf/openldap/slapd.conf -F /data/conf/openldap/slapd.d

生成证书文件

生成顶级CA,支持TLS 协议

cd /etc/openldap/ssl/
openssl req -new -x509 -nodes -out CAcert.pem -keyout CAkey.pem -days 365 

准备基础用户及组文件

此处创建ldap用户及组是通过写ldif文件实现
创建一个DN,一个rootdn用户,两个组People、Groups,一个管理员用户admin,一个密码规则相关帐号

cat ~/base.ldif
# example.com
dn: dc=example,dc=com
dc: example
o: liepass.Inc
objectClass: dcObject
objectClass: organization

# Manager,example.com
dn: cn=Manager,dc=example,dc=com
cn: Manager
description: LDAP administrator
objectClass: organizationalRole
objectClass: top
roleOccupant: dc=example,dc=com

# People,example.com
dn: ou=People,dc=example,dc=com
ou: People
objectClass: top
objectClass: organizationalUnit

# Groups,example.com
dn: ou=Groups,dc=example,dc=com
ou: Groups
objectClass: top
objectClass: organizationalUnit

# administrators,example.com
dn: cn=admin,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: shadowAccount
cn: admin
sn: admin
uid: admin
userPassword: {SSHA}RCBSFS+fgrS/a3VvO3pwEmSj8G5d68nd

dn: ou=pwpolicies,dc=example,dc=com
ou: pwpolicies
objectClass: top
objectClass: organizationalUnit

# add default policy to DIT
# attributes preceded with # indicate the defaults and
# can be omitted
# passwords must be reset every 30 days, 
# have a minimum length of 6 and users will
# get a expiry warning starting 1 hour before
# expiry, when the consecutive fail attempts exceed 5
# the count will be locked and can only be reset by an 
# administrator, users do not need to supply the old 
# password when changing
dn: cn=default,ou=pwpolicies,dc=example,dc=com
objectClass: pwdPolicy
objectClass: person
objectClass: shadowAccount
objectClass: organizationalPerson
uid: default
sn: default
cn: default
pwdAttribute: userPassword
pwdMaxAge: 2592000
pwdExpireWarning: 3600
pwdInHistory: 3
#pwdCheckQuality: 0
pwdMaxFailure: 5
pwdLockout: TRUE
#pwdLockoutDuration: 0
#pwdGraceAuthNLimit: 0
#pwdFailureCountInterval: 0
pwdMustChange: TRUE
pwdMinLength: 8
pwdAllowUserChange: TRUE
pwdSafeModify: FALSE

启动服务,并初始化ldif 文件

前台启动服务

/data/openldap/libexec/slapd -F /DATA/conf/openldap/slapd.d/ -h "ldap:/// ldaps:/// ldapi:///" -d 256

ldap:389
ldaps:636
-d:日志级别
需要注意,TLS 协议使用的是ldap:389,不是ldaps:636

执行base.ldif

/data/openldap/bin/ldapadd -D "cn=Manager,dc=example,dc=com" -W -f ~/base.ldif

创建系统服务

根据实际情况修改如下变量
slapd=/usr/sbin/slapd
slaptest=/usr/sbin/slaptest
lockfile=/var/lock/subsys/slapd
configdir=/etc/openldap/slapd.d/
configfile=/etc/openldap/slapd.conf
pidfile=/var/run/slapd.pid
slapd_pidfile=/var/run/openldap/slapd.pid

cat /etc/init.d/slapd
#!/bin/bash
#
# slapd   This shell script takes care of starting and stopping
#         ldap servers (slapd).
#
# chkconfig: - 27 73
# description: LDAP stands for Lightweight Directory Access Protocol, used \
#              for implementing the industry standard directory services.
# processname: slapd
# config: /etc/openldap/slapd.conf
# pidfile: /var/run/slapd.pid

### BEGIN INIT INFO
# Provides: slapd
# Required-Start: $network $local_fs
# Required-Stop: $network $local_fs 
# Should-Start: 
# Should-Stop: 
# Default-Start: 
# Default-Stop: 
# Short-Description: starts and stopd OpenLDAP server daemon
# Description: LDAP stands for Lightweight Directory Access Protocol, used
#              for implementing the industry standard directory services.
### END INIT INFO

# Source function library.
. /etc/init.d/functions

# Define default values of options allowed in /etc/sysconfig/ldap
SLAPD_LDAP="yes"
SLAPD_LDAPI="no"
SLAPD_LDAPS="no"
SLAPD_URLS=""
SLAPD_SHUTDOWN_TIMEOUT=3
# OPTIONS, SLAPD_OPTIONS and KTB5_KTNAME are not defined

# Source an auxiliary options file if we have one
if [ -r /etc/sysconfig/ldap ] ; then
    . /etc/sysconfig/ldap
fi

slapd=/usr/sbin/slapd
slaptest=/usr/sbin/slaptest
lockfile=/var/lock/subsys/slapd
configdir=/etc/openldap/slapd.d/
configfile=/etc/openldap/slapd.conf
pidfile=/var/run/slapd.pid
slapd_pidfile=/var/run/openldap/slapd.pid

RETVAL=0

#
# Pass commands given in $2 and later to "test" run as user given in $1.
#
function testasuser() {
    local user= cmd=
    user="$1"
    shift
    cmd="$@"
    if test x"$user" != x ; then
        if test x"$cmd" != x ; then
            /sbin/runuser -f -m -s /bin/sh -c "test $cmd" -- "$user"
        else
            false
        fi
    else
        false
    fi
}

#
# Check for read-access errors for the user given in $1 for a service named $2.
# If $3 is specified, the command is run if "klist" can't be found.
#
function checkkeytab() {
    local user= service= klist= default=
    user="$1"
    service="$2"
    default="${3:-false}"
    if test -x /usr/kerberos/bin/klist ; then
        klist=/usr/kerberos/bin/klist
    elif test -x /usr/bin/klist ; then
        klist=/usr/bin/klist
    fi
    KRB5_KTNAME="${KRB5_KTNAME:-/etc/krb5.keytab}"
    export KRB5_KTNAME
    if test -s "$KRB5_KTNAME" ; then
        if test x"$klist" != x ; then
            if LANG=C $klist -k "$KRB5_KTNAME" | tail -n 4 | awk '{print $2}' | grep -q ^"$service"/ ; then
                if ! testasuser "$user" -r ${KRB5_KTNAME:-/etc/krb5.keytab} ; then
                    true
                else
                    false
                fi
            else
                false
            fi
        else
            $default
        fi
    else
        false
    fi
}

function configtest() {
    local user= ldapuid= dbdir= file=
    # Check for simple-but-common errors.
    user=ldap
    prog=`basename ${slapd}`
    ldapuid=`id -u $user`
    # Unaccessible database files.
    dbdirs=""
    if [ -d $configdir ]; then
        for configfile in `ls -1 $configdir/cn\=config/olcDatabase*.ldif`; do
            dbdirs=$dbdirs"
            "`LANG=C egrep '^olcDbDirectory[[:space:]]*:[[:space:]]+[[:print:]]+$' $configfile | sed 's,^olcDbDirectory: ,,'`
        done
    elif [ -f $configfile ]; then
        dbdirs=`LANG=C egrep '^directory[[:space:]]+' $configfile | sed 's,^directory[[:space:]]*,,' | tr -d \"`
    else
        exit 6
    fi
    for dbdir in $dbdirs; do
        if [ ! -d $dbdir ]; then
            exit 6
        fi
        for file in `find ${dbdir}/ -not -uid $ldapuid -and \( -name "*.dbb" -or -name "*.gdbm" -or -name "*.bdb" -or -name "__db.*" -or -name "log.*" -or -name alock \)` ; do
            echo -n $"$file is not owned by \"$user\"" ; warning ; echo
        done
        if test -f "${dbdir}/DB_CONFIG"; then
            if ! testasuser $user -r "${dbdir}/DB_CONFIG"; then
                file=DB_CONFIG
                echo -n $"$file is not readable by \"$user\"" ; warning ; echo
            fi
        fi
    done
    # Unaccessible keytab with an "ldap" key.
    if checkkeytab $user ldap ; then
        file=${KRB5_KTNAME:-/etc/krb5.keytab}
        echo -n $"$file is not readable by \"$user\"" ; warning ; echo
    fi
    # Check the configuration file.
    slaptestout=`/sbin/runuser -m -s "$slaptest" -- "$user" "-u" 2>&1`
    slaptestexit=$?
#   slaptestout=`echo $slaptestout 2>/dev/null | grep -v "config file testing succeeded"`
    # print warning if slaptest passed but reports some problems
    if test $slaptestexit == 0 ; then
        if echo "$slaptestout" | grep -v "config file testing succeeded" >/dev/null ; then
            echo -n $"Checking configuration files for $prog: " ; warning ; echo
            echo "$slaptestout"
        fi
    fi
    # report error if configuration file is wrong
    if test $slaptestexit != 0 ; then
        echo -n $"Checking configuration files for $prog: " ; failure ; echo
        echo "$slaptestout"
        if /sbin/runuser -m -s "$slaptest" -- "$user" "-u" > /dev/null 2> /dev/null ; then
            #dirs=`LANG=C egrep '^directory[[:space:]]+[[:print:]]+$' $configfile | awk '{print $2}'`
            for directory in $dbdirs ; do
                if test -r $directory/__db.001 ; then
                    echo -n $"stale lock files may be present in $directory" ; warning ; echo
                fi
            done
        fi
        exit 6
    fi
}

function start() {
    [ -x $slapd ] || exit 5
    [ `id -u` -eq 0 ] || exit 4
    configtest
    # Define a couple of local variables which we'll need. Maybe.
    user=ldap
    prog=`basename ${slapd}`
    harg="$SLAPD_URLS"
    if test x$SLAPD_LDAP = xyes ; then
        harg="$harg ldap:///"
    fi
    if test x$SLAPD_LDAPS = xyes ; then
        harg="$harg ldaps:///"
    fi
    if test x$SLAPD_LDAPI = xyes ; then
        harg="$harg ldapi:///"
    fi
    # System resources limit.
    if [ -n "$SLAPD_ULIMIT_SETTINGS" ]; then
        ulimit="ulimit $SLAPD_ULIMIT_SETTINGS &>/dev/null;"
    else
        ulimit=""
    fi
    # Release reserverd port
    [ -x /sbin/portrelease ] && /sbin/portrelease slapd &>/dev/null || :
    # Start daemons.
    echo -n $"Starting $prog: "
    daemon --pidfile=$pidfile --check=$prog $ulimit ${slapd} -h "\"$harg\"" -u ${user} $OPTIONS $SLAPD_OPTIONS 
    RETVAL=$?
    if [ $RETVAL -eq 0 ]; then
        touch $lockfile
        ln $slapd_pidfile $pidfile
    fi
    echo
    return $RETVAL
}

function stop() {
    # Stop daemons.
    prog=`basename ${slapd}`
    [ `id -u` -eq 0 ] || exit 4
    echo -n $"Stopping $prog: "

    # This will remove pid and args files from /var/run/openldap
    killproc -p $slapd_pidfile -d $SLAPD_SHUTDOWN_TIMEOUT ${slapd}
    RETVAL=$?

    # Now we want to remove lock file and hardlink of pid file
    [ $RETVAL -eq 0 ] && rm -f $pidfile $lockfile
    echo
    return $RETVAL
}

# See how we were called.
case "$1" in
    configtest)
        configtest
        ;;
    start)
        start
        RETVAL=$?
        ;;
    stop)
        stop
        RETVAL=$?
        ;;
    status)
        status -p $pidfile ${slapd}
        RETVAL=$?
        ;;
    restart|force-reload)
        stop
        start
        RETVAL=$?
        ;;
    condrestart|try-restart)
        status -p $pidfile ${slapd} > /dev/null 2>&1 || exit 0
        stop
        start
        ;;
    usage)
        echo $"Usage: $0 {start|stop|restart|force-reload|status|condrestart|try-restart|configtest|usage}"
        RETVAL=0
        ;;
    *)
        echo $"Usage: $0 {start|stop|restart|force-reload|status|condrestart|try-restart|configtest|usage}"
        RETVAL=2
esac

exit $RETVAL

添加系统服务

chmod +x /etc/init.d/slapd  && chkconfig slapd on

部署web 管理应用

可选web 有phpldapadmin、ldap-account-manager,ldapadmin
本文不对具体安装(可见官方文档)做说明,只对需要注意的点分别做说明

phpldapadmin

请先copy并编辑config.php.example 文件
读取的SSL/TLS配置文件是/etc/openldap/ldap.conf增加如下内容

# 证书路径
TLS_CACERTDIR   /etc/openldap/ssl/
TLS_REQCERT allow

ERROR 1

Fatal error: Cannot redeclare password_hash() in /DATA/html/phpldap/lib/functions.php on line 2236
修改lib/functions.php 文件
将password_hash全部替换为password_hash_custom

sed -i "s/password_hash/password_hash_custom/g" lib/functions.php
sed -i "s/password_hash/password_hash_custom/g" lib/TemplateRender.php
sed -i "s/password_hash/password_hash_custom/g" lib/ds_ldap_pla.php
sed -i "s/password_hash/password_hash_custom/g" lib/PageRender.php
sed -i "s/password_hash/password_hash_custom/g" config/config.php

ERROR 2

Unrecognized error number: 8192: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead
修改lib/functions.php 2568 2573

<           $a[$key] = preg_replace('/\\\([0-9A-Fa-f]{2})/e',"''.chr(hexdec('\\1')).''",$rdn);
---
>           $a[$key] = preg_replace_callback('/\\\([0-9A-Fa-f]{2})/',function(){return "''.chr(hexdec('\\1')).''";},$rdn);

<       return preg_replace('/\\\([0-9A-Fa-f]{2})/e',"''.chr(hexdec('\\1')).''",$dn);
---
>       return preg_replace_callback('/\\\([0-9A-Fa-f]{2})/',function(){return "''.chr(hexdec('\\1')).''";},$dn);

修改lib/ds_ldap.php 1120 1125

<               $a[$key] = preg_replace('/\\\([0-9A-Fa-f]{2})/e',"''.chr(hexdec('\\1')).''",$rdn);
---
>               $a[$key] = preg_replace_callback('/\\\([0-9A-Fa-f]{2})/',function(){return "''.chr(hexdec('\\1')).''";},$rdn);

<           return preg_replace('/\\\([0-9A-Fa-f]{2})/e',"''.chr(hexdec('\\1')).''",$dn);
---
>           return preg_replace_callback('/\\\([0-9A-Fa-f]{2})/',function(){return "''.chr(hexdec('\\1')).''";},$dn);

参考:https://bugs.launchpad.net/ubuntu/+source/phpldapadmin/+bug/1241425/comments/4

ldap-account-manager

ldap-account-manager读取的SSL/TLS配置文件是/etc/ldap.conf增加如下内容

# 证书路径
TLS_CACERTDIR   /etc/openldap/ssl/
TLS_REQCERT allow

LDAP 用户自行修改密码 web工具

https://ltb-project.org/download
想要使用,需要修改配置文件内keyphrase字段对应的默认值
$keyphrase = "secret";
随意修改,只要不是默认的值就行,否则会报错
Token encryption requires a random string in keyphrase setting

参考

openldap 官方文档:http://www.openldap.org/doc/admin24/index.html
TLS 配置说明:http://www.openldap.org/faq/data/cache/185.html
生成CA:https://blog.csdn.net/howeverpf/article/details/21622545?reload
Archlinux:https://wiki.archlinux.org/index.php/OpenLDAP
Archlinux:https://wiki.archlinux.org/index.php/LDAP_authentication
TLS 1.3概述:http://www.inforsec.org/wp/?p=1960
objectClass 介绍:https://blog.csdn.net/qq_27376871/article/details/52037317
ppolicy:http://www.zytrax.com/books/ldap/ch6/ppolicy.html

phpldapadmin相关:
http://permalink.gmane.org/gmane.comp.ldap.davedap/4937
http://forums.debian.net/viewtopic.php?f=5&t=111508