OpenLDAP 2.4 Server Replication

Build and Install from Source

Compile with support of the new MDB database backend that will replace Berkeley DB. The new MDB database is much faster and doesn't need tuning.

tar -xvzf openldap-2.4.38.tgz
cd openldap-2.4.38
./configure --prefix=/opt/openldap \
  --sysconfdir=/etc/opt/openldap \
  --localstatedir=/var/opt/openldap \
  --with-subdir= \
  --enable-slapd --enable-mdb --enable-monitor --enable-overlays --enable-crypt \
  --with-tls --with-cyrus-sasl
make depend
make
make install

Configure LDAP Server

Common part for both provider and consumer replica. The official OpenLDAP 2.4 admin guide still shows all examples with the old config file format. It took a while and a lot of research to find out how to do it with the new cn=config format.

slapadd -v -F /etc/opt/openldap/slapd.d -n 0 <<
dn: cn=config
objectClass: olcGlobal
cn: config
olcAttributeOptions: lang-
olcConfigFile: /etc/opt/openldap/slapd.conf.bak
olcConfigDir: /etc/opt/openldap/slapd.d
olcPidFile: /var/opt/openldap/run/slapd.pid
olcTLSCACertificateFile: /etc/pki/tls/certs/example_ca.pem
olcTLSCertificateFile: /etc/pki/tls/certs/node-cert.pem
olcTLSCertificateKeyFile: /etc/pki/tls/private/node-key.pem
olcTLSVerifyClient: never
olcLogLevel: none
olcSecurity: simple_bind=128
olcLocalSSF: 128

dn: cn=schema,cn=config
objectClass: olcSchemaConfig
cn: schema
include: file:///etc/opt/openldap/schema/core.ldif
include: file:///etc/opt/openldap/schema/cosine.ldif
include: file:///etc/opt/openldap/schema/duaconf.ldif
include: file:///etc/opt/openldap/schema/inetorgperson.ldif
include: file:///etc/opt/openldap/schema/ppolicy.ldif
include: file:///etc/opt/openldap/schema/openldap.ldif
include: file:///etc/opt/openldap/schema/java.ldif
include: file:///root/schema/rfc2307bis.ldif
include: file:///root/schema/printing.ldif
include: file:///root/schema/solaris.ldif
include: file:///root/schema/cupsprinter.ldif
include: file:///root/schema/mozilla.ldif

dn: olcDatabase={-1}frontend,cn=config
objectClass: olcDatabaseConfig
objectClass: olcFrontendConfig
olcDatabase: {-1}frontend
olcAccess: {0}to attrs=userPassword
  by self =xw
  by anonymous auth
  by * none
olcAccess: {1}to * by * read
olcSizelimit: size.soft=10000 size.hard=1000000
olcTimelimit: time.soft=300 time.hard=3600

dn: olcDatabase={0}config,cn=config
objectClass: olcDatabaseConfig
olcDatabase: {0}config
olcRootDN: cn=Manager,cn=config
olcRootPW: {SSHA}ENCRYPTEDPASSWORD
olcMonitoring: FALSE
olcAccess: {0}to dn.subtree="cn=schema,cn=config"
  by users read
olcAccess: {1}to * by * none

dn: olcDatabase={1}monitor,cn=config
objectClass: olcDatabaseConfig
olcDatabase: {1}monitor
olcAddContentAcl: FALSE
olcLastMod: TRUE
olcMaxDerefDepth: 15
olcReadOnly: FALSE
olcSyncUseSubentry: FALSE
olcMonitoring: FALSE
olcAccess: {3}to dn.subtree="cn=monitor"
  by dn.exact="cn=Manager,cn=config" read
  by dn.exact="cn=Manager,dc=example,dc=com" read
  by * none

