Configure OpenLDAP Master/Slave Replication with Puppet

We’re going to use Puppet to configure a pair of OpenLDAP servers with a master-slave replication.

This article is part of the Homelab Project with KVM, Katello and Puppet series.


We have two CentOS 7 servers installed which we want to configure as follows:

ldap1.hl.local ( – will be configured as an LDAP master
ldap2.hl.local ( – will be configured as an LDAP slave

Both servers have SELinux set to enforcing mode.

See the image below to identify the homelab part this article applies to.

Configuration with Puppet

Puppet master runs on the Katello server. We use camptocamp-openldap Puppet module to configure OpenLDAP. Please see the module documentation for features supported and configuration options available.

See here (CentOS 7) and here (Debian) for blog posts on how to configure an OpenLDAP server manually.

Note that instructions below apply to both LDAP servers.

Firewall configuration to allow LDAPS access from homelab LAN:

firewall { '007 allow LDAPS':
  dport  => [636],
  source => '',
  proto  => tcp,
  action => accept,

Ensure that the private key (which we created previously) in the PKCS#8 format is available.

file {'/etc/pki/tls/private/hl.pem':
  ensure => file,
  source => 'puppet:///homelab_files/hl.pem',
  owner => '0',
  group => 'ldap',
  mode  => '0640',

Configure the LDAP server (note how we bind to the SSL port):

class { 'openldap::server': 
  ldap_ifs  => [''],
  ldaps_ifs => [''],
  ssl_cert  => '/etc/pki/tls/certs/hl.crt',
  ssl_key   => '/etc/pki/tls/private/hl.pem',

Configure the database:

openldap::server::database { 'dc=top':
  ensure    => present,
  directory => '/var/lib/ldap',
  suffix    => 'dc=top',
  rootdn    => 'cn=admin,dc=top',
  rootpw    => '{SSHA}cGfSAyREZC5XnJa77iP+EdR8BrvZfUuo',

Configure schemas:

openldap::server::schema { 'cosine':
  ensure  => present,
  path    => '/etc/openldap/schema/cosine.schema',
openldap::server::schema { 'inetorgperson':
  ensure  => present,
  path    => '/etc/openldap/schema/inetorgperson.schema',
  require => Openldap::Server::Schema["cosine"],
openldap::server::schema { 'nis':
  ensure  => present,
  path    => '/etc/openldap/schema/nis.ldif',
  require => Openldap::Server::Schema["inetorgperson"],

Configure ACLs:

$homelab_acl = {
  '0 to attrs=userPassword,shadowLastChange' => [
    'by dn="cn=admin,dc=top" write',
    'by dn="cn=reader,dc=top" read',
    'by self write',
    'by anonymous auth',
    'by * none',
  '1 to dn.base=""' => [
    'by * read',
  '2 to *' => [
    'by dn="cn=admin,dc=top" write',
    'by dn="cn=reader,dc=top" read',
    'by self write',
    'by users read',
    'by anonymous auth',
    'by * none',
openldap::server::access_wrapper { 'dc=top' :
  acl => $homelab_acl,

Base configuration:

file { '/root/.ldap_config.ldif':
  ensure => file,
  source => 'puppet:///homelab_files/ldap_config.ldif',
  owner  => '0',
  group  => '0',
  mode   => '0600',
  notify => Exec['configure_ldap'],
exec { 'configure_ldap':
  command  => 'ldapadd -c -x -D cn=admin,dc=top -w PleaseChangeMe -f /root/.ldap_config.ldif && touch /root/.ldap_config.done',
  path     => '/usr/bin:/usr/sbin:/bin:/sbin',
  provider => shell,
  onlyif   => ['test -f /root/.ldap_config.ldif'],
  unless   => ['test -f /root/.ldap_config.done'],

Content of the file ldap_config.ldif can be seen below.

We create a read-only account cn=reader,dc=top for LDAP replication, we also create an LDAP user uid=tomas,ou=Users,dc=hl.local,dc=top to log into homelab servers.

dn: cn=reader,dc=top
objectClass: simpleSecurityObject
objectclass: organizationalRole
description: LDAP Read-only Access
userPassword: {SSHA}NrBn6Kd4rW8jmf+KWmfbTMFOkcC43ctF

dn: dc=hl.local,dc=top
o: hl.local
dc: hl.local
objectClass: dcObject
objectClass: organization

dn: ou=Users,dc=hl.local,dc=top
objectClass: organizationalUnit
ou: Users

dn: uid=tomas,ou=Users,dc=hl.local,dc=top
uid: tomas
uidNumber: 5001
gidNumber: 5001
objectClass: top
objectClass: person
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
userPassword: {SSHA}aBLnLxAUZAqwwII6fNUzizyOY/YAowtt
cn: Tomas
gn: Tomas
sn: Admin
mail: [email protected]
shadowLastChange: 16890
shadowMin: 0
shadowMax: 99999
shadowWarning: 14
shadowInactive: 3
loginShell: /bin/bash
homeDirectory: /home/guests/tomas

dn: ou=Groups,dc=hl.local,dc=top
objectClass: organizationalUnit
ou: Groups

dn: cn=tomas,ou=Groups,dc=hl.local,dc=top
gidNumber: 5001
objectClass: top
objectClass: posixGroup
cn: tomas

LDAP Master Server

Configure sync provider on the master node:

file { '/root/.ldap_syncprov.ldif':
  ensure => file,
  source => 'puppet:///homelab_files/ldap_syncprov.ldif',
  owner  => '0',
  group  => '0',
  mode   => '0600',
  notify => Exec['configure_syncprov'],
exec { 'configure_syncprov':
  command  => 'ldapadd -c -Y EXTERNAL -H ldapi:/// -f /root/.ldap_syncprov.ldif && touch /root/.ldap_syncprov.done && systemctl restart slapd',
  path     => '/usr/bin:/usr/sbin:/bin:/sbin',
  provider => shell,
  onlyif   => [
    'test -f /root/.ldap_syncprov.ldif',
    'test -f /root/.ldap_config.done'
  unless   => ['test -f /root/.ldap_syncprov.done'],

Content of the file ldap_syncprov.ldif for the master server can be seen below.

dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulePath: /usr/lib64/openldap

dn: olcOverlay=syncprov,olcDatabase={2}hdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpSessionLog: 100

LDAP Slave Server

Configure replication on the slave node:

file { '/root/.ldap_replication.ldif':
  ensure => file,
  source => 'puppet:///homelab_files/ldap_replication.ldif',
  owner  => '0',
  group  => '0',
  mode   => '0600',
  notify => Exec['configure_replication'],
exec { 'configure_replication':
  command  => 'ldapadd -c -Y EXTERNAL -H ldapi:/// -f /root/.ldap_replication.ldif && touch /root/.ldap_replication.done && systemctl restart slapd',
  path     => '/usr/bin:/usr/sbin:/bin:/sbin',
  provider => shell,
  onlyif   => ['test -f /root/.ldap_config.done'],
  unless   => ['test -f /root/.ldap_replication.done'],

Content of the file ldap_replication.ldif for the slave server is below. Note how we bind to the SSL port.

dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcSyncRepl
olcSyncRepl: rid=001
  retry="60 10 300 +"

The Result

We should end up with the following LDAP structure:

Anything that gets created on the LDAP master should be automatically synced to the slave.

Debugging LDAP Issues

If you hit problems, try running the following to start the LDAP server in debug mode with logging to the console:

# slapd -h ldapi:/// -u ldap -d 255

The logs can be a difficult to parse, but with Google search and a bit of luck you should to be able to work out what is going on.

Configure All Homelab Servers to use LDAP Authentication

We use Puppet module sgnl05-sssd to configure SSSD.

Add the following to the main homelab environment manifest file /etc/puppetlabs/code/environments/homelab/manifests/site.pp so that it gets applied to all servers.

Note how SSSD is configured to use both LDAP servers for redundancy.

class {'::sssd':
  ensure => 'present',
  config => {
    'sssd' => {
      'domains'               => 'default',
      'config_file_version'   => 2,
      'services'              => ['nss', 'pam'],
    'domain/default' => {
      'id_provider'           => 'ldap',
      'auth_provider'         => 'ldap',
      'cache_credentials'     => true,
      'default_shell'         => '/bin/bash',
      'mkhomedir'             => true,
      'ldap_search_base'      => 'dc=hl.local,dc=top',
      'ldap_uri'              => 'ldaps://ldap1.hl.local,ldaps://ldap2.hl.local',
      'ldap_id_use_start_tls' => false,
      'ldap_tls_reqcert'      => 'never',
      'ldap_default_bind_dn'  => 'cn=reader,dc=top',
      'ldap_default_authtok'  => 'PleaseChangeMe',

After Puppet applies the configuration above, we should be able to log into all homelab servers with the LDAP user.

12 thoughts on “Configure OpenLDAP Master/Slave Replication with Puppet

    • The article explains how to configure LDAP servers with Puppet.

      Puppet code snippets are provided with some brief explanation on what they do. If you’re not familiar with Puppet, or if these don’t make any sense to you, then there are also two weblinks listed for how to configure LDAP manually (on CentOS and Debian).

    • indeed I’m not familiar with puppet and if you could explain the snippets would help me to understand how it works and how to do it.


    • I think that the Puppet website is probably the best place for you to start. It explains what Puppet is, and most importantly, how it works. They also offer the Learning VM (I used it myself when I started with Puppet) which is the perfect resource for trying out Puppet without having to deploy it in your own environment.

  1. Error: ‘ldapadd -c -x -D cn=admin,dc=top -w pass -f /root/.ldap_config.ldif && touch /root/.ldap_config.done’ returned 49 instead of one of [0]
    Error: /Stage[main]/Main/Node[ldap1.hl.local]/Exec[configure_ldap]/returns: change from notrun to 0 failed: ‘ldapadd -c -x -D cn=admin,dc=top -w rolecs123 -f /root/.ldap_config.ldif && touch /root/.ldap_config.done’ returned 49 instead of one of [0]

    Do you have any idea?


    • Try the command manually, see if it works. If it does, then the problem is Puppet. If it doesn’t, then it’s LDAP related. Start the LDAP server in a debug mode, identify and fix the problem.

      I suspect it’s nothing to do with Puppet but rather a misconfigured LDAP server.

  2. Thanks for this great guide. I am setting up something similar in my home lab and trying to use puppet (and Katello) to set up the ldap server/clients. In you guide above, I cannot figure out what files you are editing with the following :
    Private key
    LDAP server (bind to SSL)
    Database config
    Schema config
    ACL config
    Base config

    Are you appending the *.pp files which come with the camptocamp-openldap, or simply creating your own *.pp files with just the text you provide above?

    I am still new to puppet and doing some online training to better understand the classes syntax, just now sure how your configuration ties in with the configuration already provided in the openldap module.

    Thanks for any help you can give.


    • You’re welcome!

      When you install a Puppet module (e.g. camptocamp-openldap), that module does nothing by default. You need to create your own manifest (a .pp file under manifests/) that will be used to apply configuration to a client node. I use various Puppet modules from Puppet Forge in my home lab, but I write my own manifests, classes and sometimes modules that I use to apply configuration to servers. I hope this answers your question.

    • Thanks again, I guess the issue is knowing if you should edit the pp files which are part of the openldap module, or create your own that work alongside the default pp files. From your response, I am guessing you dont touch the default files and simply create your own?

      The default files in the openldap(camptocamp) module are a follows:

      client client.pp params.pp server server.pp utils.pp
      [[email protected] manifests]# cd server
      [[email protected] server]# ls
      access.pp config.pp dbindex.pp install.pp module.pp schema.pp slapdconf.pp
      access_wrapper.pp database.pp globalconf.pp iterate_access.pp overlay.pp service.pp

    • If you edit the files that come with the module, then you will need to somehow merge changes every time you update that module with the new version. For that reason I don’t modify them but write a separate manifest instead that meets my needs. I find it easier this way.

  3. Hello,

    Your homelab series is really great in Covid 19 pandemic, I know it is too late in 2020 however it would be much appreciated if you can help me little bit.

    I have installed a module camptocamp/openldap in homelab environment and under manifest directory I have create a file called ldap.pp
    “/etc/puppetlabs/code/environments/homelab/modules/openldap/manifests/ldap.pp” but it is not getting applied to ldap hosts I have also edit host from “All host and Puppet classes”

    If I move this file ldap.pp to “/etc/puppetlabs/code/environments/homelab/manifests/” directory so it is applied to all hosts like ldaps ad zabbix.

    so my questions is “where to keep ldap.pp file and how to exactly applied it to only ldap hosts.

    Thanks in advance

Leave a Reply

Your email address will not be published. Required fields are marked *