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.

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

Leave a Reply

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