dn: olcDatabase={2}mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {2}mdb
olcMonitoring: TRUE
olcSuffix: dc=example,dc=com
olcRootDN: cn=Manager,dc=example,dc=com
olcRootPW: {SSHA}ENCRYPTEDPASSWORD
olcDbDirectory: /var/opt/openldap/example_com
olcDbMaxSize: 42949672960
olcDbIndex: objectClass pres,eq
olcDbIndex: cn pres,eq,sub
olcDbIndex: uid pres,eq
olcDbIndex: uidNumber pres,eq
olcDbIndex: gidNumber pres,eq
olcDbIndex: mail pres,eq,sub
olcDbIndex: ou pres,eq
olcDbIndex: loginShell pres,eq
olcDbIndex: sn pres,eq,sub
olcDbIndex: givenName pres,eq,sub
olcDbIndex: memberUid pres,eq
olcDbIndex: nisMapName pres,eq
olcDbIndex: nisMapEntry pres,eq
olcDbIndex: entryCSN eq
olcDbIndex: entryUUID eq
olcAccess: {0}to attrs=userPassword
  by dn.exact="cn=Manager,dc=example,dc=com" write
  by dn.exact="cn=Replicator,dc=example,dc=com" read
  by self =xw
  by anonymous auth
  by * none
olcAccess: {2}to * by * read
olcLimits: {0}dn.exact="cn=Manager,dc=example,dc=com"
  size.soft=unlimited size.hard=unlimited
  time.soft=unlimited time.hard=unlimited
olcLimits: {1}dn.exact="cn=Replicator,dc=example,dc=com"
  size.soft=unlimited size.hard=unlimited
  time.soft=unlimited time.hard=unlimited

dn: olcOverlay=ppolicy,olcDatabase={2}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcPPolicyConfig
olcOverlay: ppolicy
olcPPolicyDefault: cn=default,ou=pwpolicies,dc=example,dc=com
olcPPolicyHashCleartext: TRUE
olcPPolicyUseLockout: FALSE
EOF

Start slapd in the foreground with replication debugging.

/opt/openldap/libexec/slapd -d 0x4100 -F /etc/opt/openldap/slapd.d -h "ldapi:/// ldap:/// ldaps:///"

Apply Role as Provider Replica

Perform this step only once on the provider. Skip this step if you are setting up a consumer. Create the accesslog database and enable the accesslog overlay for dc=example,dc=com. The accesslog database is used for delta-syncrepl replication. Enable the syncprov overlays for both cn=accesslog and dc=example,dc=com databases.

ldapadd -H ldaps://ldapmaster.example.com -D cn=Manager,cn=config -W <<EOF
dn: olcDatabase={3}mdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMdbConfig
olcDatabase: {3}mdb
olcMonitoring: TRUE
olcSuffix: cn=accesslog
olcRootDN: cn=accesslog
olcDbDirectory: /var/opt/openldap/accesslog
olcDbMaxSize: 42949672960
olcDbIndex: default eq
olcDbIndex: entryCSN,objectClass,reqEnd,reqResult,reqStart
olcAddContentAcl: FALSE
olcLastMod: TRUE
olcMaxDerefDepth: 15
olcReadOnly: FALSE
olcSyncUseSubentry: FALSE
olcDbNoSync: TRUE
olcAccess: {0}to *
  by dn.exact="cn=Manager,cn=config" read
  by dn.exact="cn=Manager,dc=example,dc=com" read
  by dn.exact="cn=Replicator,dc=example,dc=com" read
  by * none
olcLimits: {0}dn.exact="cn=Manager,dc=example,dc=com"
  size.soft=unlimited size.hard=unlimited
  time.soft=unlimited time.hard=unlimited
olcLimits: {1}dn.exact="cn=Replicator,dc=example,dc=com"
  size.soft=unlimited size.hard=unlimited
  time.soft=unlimited time.hard=unlimited

dn: olcOverlay=accesslog,olcDatabase={2}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcAccessLogConfig
olcOverlay: accesslog
olcAccessLogDB: cn=accesslog
olcAccessLogOps: writes
olcAccessLogPurge: 7+00:00 1+00:00
olcAccessLogSuccess: TRUE

dn: olcOverlay=syncprov,olcDatabase={2}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpNoPresent: FALSE
olcSpReloadHint: TRUE

dn: olcOverlay=syncprov,olcDatabase={3}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpNoPresent: TRUE
olcSpReloadHint: TRUE
EOF

Feed the LDAP database with the base tree.

ldapadd -H ldaps://ldapmaster.example.com -D cn=Manager,dc=example,dc=com -W <<EOF
dn: dc=example,dc=com
objectClass: top
objectClass: dcobject

