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.

Homelab

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

ldap1.hl.local (10.11.1.11) – will be configured as an LDAP master
ldap2.hl.local (10.11.1.12) – 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 => '10.11.1.0/24',
  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  => ['127.0.0.1:389/'],
  ldaps_ifs => ['0.0.0.0:636/'],
  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
olcModuleLoad: syncprov.la

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
  provider=ldaps://ldap1.hl.local:636/
  searchbase="dc=hl.local,dc=top"
  type=refreshAndPersist
  retry="60 10 300 +"
  schemachecking=on
  bindmethod=simple
  binddn="cn=reader,dc=top"
  credentials=PleaseChangeMe
  tls_reqcert=never
  tls_cert=/etc/pki/tls/certs/hl.crt
  tls_cacert=/etc/pki/tls/certs/hl.crt
  tls_key=/etc/pki/tls/private/hl.pem

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.

20 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.

      Thanks

    • 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?

    Thanks

    • 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 :
    Firewall
    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.

    Damien.

    • 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
      [root@katello manifests]# cd server
      [root@katello 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

  4. Hello Lisenet,

    Please help me on the below issue.

    Error:
    ==> default: Notice: /Stage[main]/Openldap::Server::Install/Package[openldap-servers]/ensure: created
    ==> default: Notice: /Stage[main]/Openldap::Server::Config/Shellvar[slapd]/value: value changed [‘ldapi:/// ldap:///’] to ‘ldap:/// ldapi:/// ‘
    ==> default: Notice: /Stage[main]/Openldap::Server::Service/Service[slapd]/ensure: ensure changed ‘stopped’ to ‘running’
    ==> default: Notice: /Stage[main]/Openldap::Utils/Package[openldap-clients]/ensure: created
    ==> default: Error: LDIF content:
    ==> default: dn: cn=nis,cn=schema,cn=config
    ==> default: objectClass: olcSchemaConfig
    ==> default: cn: nis

    ==> default: Error message: Execution of ‘/bin/ldapadd -cQY EXTERNAL -H ldapi:/// -f /tmp/openldap_schemas_ldif20210802-15796-7okrw0’ returned 80: ldap_add: Other (e.g., implementation specific) error (80)
    ==> default: additional info: olcObjectClasses: AttributeType not found: “manager”
    ==> default: adding new entry “cn=nis,cn=schema,cn=config”

    I guess because of the above error, getting the following error too.

    ==> default: Notice: /Stage[main]/Iwd-openldap/Openldap::Server::Schema[ifast]/Openldap_schema[ifast]: Dependency Openldap_schema[nis] has failures: true

    Details:
    OS: CentOS 7
    Virtual Box: Oracle Virtual Box 6.0
    Puppet Version: 3.8.7
    Puppet module:
    puppet module install puppetlabs/postgresql –version 4.7.1
    puppet module install herculesteam-augeasproviders_core –version 2.2.0
    puppet module install camptocamp/openldap –version 1.14.0
    puppet module install puppetlabs/firewall –version 1.8.0

    This script was written when we had Centos 6.x version:

    init.pp :
    class iwd-openldap {
    class { ‘openldap::server’: }

    openldap::server::schema { ‘cosine’:
    ensure => present,
    path => ‘/etc/openldap/schema/cosine.schema’
    }

    openldap::server::schema { ‘nis’:
    ensure => present,
    path => ‘/etc/openldap/schema/nis.schema’
    }

    openldap::server::schema { ‘ifast’:
    ensure => present,
    path => ‘/vagrant/puppet/modules/iwd-openldap/files/ifast-schema.schema’,
    require => [ Openldap::Server::Schema[“cosine”], Openldap::Server::Schema[“nis”] ]
    } -> exec { ‘insert ldap data’:
    command => ‘/bin/sh /vagrant/puppet/modules/iwd-openldap/files/import-ldap.sh’
    }
    }

    base.ldif:
    version: 1

    dn: cn=Manager,dc=ifdsgroup,dc=com
    objectClass: organizationalRole
    cn: Manager

    dn: ou=ifastbase,dc=ifdsgroup,dc=com
    objectClass: organizationalUnit
    objectClass: top
    ou: ifastbase

    dn: ou=ifast,ou=ifastbase,dc=ifdsgroup,dc=com
    objectClass: top
    objectClass: organizationalUnit
    ou: ifast
    — some more entries will be present

    Please help me to resolve this issue. I am struggling on this last two weeks. Let me know if you need any other details.

    Thanks & Regards,
    Raja Rajagopal

  5. Hello Lisenet,

    Thanks for the reply. However, I did not follow up any particular article. I had vagrant script (vagrant up command). When we had Cent OS 6, I ran the script, the script or vagrant up command will install all the necessary software’s. Howeever, now we moved to Cent OS 7 and I am running the same vagrant up command and getting that error. That’s the reason I had given all those details.

    Note: The Cent OS 7 also gets installed through vagrant and we are using puppet to install all the softwares in that VM.

    Thanks,
    Raja Rajagopal

    • It sounds like you have a work assignment to migrate your LDAP server from CentOS 6 to CentOS 7. If you are having issues with CentOS 7, then you will have to make sure that your Puppet/LDAP/Vagrant code is compatible with the new OS. Grab the error message, put it into a search engine and see what results you get. Also check LDAP migration/compatibility guides.

  6. Hello Lisenet,

    Once again thanks for the reply. How will I come to know that the issue is with Cent OS 7 and how I can make sure that Puppet/LDAP/Vagrant code is compatible with new OS. When I ran vagrant up command it is creating the VM, but after that when it is trying to install puppet module (which I mentioned in my earlier post) it is giving issue. I searched with the error message, however I did not get anything. Sure I will check the guide too.

    Please let me know if you need any other details I will provide.

    Thanks,
    Raja Rajagopal

    • You will have to check the code to understand what it does, line by line, and possibly run it in blocks to see if everything works, then move on to the next one. This should make troubleshooting easier.

  7. Hello Lisenet,

    Once again thanks for the reply. Yes.. It not migrate LDAP server from Cent OS 6 to Cent OS 7. Earlier we used Cent OS 6 in the VM and Windows 7. Now Cent OS 6 is out of scope and Windows 7 support is going to end. So, we moved to Cent OS 7 and running the same vagrant script.(vagrant up). Now it is giving error. I am not sure what puppet modules should I use for Cent OS 7. I searched the error in google and all, but did not get correct answer. That’s why I posted in your site.

    Please let me know if you need any details.

    Note: We did not change anything in the vagrant file except the box.

    Thanks & Regards,
    Raja Rajagopal

    • Putting the reason aside, the fact is that your code was originally written for an older version of the OS/software, and may therefore be incompatible with CentOS 7 and/or software versions that come with it. You’ll have to check the code, line by line, to understand what it does and which part is failing. Once you isolate the issue, you should check for differences (changelogs, upgrade notes) between software versions that the problem might be caused by.

Leave a Reply

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