dn: cn=Manager,dc=example,dc=com
cn: Manager
objectClass: top
objectClass: organizationalrole

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

dn: cn=default,ou=pwpolicies,dc=example,dc=com
cn: default
sn: Default Password Policy
objectClass: pwdPolicyChecker
objectClass: pwdPolicy
objectClass: person
objectClass: top
pwdAttribute: userPassword
pwdMinAge: 0
pwdMaxAge: 0
pwdInHistory: 0
pwdCheckQuality: 0
pwdMinLength: 6
pwdExpireWarning: 0
pwdGraceAuthNLimit: 0
pwdLockout: TRUE
pwdLockoutDuration: 3600
pwdMaxFailure: 10
pwdFailureCountInterval: 300
pwdMustChange: FALSE
pwdAllowUserChange: TRUE
pwdSafeModify: FALSE

dn: cn=Replicator,dc=example,dc=com
cn: Replicator
sn: LDAP Replication User
objectClass: person
objectClass: top
userPassword: {SSHA}ENCRYPTEDPASSWORD
EOF

Apply Role as Consumer Replica

Perform these steps on every single consumer. Change names of hosts and certificate files accordingly. If provider is unreachable: Retry interval is 60 seconds for the first 10 times and then every 300 seconds until success. Replication starts immediately after running this command.

ldapmodify -v -H ldaps://ldap1.example.com -D cn=Manager,cn=config -W <<
dn: olcDatabase={2}mdb,cn=config
changetype: modify
add: olcSyncrepl
olcSyncrepl: rid=0
  provider="ldaps://ldapmaster.example.com:636"
  searchbase="dc=example,dc=com"
  type=refreshAndPersist
  retry="60 10 300 +"
  scope=sub
  schemachecking=on
  bindmethod=simple
  binddn="cn=Replicator,dc=example,dc=com"
  credentials=CLEARTEXTPASSWORD
  logbase="cn=accesslog"
  logfilter="(objectClass=*)"
  syncdata=accesslog
  tls_cert=/etc/pki/tls/certs/node-cert.pem
  tls_key=/etc/pki/tls/private/node-key.pem
  tls_cacert=/etc/pki/tls/certs/example-ca.pem
-
add: olcUpdateRef
olcUpdateRef: ldaps://ldapmaster.example.com:636
-
EOF

Verify Replication

When trying to modify your password on a readonly replica results in an return of a referral.

ldapmodify -v -H ldaps://ldap1.example.com -D uid=someuser,ou=people,dc=example,dc=com -W <<EOF
dn: uid=someuser,ou=people,dc=example,dc=com
changetype: modify
replace: userPassword
userPassword: {SSHA}THENEWENCRYPTEDPASSWORD
EOF

Enter LDAP Password:
modifying entry "uid=someuser,ou=people,dc=example,dc=com"
ldap_modify: Referral (10)
    referrals:
ldaps://ldapmaster.example.com:636/uid=someuser,ou=people,dc=example,dc=com

Verify data integrity across all replicas. Replace CLEARTEXPASSWORD with the actual password. Bind as manger compares also the hidden userpassword attributes.

LDAPSERVERS=(ldapmaster.example.com ldap1.example.com ldap2.example.com ldap3.example.com)

for server in ${LDAPSERVERS[*]}; do
  ldapsearch -LLL -H ldaps://$server -b dc=example,dc=com \
    -D cn=Manager,dc=example,dc=com -w 'CLEARTEXTPASSWORD' >/tmp/$server.ldif
  md5sum /tmp/$server.ldif
  rm -f /tmp/$server.ldif
done

a9efca26be302f31150e8068df0c192d /tmp/ldapmaster.example.com.ldif
a9efca26be302f31150e8068df0c192d /tmp/ldap1.example.com.ldif
a9efca26be302f31150e8068df0c192d /tmp/ldap2.example.com.ldif
a9efca26be302f31150e8068df0c192d /tmp/ldap3.example.com.ldif

Repeat this after turning off one consumer replica, changing data on the provider replica and starting the consumer replica again. The checksum should have changed but be identical on all servers. Iterate through all consumer replicas if necessary. Turn off the provider replica. The consumer replicas should still serve readonly data.

If a consumer replica is out of sync, stop the slapd, erase the database files and start slapd again. A full replication will start.