DEBOPS.SLAPD(5)

NAME
debops.slapd - Manage OpenLDAP server using Ansible

SYNOPSIS

debops run service/slapd [--limit group,host,...] [--diff] [--check] [--tags tag1,tag2,...] [--skip-tags tag1,tag2,...] [<ansible-playbook options>] ...

debops run service/slapd --tags role::slapd:tasks [--limit group,host,...] [--diff] [--check] [<ansible-playbook options>] ...

DESCRIPTION

OpenLDAP <https://openldap.org/> is an open source Lightweight DIrectory Access Protocol <https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol> server, that can be used for centralized directory services.

The debops.slapd Ansible role can be used to install and manage OpenLDAP server (slapd) on Debian or Ubuntu hosts. The role supports easy management of the on-line configuration (OLC) used to manage slapd instances and integrates with other DebOps roles like debops.pki and debops.dhparam for additional functionality.

GETTING STARTED

  OpenLDAP features enabled by default

The debops.slapd role enables and configures some of the OpenLDAP features that otherwise are enabled dynamically and could have different names in the LDAP directory on different installations due to the order in which they were enabled. If you are planning to apply the role on an existing installation, you should review the configuration before doing so - the OpenLDAP server usually refuses the incorrect configuration outright, which should not affect the existing installation, but that's not a 100% guarantee.

  Access to service denied by default

The default configuration denies access to the OpenLDAP service from anywhere through the firewall and TCP Wrappers, although the slapd daemon will listen for connections on all interfaces. You can use the slapd__*_allow variables to define what IP addresses or subnets have access to the LDAP service.

If you don't use the debops.ferm and the debops.tcpwrappers Ansible roles to control access to services, keep in mind that the OpenLDAP service will be available publicly. This might be important while designing the LDAP Access Control List and password policies.

You can control the default behaviour using the slapd__accept_any boolean variable. Another option is to use an external firewall with IDS/IPS systems that can analyze LDAP traffic. Access through a VPN connection, for example using the debops.tinc Ansible role, can also be a good option for limiting the exposure of LDAP directory directly to the Internet.

  SASL authentication

The role uses the debops.saslauthd Ansible role to configure the saslauthd service which provides SASL authentication capability. PAM authentication is used by default to authenticate LDAP access via the UNIX accounts. If LDAP support is configured on the host by the debops.ldap role, the directory will be used for authentication instead.

Humans can authenticate themselves by specifying their account names without a domain part, which are defined by the uid attribute of their inetOrgPerson LDAP objects, looked up in the ou=People subtree of the LDAP directory.

Computers can authenticate to the LDAP directory by specifying an UNIX account name and their FQDN domain name, for example sshd@host.example.org, which corresponds to the uid and host attributes of the account LDAP objects. Only hosts that have been registered in the LDAP directory ou=Hosts subtree can authenticate with this method.

The debops.saslauthd role defines the slapd LDAP profile which contains the LDAP search query and filtering rules which can be used for access control to the OpenLDAP directory itself.

  Authentication debugging and logs

By default the slapd log output is set to none, which results in minimal logs. If you need to debug or keep track of LDAP authentication and search queries, you can easily configure slapd to do that through the Ansible inventory, by adding a configuration file, for example in a cluster of LDAP hosts:

---
# ansible/inventory/group_vars/slapd_cluster/slapd.yml
slapd__group_tasks:
  - name: 'Configure the OpenLDAP server log level'
    dn: 'cn=config'
    attributes:
      olcLogLevel: 'stats'
    state: 'exact'

Make sure that the name parameter corresponds to the correct slapd option defined in the slapd__default_tasks variable, to modify it using Universal Configuration mechanism included in DebOps.

You can use journald to view the slapd logs:

journald -f -u slapd.service

  Example inventory

To install and manage the OpenLDAP server on a host, you need to add it to the [debops_service_slapd] Ansible inventory group:

[debops_service_slapd]
hostname

  Example playbook

If you are using this role without DebOps, here's an example Ansible playbook that uses the debops.slapd role:

---
- name: Manage OpenLDAP service
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_service_slapd' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  roles:
    - role: ferm
      tags: [ 'role::ferm', 'skip::ferm' ]
      ferm__dependent_rules:
        - '{{ slapd__ferm__dependent_rules }}'
    - role: tcpwrappers
      tags: [ 'role::tcpwrappers', 'skip::tcpwrappers' ]
      tcpwrappers__dependent_allow:
        - '{{ slapd__tcpwrappers__dependent_allow }}'
    - role: cron
      tags: [ 'role::cron', 'skip::cron' ]
    - role: logrotate
      tags: [ 'role::logrotate', 'skip::logrotate' ]
      logrotate__dependent_config:
        - '{{ slapd__logrotate__dependent_config }}'
    - role: python
      tags: [ 'role::python', 'skip::python', 'role::ldap', 'role::slapd' ]
      python__dependent_packages3:
        - '{{ ldap__python__dependent_packages3 }}'
        - '{{ slapd__python__dependent_packages3 }}'
      python__dependent_packages2:
        - '{{ ldap__python__dependent_packages2 }}'
        - '{{ slapd__python__dependent_packages2 }}'
    - role: ldap
      tags: [ 'role::ldap', 'skip::ldap' ]
      ldap__dependent_tasks:
        - '{{ saslauthd__ldap__dependent_tasks }}'
      when: slapd__saslauthd_enabled | bool
    - role: saslauthd
      tags: [ 'role::saslauthd', 'skip::saslauthd' ]
      saslauthd__dependent_instances:
        - '{{ slapd__saslauthd__dependent_instances }}'
      when: slapd__saslauthd_enabled | bool
    - role: slapd
      tags: [ 'role::slapd', 'skip::slapd' ]

The included debops.ferm and debops.tcpwrappers Ansible roles are optional. They can be used for managing firewall and access rules to the LDAP service.

If you further want to enable LDAP transport layer security in debops.slapd role, the debops.pki and debops.dhparam roles must also be applied on the host. The debops.slapd role will automatically detect and use their configured environments if available.

  Ansible tags

You can use Ansible --tags or --skip-tags parameters to limit what tasks are performed during Ansible run. This can be used after host is first configured to speed up playbook execution, when you are sure that most of the configuration has not been changed.

Available role tags:

role::slapd
    Main role tag, should be used in the playbook to execute all of the role tasks as well as role dependencies.

role::slapd:tasks
    Run the LDAP tasks generated by the role to apply OpenLDAP cn=config configuration. This will also include the ACL tests.

role::slapd:slapacl
    Run tasks that maintain the slapacl test suite script and perform OpenLDAP ACL tests when enabled.

  Other resources

List of other useful resources related to the debops.slapd Ansible role:

•   Manual pages: slapd(8) <https://manpages.debian.org/slapd(8)>, slapd-config(5) <https://manpages.debian.org/slapd-config(5)>

•   LDAP for Rocket Scientists <http://www.zytrax.com/books/ldap/>, an excellent book about LDAP and OpenLDAP

•   Debian LDAP Portal <https://wiki.debian.org/LDAP> page in the Debian Wiki

OPENLDAP OVERLAYS

OpenLDAP server supports overlays <https://www.openldap.org/doc/admin24/overlays.html> which can be added to a LDAP database to modify its functionality. The overlays listed below are enabled by the debops.slapd role by default.

  Sync Provider overlay

The role will by default enable the Sync Provider <http://www.zytrax.com/books/ldap/ch6/syncprov.html> (syncprov) dynamic module and overlay, in both the cn=config configuration database, and the main OpenLDAP database.

The Sync Provider functionality is used in different data replication <https://www.openldap.org/doc/admin24/replication.html> strategies. Enabling it by default, even on a standalone OpenLDAP server, should be harmless - the replication requires additional configuration defined in each OpenLDAP database. The overlay is enabled first to keep the X-ORDERED index number consistent between the cn=config database and the main database.

Manual page: slapo-syncprov(5) <https://manpages.debian.org/slapo-syncprov(5)>

  Password Policy overlay

The debops.slapd role will by default import the ppolicy LDAP schema, load the ppolicy dynamic module and enable the Password Policy overlay in the main OpenLDAP database.

The Password Policy <https://www.zytrax.com/books/ldap/ch6/ppolicy.html> overlay is used to maintain the security and quality of various passwords stored in the LDAP database. By default the overlay will ensure that the cleartext passwords passed to the OpenLDAP server are hashed using the algorithms specified in the olcPasswordHash parameter (salted SHA-512 via crypt(3) <https://manpages.debian.org/crypt(3)> function is set by default by the debops.slapd role).

The LDAP administrators can define default and custom Password Policies in the main database, which can enforce additional password requirements, like minimum password length, different types of characters used, lockout policy, etc.

Manual page: slapo-ppolicy(5) <https://manpages.debian.org/slapo-ppolicy(5)>

  Attribute Uniqueness overlay

The Attribute Uniqueness overlay <https://www.openldap.org/doc/admin24/overlays.html#Attribute%20Uniqueness> is used to enforce that specific LDAP attributes are unique acrosse the LDAP directory. The default configuration enforces the uniqueness of the uidNumber and gidNumber attributes in the entire LDAP directory, and the uid, gid and mail attributes in the ou=People,dc=example,dc=org subtree of the directory.

Manual page: slapo-unique(5) <https://manpages.debian.org/slapo-unique(5)>

  Reverse Group Membership Maintenance overlay

The memberOf overlay <https://www.openldap.org/doc/admin24/overlays.html#Reverse%20Group%20Membership%20Maintenance> is used to update the LDAP objects of group members when they are added or removed from a particular groupOfNames or groupOfEntries objects, as well as "role occupants" defined in a given organizationalRole object. The overlay also maintains reverse membership information of the groupOfURLs objects maintained by the AutoGroup overlay.

Applications and services can search for objects with the memberOf attribute with specific values to get the list of groups or roles a given user belongs to.

Manual page: slapo-memberof(5) <https://manpages.debian.org/slapo-memberof(5)>

  Referential Integrity overlay

The refint overlay <https://www.openldap.org/doc/admin24/overlays.html#Referential%20Integrity> is used to update Distinguished Name references in other LDAP objects when a particular object is renamed or removed. This ensures that the references between objects in the LDAP database are consistent.

Manual page: slapo-refint(5) <https://manpages.debian.org/slapo-refint(5)>

  Audit Logging overlay

The auditlog overlay <https://www.openldap.org/doc/admin24/overlays.html#Audit%20Logging> records all changes performed in the LDAP database using an external log file. Changes are stored in the LDIF format, that includes a timestamp and the identity of the modifier. The role will automatically ensure that the audit log files are rotated periodically using the logrotate service to keep the disk usage under control.

Manual page: slapo-auditlog(5) <https://manpages.debian.org/slapo-auditlog(5)>

  Attribute Constraints overlay

The constraint overlay <https://www.openldap.org/doc/admin24/overlays.html#Constraints> can be used to place constraints on specific LDAP attributes, for example number of possible values, size or format.

Manual page: slapo-constraint(5) <https://manpages.debian.org/slapo-constraint(5)>

  AutoGroup overlay

The autogroup overlay is yet another attempt at creating dynamic groups <https://www.zytrax.com/books/ldap/ch11/dynamic.html> in the LDAP directory. Normally using the combination of the slapo-dynlist(5) <https://manpages.debian.org/slapo-dynlist(5)> and the slapo-dyngroup(5) <https://manpages.debian.org/slapo-dyngroup(5)> overlays the LDAP directory can support dynamic group objects which define membership in a group using LDAP search URLs. However these groups are "virtual" and don't really exist, using the dynamic attributes in searches will not include these groups. Also, the reverse membership information defined by the memberOf attribute cannot be implemented this way.

With autogroup overlay, the directory server checks on each add, modify or delete operation on an object if that object is included in a search of a particular groupOfURLs group and statically adds or removes a reference to it in the member attribute as needed. With addition of the memberof overlay which maintains reverse membership information of a given object using the memberOf attribute, the AutoGroup overlay can be used to provide two-way dynamic group support in the LDAP directory. The write performance might be an issue with large datasets.

The dynamic groups are defined using the groupOfURLs LDAP object. The memberURL attribute(s) define the LDAP search URLs <https://ldapwiki.com/wiki/LDAP%20URL> (RFC 4516 <https://datatracker.ietf.org/doc/html/rfc4516.html>) used to specify the members of the group.

WARNING: During development of the feature in DebOps, crashes of the slapd daemon were observed in multi-master replication mode on older Debian releases. The OpenLDAP version included in Debian Buster seems to work fine, though.

  LastBind overlay

The lastbind overlay and the corresponding OpenLDAP module can be used to maintain information about last login time of a LDAP account, similar to the lastLogon <https://ldapwiki.com/wiki/LastLogon> functionality from Active Directory. The primary purpose of the lastbind overlay is detection of inactive user accounts; it shouldn't be relied on for real-time login tracking.

The time of the last successful authenticated bind operation of a given LDAP object is stored in the authTimestamp operational attribute (not replicated, not visible in normal queries, has to be specifically requested). By default the timestamp is updated once a day to avoid performance issues in larger environments.

Manual page: slapo-lastbind(5) <https://manpages.debian.org/slapo-lastbind(5)>

CUSTOM LDAP SCHEMAS

The debops.slapd role by default configures a set of custom LDAP schemas on top of the default ones enabled during slapd installation. The schemas included in a LDAP directory cannot be removed without issues, therefore it's best to select the ones you want to use at the database initialization.

You can find more details about managing the LDAP schemas using cn=config configuration in the Zytrax LDAP guide <http://www.zytrax.com/books/ldap/ch6/slapd-config.html#use-schemas>.

  General notes

The slapd__*_schemas variables define a list of LDAP schemas to import by the role. The list is not merged via custom Ansible filter plugins and is only additive. You can define different sets of schemas on different Ansible inventory levels.

The schema files need to already be present on the remote host to be imported by the role. The default schemas included by the role are installed via APT packages, you can see the available set of schemas by running the command:

apt-cache search fusiondirectory plugin schema

The Debian/Ubuntu archive also contains a smaller set of APT packages for the GOsa² application which contain LDAP schemas, however the gosa-* and fusiondirectory-* packages conflict with each other.

You can use the debops.resources role to copy custom *.schema or *.ldif files to the remote host before importing them. The *.ldif files can be imported automatically, but the *.schema import relies on the fusiondirectory-insert-schema command which is available in the fusiondirectory-schema APT package.

If you are using clustered OpenLDAP, for example in N-Way Multi Master replication mode, you should import the schemas only on one node at a time. Execution of the import command on multiple remote hosts at once will result in an error when one replication host already has a schema defined with a specific X-ORDERED index number, but the other nodes don't. The schema will be replicated automatically between all of the OpenLDAP masters.

  The rfc2307bis schema

The rfc2307bis LDAP schema <https://tools.ietf.org/html/draft-howard-rfc2307bis-02> is a proposed replacement of the RFC 2307 <https://datatracker.ietf.org/doc/html/rfc2307.html> document that defines the nis LDAP schema. Main improvement over the old nis schema is the modified posixGroup LDAP object, which in the new schema has an AUXILIARY type instead of the STRUCTURAL type. Notably, the posixAccount LDAP object has an AUXILIARY type in the original nis scheme; the change makes both objects work similarly by allowing an UNIX-based attributes to be combined with LDAP-based directory entries.

Without this change, LDAP directories that are used in UNIX-like environments for group authentication require two sets of groups stored in the directory - one for LDAP entities based on DN attributes, and another for UNIX entities based on GID attributes. The new schema enables the posixGroup object type to combine with other LDAP object types, namely groupOfNames or groupOfUniqueNames. This allows the two sets of data to be combined in one object - allowing both the UID and DN attributes to be used as group members. This of course depends on the client-side support - both nslcd and sssd daemons should be able to use the combined data sets correctly.

  Support in Debian

Some of the Linux-based distributions provide the slapd package with the new rfc2307bis schema enabled by default. Unfortunately, Debian's slapd APT package includes the original nis schema which conflicts with the new rfc2307bis schema because both define the same LDAP objects and their attributes. The nis schema is loaded by default during the slapd package installation and removing it from the already initialized directory can be difficult.

Fortunately, there's a clean way to avoid this issue and enable the rfc2307bis schema on slapd installation. The Debian Archive contains two packages that provide it: fusiondirectory-schema <https://packages.debian.org/stable/fusiondirectory-schema> and gosa-schema <https://packages.debian.org/stable/gosa-schema>. Both packages conflict with each other, therefore only one can be installed at a time. In the debops.slapd role, the fusiondirectory-schema has been selected because FusionDirectory project seems to be an actively maintained fork of GOsa² and will be more likely to be selected for installation; another reason is more fusiondirectory-plugin-*-schema APT packages available in Debian.

The role still works fine with gosa-schema APT package installed, however this will not be detected automatically; the user should redefine the slapd__rfc2307bis_packages list the Ansible inventory to select this APT package.

Before the installation of the slapd APT package, the debops.slapd role will install the fusiondirectory-schema package, divert the /etc/ldap/schema/nis.(ldif,schema) files aside using the dpkg-divert tool and create a symlink to the /etc/ldap/fusiondirectory/rfc2307bis.(ldif,schema) files in their place. With this modification, when the slapd APT package is installed, it will automatically include the modified rfc2307bis schema.

The automatic installation of the rfc2307bis schema can be disabled by setting the slapd__rfc2307bis_enabled boolean variable to False. This allows usage of the LDAP directories that use the old nis schema without modifications to the directory contents.

  The initial LDAP schemas

During installation of the slapd Debian package, the postinstall script creates a new OpenLDAP configuration using the /usr/share/slapd/slapd.init.ldif LDIF configuration file. The default LDAP schemas imported at that time are:

•   core.schema

•   cosine.schema

•   nis.schema (replaced by the rfc2307bis.schema before installation)

•   inetorgperson.schema

    You can find the schema files in the /etc/ldap/schema/ directory on the OpenLDAP server host.

  The debops schema

This schema provides the basic LDAP object identifiers (OIDs) for other LDAP schemas that are created by DebOps project - the Private Enterprise Number assigned to the project by IANA and the separate namespace designated to be used for LDAP schemas. These object identifiers are in a separate "schema" so that they can be re-used in multiple LDAP schemas imported after this schema without creating conflicts in the OpenLDAP cn=schema subtree.

  The posixgroupid schema

This is a custom LDAP schema maintained by DebOps. It can be found in the ansible/roles/debops.slapd/files/etc/ldap/schema/debops/ directory of the DebOps monorepo.

The rfc2307bis schema fixes one issue with POSIX groups in LDAP - the posixGroup object attributes can be added to any object type. But there's one other problem not fixed by this schema - the name of the group is taken from the cn attribute. This causes an issue when LDAP group names are in a human-readable form, instead of a short string form preferred in POSIX environments, for example: UNIX Administrators vs admins.

Another issue shows up with different User Private Group <https://wiki.debian.org/UserPrivateGroups> implementations in LDAP - because the cn attribute in the LDAP objects that define people is used for the person's full name, for example "John Smith", personal UNIX groups cannot be defined in the same LDAP object, even though the gidNumber attribute is required by the posixAccount object type.

There are different solutions to this problem - create a separate posixGroup object for each person and put it either in a separate directory subtree, or as a child entry of the person's object, but these solutions are cumbersome and require hard to implement ACL rules. A different solution is adding a new attribute that would define the UNIX group name separate from the common name.

This is what the posixgroupid.schema LDAP schema does - it adds the gid or groupid attribute, either in a separate gidObject object type, or in the posixGroupId object which is a subclass of the posixGroup object. With the gid attribute, LDAP clients that are configured to use it, can use a different LDAP attribute as the UNIX group name instead of the human-readable cn name. Similarly, LDAP objects that represent people can have a gid attribute that contains the name of the Private User Group, usually the same as the uid attribute. This requires that the LDAP clients look for the gid attribute instead of the cn attribute as the UNIX group name, but it's usually a simple configuration change.

  The nextuidgid schema

The nexuidgid schema defines a set of simple LDAP objects (uidNext, gidNext), which can be used to store uidNumber and gidNumber values, respectively. These objects are used in DebOps to keep track of the next available UID/GID values, but can be used for other purposes, for example to split the UID/GID range used in the LDAP directory into subranges.

  The orgstructure schema

The orgstructure schema defines an additional LDAP object, organizationalStructure. This object is meant to replace the use of the organizationalUnit object class in the base LDAP directory entries, such as ou=People, ou=Groups, and so on. The new object contains attributes selected to help manage the access control for specific LDAP directory subtrees.

  The ppolicy schema

The ppolicy schema provides LDAP object and attribute definitions required by the Password Policy overlay.

  The ldapns schema

The ldapns schema provides a set of LDAP objects and attributes that can be used for granular access control to services and hosts that use the LDAP directory. The host attribute can be used to define a list of FQDN names, or hostnames to which a given user or application has access. The authorizedService attribute can contain a list of services accessible to an user or application. LDAP clients can use these attributes in LDAP filters to grant access only to specific people or applications.

  The groupofentries schema

The groupofentries schema defines a new "groupOfEntries" LDAP Object Class <https://tools.ietf.org/html/draft-findlay-ldap-groupofentries-00> which is meant to replace the groupOfNames object defined by RFC 2256 <https://datatracker.ietf.org/doc/html/rfc2256.html>. The new object can be used to create groups that can be empty (no member attribute required).

  The openssh-lpk schema

The openssh-lpk schema allows the LDAP directory to hold SSH public keys, which combined with OpenSSH AuthorizedKeysCommand configuration can allow SSH authentication via LDAP directory. An example openssh-ldap-publickey <https://github.com/AndriiGrytsenko/openssh-ldap-publickey> script shows how this can be configured with OpenSSH and OpenLDAP.

The debops.slapd Ansible role contains a modified version of the official schema which adds a separate attribute for the public key fingerprints, meant to help find the key owners based on the information logged by the sshd service.

The debops.sshd Ansible role already contains support for SSH public key lookup in OpenLDAP, see its documentation for more details about enabling the support.

  The sudo schema

The sudo <https://en.wikipedia.org/wiki/Sudo> service can be configured to use LDAP directory as a backend <https://www.sudo.ws/man/1.8.17/sudoers.ldap.man.html> for its rules. The debops.sudo Ansible role will enable the LDAP support when the debops.ldap configuration is detected (not implemented yet).

The rules in the LDAP directory are meant to be used with LDAP-based users and groups; local accounts should still rely on local /etc/sudoers contents to ensure service availability in case of an issue with connection to the LDAP service.

Manual pages: sudoers.ldap(5) <https://manpages.debian.org/sudoers.ldap(5)>

  The eduPerson schema

The eduPerson and eduOrg are Lightweight Directory Access Protocol (LDAP) schema designed to include widely-used person and organizational attributes in higher education. The eduPerson object class provides a common list of attributes and definitions, drawing on the existing standards in higher education. The schema were developed by the Internet2 project <https://www.internet2.edu/products-services/trust-identity/eduperson-eduorg/> and are commonly used in academic institutions.

The schema is available in Debian in the fusiondirectory-plugin-supann-schema APT package, however that version is slightly outdated and does not include object and attribute descriptions. Because of that, DebOps contains its own copy of the schema, cleaned up and updated, which will be imported by default to OpenLDAP directory server.

The version of the schema included in DebOps has been extended with additional attributes for the eduPerson and eduOrg Object Classes not present in the official specification. These attributes have been found useful in an academic environment; more attributes might be added in the future if needed.

  The schac schema

The schac (SCHema for ACademia <https://wiki.refeds.org/display/STAN/SCHAC>) LDAP schema extends the eduPerson schema with an additional set of LDAP object classes and attributes useful in university and higher education environments.

  The nextcloud schema

The nextcloud schema provides a set of LDAP objectClasses and attributes that can be used to control LDAP integration with Nextcloud application. Using these attributes, administrators can define disk quotas for Nextcloud users stored in the LDAP directory, as well as define which user groups present in LDAP are available in the Nextcloud user interface.

  The mailservice schema

The mailservice schema is based on several draft RFCs and includes a set of LDAP objects and attributes useful for mail services. The mailRecipient object class provides attributes for "final destination" mail recipient accounts, like mail aliases, Sieve filtering rules, mail storage location, quota. The mailDistributionList object class allows for creation of simple mailing lists or distribution lists which can be used to distribute e-mail messages to multiple recipients with basic access controls.

With this schema installed, the mail attribute should not be used for mail services, but should be relegated to user authentication only.

  The dyngroup schema

The dyngroup schema provides LDAP object and attribute definitions for creation of dynamic groups <https://www.zytrax.com/books/ldap/ch11/dynamic.html> inside of the LDAP directory. Dynamic group objects define LDAP search URLs which are resolved by the directory server on searches and provided to the client with computed set of member attributes as opposed to having a static object with members.

In DebOps, this schema is modified by adding the optional member attribute to the group objects to support usage of the AutoGroup overlay which maintains "static" dynamic groups by updating these objects each time an LDAP entry included in the specified search is created, modified or destroyed.

  The freeradius schema

The freeradius schema provide definitions of various LDAP objects usable by the FreeRADIUS <https://freeradius.org/> service, namely RADIUS Clients, RADIUS Profiles, RADIUS Accounting database objects, and FreeRADIUS DHCPv4/DHCPv6 configuration objects. The version of the schema included in DebOps is based on the master branch of the FreeRADIUS project code.

Keep in mind that the FreeRADIUS schema is split into multiple freeradius-*.schema files, and the main file defines just the objectIdentifier LDAP objects used in other schema files. If you don't want to use specific FreeRADIUS features you should be able to disable them by changing the list of LDAP schemas the role loads, but the freeradius.schema file should be always present.

LDAP ACCESS CONTROL LIST

The default Access Control List is defined in the slapd__acl_tasks default variable. It might be preferable to override this variable in the Ansible inventory by copying its contents there. This should keep the existing ACL rules intact in case of any updates to the debops.slapd role.

The default ACL will be updated over time when new DebOps roles are integrated with the LDAP directory. The DebOps documentation contains a browsable representation of the LDAP Directory Information Tree that maps how various DebOps roles interact with the directory; this should enable easier redesign of the Access Control List according to the needs of one's organization.

The user-facing LDAP directory structure that conforms to the ACL defined by the role will be initialized by the debops.slapd role as well. You can use the ansible/playbooks/ldap/init-directory.yml Ansible playbook to create the initial LDAP Administrator account which will finish the installation. See LDAP directory initialization for more details.

  Default security policy

•   Deny anonymous access, apart from authentication to the LDAP directory.

•   Define access for groups of LDAP objects, not for specific objects.

•   Nobody but LDAP administrators and replicators should be able to read userPassword attribute values, but replacement should be possible.

•   Grant LDAP administrators full access to the LDAP directory, including passwords and confidential information.

•   Grant LDAP replicators read-only access to entire LDAP directory, including passwords and other confidential data (required for full replication).

•   Treat UNIX/POSIX environment as separate security domain, with its own group of administrators that can define UID/GID values and other attributes related to it.

•   Grant LDAP editors write access to most of the LDAP directory. They don't have write access to the LDAP Administrators and the LDAP Replicators roles, the UNIX Administrators group, the ou=System Groups subtree as well as to the UNIX attributes, they cannot see passwords (but can change them).

•   Group owners should be able to add or remove members of their own group.

•   Object owners should be able to modify passwords in their own objects.

•   Authenticated users should have read access to most of the directory, apart from security-sensitive data like passwords or private information.

  Required LDAP schemas

These LDAP schemas are expected to be present in the LDAP directory by the default ACL rules:

•   rfc2307bis

•   posixgroupid

    If some of the schemas specified here are not present, the default ACL configuration will not be enabled correctly.

  Directory groups

In this section of the documentation you can find a list of LDAP groups which are used in the default debops.slapd Access Control List rules. he LDAP Distinguished Names used in the documentation assume that the example.org DNS domain is used by the OpenLDAP server.

  UNIX Administrators

DN  cn=UNIX Administrators,ou=Groups,dc=example,dc=org

Obsolete
    cn=UNIX Administrators,ou=System Groups,dc=example,dc=org

•   Members of this group have write access to the uid, uidNumber, gid, gidNumber and homeDirectory attributes of the posixAccount, posixGroup, posixGroupId, uidNext and gidNext LDAP objects. Everyone else has read-only access to these attributes.

•   Members of this group have write access to the ou=SUDOers,dc=example,dc=org LDAP subtree which contains sudoers.ldap(5) <https://manpages.debian.org/sudoers.ldap(5)> configuration. Everyone else has read-only access.

•   Access to the group is restricted to Read-only by role occupants of the LDAP Editor and the Account Administrator LDAP roles.

  Hidden Objects

DN  cn=Hidden Objects,ou=Groups,dc=example,dc=org

•   Objects in this group are visible only to themselves as well as people and other entities with the LDAP Administrator, the LDAP Editor and the Hidden Object Viewer roles.

•   The access control list checks the memberOf attribute of an LDAP object and grants or denies access to it depending on its membership status.

    NOTE: Due to limitations of the OpenLDAP Access Control List features, to hide the children objects of a given LDAP object, all of them need to be also included as separate member attributes in the Hidden Objects group. Otherwise the children of hidden objects can be still visible in general LDAP searches, for example (objectClass=*). The DN attribute of such entries can also disclose the presence of a hidden object.

  Directory roles

In this section of the documentation you can find a list of LDAP roles which are used in the default debops.slapd Access Control List rules. The LDAP Distinguished Names used in the documentation assume that the example.org DNS domain is used by the OpenLDAP server.

  LDAP Administrator

DN  cn=LDAP Administrator,ou=Roles,dc=example,dc=org

Obsolete
    cn=LDAP Administrators,ou=System Groups,dc=example,dc=org

•   Role grants full access to the entire LDAP directory.

•   Access to the role is restricted to read-only by role occupants of the LDAP Editor and the Account Administrator LDAP roles.

  LDAP Replicator

DN  cn=LDAP Replicator,ou=Roles,dc=example,dc=org

Obsolete
    cn=LDAP Replicators,ou=System Groups,dc=example,dc=org

•   Role grants read-only access to the entire LDAP directory.

•   Access to the role is restricted to read-only by role occupants of the LDAP Editor and the Account Administrator LDAP roles.

  LDAP Editor

DN  cn=LDAP Editor,ou=Roles,dc=example,dc=org

Obsolete
    cn=LDAP Editors,ou=System Groups,dc=example,dc=org

•   Role grants write access to most of the LDAP directory, apart from the privileged groups and roles.

  LDAP Monitor

DN  cn=LDAP Monitor,ou=Roles,dc=example,dc=org

•   Role grants read access to the cn=Monitor LDAP subtree, which stores the information about the OpenLDAP server. It is usually utilized by monitoring services to gather data about OpenLDAP service.

  Account Administrator

DN  cn=Account Administrator,ou=Roles,dc=example,dc=org

Obsolete
    cn=Account Administrators,ou=System Groups,dc=example,dc=org

•   Role grants write access to the shadowLastChange and write-only access to the userPassword attributes in the ou=People,dc=example,dc=org LDAP subtree to allow password changes in personal accounts.

•   Role grants write access in the ou=People,dc=example,dc=org, ou=Groups,dc=example,dc=org and the ou=Machines,dc=example,dc=org LDAP subtrees.

    NOTE: Purpose of this role is too broad and in the future it will be split into separate, more focused LDAP roles.

  Password Reset Agent

DN  cn=Password Reset Agent,ou=Roles,dc=example,dc=org

Obsolete
    cn=Password Reset Agents,ou=System Groups,dc=example,dc=org

•   Role grants write-only access to the shadowLastChange and the userPassword attributes in the ou=People,dc=example,dc=org LDAP subtree to allow password changes in personal accounts.

•   This role is meant for applications that act on behalf of the users to allow them to perform password changes after out-of-band authentication.

  SMS Gateway

DN  cn=SMS Gateway,ou=Roles,dc=example,dc=org

•   Role grants read-only access to the mobile LDAP attribute, required by the SMS gateways to send SMS messages.

  Hidden Object Viewer

DN  cn=Hidden Object Viewer,ou=Roles,dc=example,dc=org

•   Role occupants can see LDAP objects included in the Hidden Objects LDAP group.

  Other directory objects

This section of the documentation describes various other LDAP objects and their default access policy defined by the debops.slapd Ansible role.

  System Groups

DN  ou=System Groups,dc=example,dc=org

•   This subtree was used to hold LDAP objects related to access control, which have been converted to normal groups and roles. It can be safely removed from existing LDAP directories; the ACL rules for this LDAP object will be removed at a later date to allow for secure migration to the new directory layout.

  Group owners

•   The owners of the LDAP groups under the ou=Groups,dc=example,dc=org LDAP subtree, defined by the owner attribute, can add, modify or remove members in their respective groups, using the member attribute.

  Object owners

DN  self

•   Object owners see their own LDAP objects even if they are hidden using the Hidden Objects LDAP group.

•   Object owners can authenticate to the LDAP directory via the userPassword attribute.

•   Object owners have write access to the shadowLastChange attribute, and write-only access to the userPassword attribute in their own LDAP objects to allow password changes.

•   Object owners have write access to the mobile, carLicense, homePhone and homePostalAddress attributes in their own objects. These attributes cannot be seen by other unprivileged users.

  Authenticated users

DN  users

Test RDN
    person_rdn

•   Authenticated users have read-only access to most of the LDAP directory, depending on the restrictions defined by the ACL rules.

  Anonymous users

DN  anonymous

•   Anonymous users can authenticate to the LDAP directory via the userPassword attribute.

•   No other access is granted to anonymous users.

  Current issues with the default ACL

•   LDAP editors and account administrators can modify or remove accounts of the LDAP administrators, thus denying access to the service. There should be a way to protect certain user objects based on the member attribute of a specific groupOfNames LDAP object.

•   users can create new LDAP objects with object classes or attributes that they don't have access to (for example, UNIX attributes). There should be a server-side way to restrict object creation to allowed object classes only.

  Access Control List tests and validation

Due to its complexity, LDAP access control policy requires extensive testing to ensure that there are no missed loopholes or unintended data disclosures. With OpenLDAP service, the slapacl(8) <https://manpages.debian.org/slapacl(8)> command can be used to test the ACL rules against existing or simulated LDAP objects.

The slapacl command has to be executed with full access to the cn=config database, which means running it on the OpenLDAP server itself, as the openldap UNIX account. Unfortunately, slapacl command does not support any test definition files and the tests have to be applied using command line arguments.

To make ACL testing more reliable and easier to use, the debops.slapd Ansible role implements a custom template and a set of variables which can be used to generate a shell script, by default located at /etc/ldap/slapacl-test-suite. This script can then be executed to perform various ACL tests and report the results. The test suite is executed by Ansible on each run of the debops.slapd role to ensure that any changes to the ACL rules are immediately tested.

WARNING: The test suite shell script is executed by Ansible as the openldap UNIX account and has full access to the OpenLDAP environment, database and other files owned by the service. The generated test cases are not validated against any command injection attacks through the Ansible variables and could be used to take over the OpenLDAP service. Ensure that the access to the OpenLDAP servers and the Ansible inventory used to configure them is restricted.

To generate the test suite script and perform the tests using Ansible, you can execute the debops.slapd playbook with a special tag:

debops run service/slapd -l <host> -t role::slapd:slapacl

This command will regenerate the script and execute it to check the ACL rules.

The test script is designed with a large number of ACL test cases in mind (200+). By default it only outputs the details about failed test cases, to make them easier to spot on the command line, or in Ansible output. To see the full report of the various tests, you need to redirect the standard output to another command, for example:

/etc/ldap/slapacl-test-suite | more

The output of the failed test cases is sent to the standard error. You can redirect the failed test cases to a file for further analysis:

/etc/ldap/slapacl-test-suite 2> /tmp/slapd-acl-errors

In this case the script will print the . to indicate successful tests and X for failed tests on its standard output.

The default set of test cases is designed to test validity of the default LDAP Access Control List rules defined by the debops.slapd role and will be expanded over time to cover more test cases. If you modify the default ACL rules, you might also need to update the existing test cases to conform to the new rules. Alternatively, the execution of the test script by Ansible can be disabled temporarily or permanently if you don't want your new ACL rules to fail the Ansible execution during development.

Some of the test cases require real, existing LDAP objects to execute properly. The debops.slapd role provides the slapd__slapacl_default_tasks YAML list (and additional lists for use in the Ansible inventory) that contains definitions of various LDAP objects like unprivileged and privileged user accounts. To enable the more extensive tests, you need to set the slapd__slapacl_test_objects_state variable to present which will tell the role to add the needed test objects in the directory. This functionality is meant to be used in a development environment to not interfere with the production database.

  References

•   OpenLDAP Access Control <https://www.openldap.org/doc/admin24/access-control.html> documentation

•   OpenLDAP-DIT <https://wiki.ubuntu.com/OpenLDAP-DIT> page on Ubuntu Wiki, along with the project page <https://launchpad.net/openldap-dit> on Launchpad

•   Keeping your sanity while designing LDAP ACLs <https://medium.com/@moep/keeping-your-sanity-while-designing-openldap-acls-9132068ed55c>

•   Basic ACL configuration <http://www.zytrax.com/books/ldap/ch5/step2.html#step2> in Zytrax LDAP guide

BACKUP AND RESTORE PROCEDURES

Here you can find information about the backup procedure for the OpenLDAP service configured by the debops.slapd Ansible role as well as tips about restoring the backed-up data.

  Backup snapshots

The debops.slapd role installs the slapd-snapshot shell script that can be used to create periodic LDIF snapshots of the databases used by the OpenLDAP service.

By default, three cron jobs will be configured by the role to create daily (7 days), weekly (4-5 weeks) and monthly (12 months) snapshots of all OpenLDAP databases found (ignoring the "frontend" database or any databases with the olcReadOnly attribute present). This can be controlled using the slapd__snapshot_deploy_state and slapd__snapshot_cron_jobs default variables. Alternatively, the periodic cron jobs can be disabled, and the slapd-snapshot script can be executed as root to create current snapshot of the OpenLDAP databases in LDIF format; previous snapshots are automatically removed in this case with assumption that they have been transferred to a remote storage by other means.

The slapd-snapshot script will enable and disable read-only mode for each database, with some caveats. The cn=config database is not backed-up read-only, because read-only mode cannot be disabled without stopping the service. The databases that have the olcReadOnly attribute defined (enabled or disabled) are not backed up automatically.

The snapshots are stored in the /var/backups/slapd/ directory as compressed tarballs. After finishing the snapshot, the slapd-snapshot script will change ownership of the created tarballs to the backup:backup UNIX account and group. This account can then encrypt the tarballs via its own set of scripts, using GnuPG asymmetric encryption, to prepare them to be sent to a remote location (this functionality is not implemented by the debops.slapd role). The slapd-snapshot script will automatically remove periodic *.gz.asc or *.gz.gpg files before creating new iterations to preserve disk space.

  Restore procedure

The LDAP server has crashed and burned, but you have the backup snapshots available, how to restore them? The approach described here assumes that all OpenLDAP server configuration was performed using the debops.slapd role and is still available in the inventory; only the backup of the main LDAP databass is needed.

NOTE: This procedure can also be used to migrate LDAP directory between OpenLDAP installations.

  tl;dr version

Set up a new OpenLDAP cluster, select one host as the restore point.

scp data.ldif slapd-host:
ssh slapd-host
sudo systemctl stop slapd.service
sudo rm -rf /var/lib/ldap/*
sudo slapadd -F /etc/ldap/slapd.d -n 1 -l data.ldif -w  # cluster
sudo slapadd -F /etc/ldap/slapd.d -n 1 -l data.ldif     # standalone
sudo chown openldap:openldap /var/lib/ldap/*
sudo systemctl start slapd.service

After a while, data should be synchronized between all nodes in the cluster.

  Detailed explanation

1.  Create a new OpenLDAP server and configure it using DebOps. If it's a cluster of servers, make sure that after applying the configuration the synchronization happens correctly, for example by adding and removing an OpenLDAP object on one host, and noticing it appearing and disappearing on the other(s).

2.  Select one host as the backup importer. Copy the contents of the main LDAP database to it via scp to have the data available on the host locally.

3.  Stop the OpenLDAP service on the host:

    systemctl stop slapd.service

4.  Remove the existing database files in the /var/lib/ldap/ directory and any other auxiliary directories, if you use multiple databases/DITs.

5.  Import the backed up LDIF dataset to the OpenLDAP server using the slapadd command. If you use multiple databases, make sure that you use the correct database number during import.

    WARNING: If you use a clustered OpenLDAP setup, use the -w flag to ensure that the imported LDAP objects have the correct attributes to override the synchronization data from other cluster nodes. Otherwise, the import node will have its data wiped after synchronizing with the other cluster nodes.

    Import of the main database in clustered setup:

    slapadd -F /etc/ldap/slapd.d -n 1 -l data.ldif -w

    Import of the main database in standalone setup:

    slapadd -F /etc/ldap/slapd.d -n 1 -l data.ldif

6.  Set the correct UNIX account and UNIX group ownership of the OpenLDAP database(s), for example:

    chown openldap:openldap /var/lib/ldap/*

7.  Start the OpenLDAP service:

    systemctl start slapd.service
    The OpenLDAP cluster should now synchronize new LDAP objects imported into the LDAP directory.

GUIDE: N-WAY MULTI-PROVIDER REPLICATION

N-Way Multi-Provider replication <https://www.openldap.org/doc/admin24/replication.html#N-Way%20Multi-Provider%20Replication> (previously referred to in the OpenLDAP documentation as N-Way Multi-Master replication) can be used to create and manage multiple LDAP directory servers that share the data between them. This configuration can provide better LDAP directory service availability via host and database redundancy, failover capability and easy sharing of the LDAP database contents across multiple sites.

  Introduction

There are arguments for and against this setup (see the OpenLDAP documentation linked above). N-Way Multi-Provider replication is a good solution for the core infrastructure to provide redundancy and failover; it might be a wrong approach for providing LDAP directory services "closer" to the end-users, for this you might want to look into other OpenLDAP replication topologies <https://www.openldap.org/doc/admin24/replication.html> (check out the Zytrax guide about OpenLDAP replication <http://www.zytrax.com/books/ldap/ch7/> as well).

This guide shows how to implement N-Way Multi-Provider replication of the LDAP directory using DebOps. You might want to check the example configuration <https://www.openldap.org/doc/admin24/replication.html#N-Way%20Multi-Master> in the OpenLDAP documentation for comparison. There's also a Zytrax guide <http://www.zytrax.com/books/ldap/ch7/#ol-syncrepl-mm> with a Multi-Master replication example.

  Requirements

•   At least 2 Debian hosts configured by DebOps/Ansible, there can be more hosts included in the cluster.

•   All hosts have proper time synchronization using NTP.

•   All hosts can reach each other via DNS hostnames and the firewall + TCP Wrappers access has been allowed using the slapd__*_allow variables.

  DNS configuration

For flexibility, the LDAP directory cluster will be reachable by the clients using CNAME and DNS SRV Records. Here's an example dnsmasq(8) <https://manpages.debian.org/dnsmasq(8)> configuration for 3 OpenLDAP cluster hosts and 1 OpenLDAP test host used for development:

host-record = slapd-server1.example.org,192.0.2.1
host-record = slapd-server2.example.org,192.0.2.2
host-record = slapd-server3.example.org,192.0.2.3
host-record = slapd-tests.example.org,192.0.2.4
cname = ldap1.example.org,slapd-server1.example.org
cname = ldap2.example.org,slapd-server2.example.org
cname = ldap3.example.org,slapd-server3.example.org
srv-host = _ldap._tcp.example.org,ldap1.example.org,389,10,0
srv-host = _ldap._tcp.example.org,ldap2.example.org,389,20,0
srv-host = _ldap._tcp.example.org,ldap3.example.org,389,30,0
cname = ldap.example.org,slapd-server1.example.org
cname = ldap-test.example.org,slapd-tests.example.org

For a further explanation of this example configuration, see Example SRV Configuration using CNAMEs.

Keep in mind that in the replicated cn=config configuration (see below) you should use the real server hostnames, and not the CNAME records, to avoid possible issues when cluster nodes are replaced.

  Ansible inventory configuration

Each OpenLDAP database has its own replication configuration. For maximum consistency, the cn=config database should also be replicated, which means that each cluster node has to be able to work using the same configuration options.

In the Ansible inventory, you should create an Ansible inventory group for the OpenLDAP cluster, let's call it [slapd_masters_cluster1], just in case that in the future there will be multiple OpenLDAP clusters. You could also create a separate OpenLDAP server not connected to the main cluster for development and testing.

[debops_all_hosts]
slapd-server1    ansible_host=slapd-server1.example.org
slapd-server2    ansible_host=slapd-server2.example.org
slapd-server3    ansible_host=slapd-server3.example.org
slapd-tests      ansible_host=slapd-tests.example.org
[debops_service_slapd]
slapd-server1
slapd-server2
slapd-server3
slapd-tests
[slapd_masters_cluster1]
slapd-server1
slapd-server2
slapd-server3

  OpenLDAP configuration tasks

The specific OpenLDAP tasks that are used to configure the replication between the cluster nodes will be stored in the ansible/inventory/group_vars/slapd_masters_cluster1/slapd.yml inventory file.

This is an example OpenLDAP configuration for 3 nodes, each replicating the cn=config database and the main database:

---
slapd__group_allow: [ '192.0.2.0/24' ]
slapd__cluster_tasks:
  - name: 'Configure ServerID values'
    dn: 'cn=config'
    attributes:
      olcServerID:
        - '001 ldap://slapd-server1.example.org/'
        - '002 ldap://slapd-server2.example.org/'
        - '003 ldap://slapd-server3.example.org/'
    state: 'exact'
  - name: 'Configure replication of the cn=config database'
    dn: 'olcDatabase={0}config,cn=config'
    attributes:
      olcSyncrepl:
        - |-
          {0}rid=000
             provider="ldap://slapd-server1.example.org/"
             binddn="{{ slapd__config_rootdn }}"
             bindmethod="simple"
             credentials="{{ slapd__config_rootpw }}"
             searchbase="cn=config"
             type="refreshAndPersist"
             retry="20 5 300 +"
             timeout="4"
             starttls="critical"
             tls_cacert="{{ slapd__tls_ca_certificate }}"
             tls_cert="{{ slapd__tls_certificate }}"
             tls_key="{{ slapd__tls_private_key }}"
             tls_cipher_suite="{{ slapd__tls_cipher_suite }}"
        - |-
          {1}rid=001
             provider="ldap://slapd-server2.example.org/"
             binddn="{{ slapd__config_rootdn }}"
             bindmethod="simple"
             credentials="{{ slapd__config_rootpw }}"
             searchbase="cn=config"
             type="refreshAndPersist"
             retry="20 5 300 +"
             timeout="4"
             starttls="critical"
             tls_cacert="{{ slapd__tls_ca_certificate }}"
             tls_cert="{{ slapd__tls_certificate }}"
             tls_key="{{ slapd__tls_private_key }}"
             tls_cipher_suite="{{ slapd__tls_cipher_suite }}"
        - |-
          {2}rid=002
             provider="ldap://slapd-server3.example.org/"
             binddn="{{ slapd__config_rootdn }}"
             bindmethod="simple"
             credentials="{{ slapd__config_rootpw }}"
             searchbase="cn=config"
             type="refreshAndPersist"
             retry="20 5 300 +"
             timeout="4"
             starttls="critical"
             tls_cacert="{{ slapd__tls_ca_certificate }}"
             tls_cert="{{ slapd__tls_certificate }}"
             tls_key="{{ slapd__tls_private_key }}"
             tls_cipher_suite="{{ slapd__tls_cipher_suite }}"
      olcMirrorMode: 'TRUE'
    state: 'exact'
  - name: 'Configure time and size limits in the main database'
    dn: 'olcDatabase={1}mdb,cn=config'
    attributes:
      olcLimits:
        - |-
          dn.exact="{{ slapd__data_rootdn }}"
          time="unlimited"
          size="unlimited"
    ordered: True
    state: 'exact'
  - name: 'Configure replication of the main database'
    dn: 'olcDatabase={1}mdb,cn=config'
    attributes:
      olcSyncrepl:
        - |-
          {0}rid=010
             provider="ldap://slapd-server1.example.org/"
             binddn="{{ slapd__data_rootdn }}"
             bindmethod="simple"
             credentials="{{ slapd__data_rootpw }}"
             searchbase="{{ slapd__basedn }}"
             type="refreshAndPersist"
             retry="20 5 300 +"
             timeout="4"
             starttls="critical"
             tls_cacert="{{ slapd__tls_ca_certificate }}"
             tls_cert="{{ slapd__tls_certificate }}"
             tls_key="{{ slapd__tls_private_key }}"
             tls_cipher_suite="{{ slapd__tls_cipher_suite }}"
        - |-
          {1}rid=011
             provider="ldap://slapd-server2.example.org/"
             binddn="{{ slapd__data_rootdn }}"
             bindmethod="simple"
             credentials="{{ slapd__data_rootpw }}"
             searchbase="{{ slapd__basedn }}"
             type="refreshAndPersist"
             retry="20 5 300 +"
             timeout="4"
             starttls="critical"
             tls_cacert="{{ slapd__tls_ca_certificate }}"
             tls_cert="{{ slapd__tls_certificate }}"
             tls_key="{{ slapd__tls_private_key }}"
             tls_cipher_suite="{{ slapd__tls_cipher_suite }}"
        - |-
          {2}rid=012
             provider="ldap://slapd-server3.example.org/"
             binddn="{{ slapd__data_rootdn }}"
             bindmethod="simple"
             credentials="{{ slapd__data_rootpw }}"
             searchbase="{{ slapd__basedn }}"
             type="refreshAndPersist"
             retry="20 5 300 +"
             timeout="4"
             starttls="critical"
             tls_cacert="{{ slapd__tls_ca_certificate }}"
             tls_cert="{{ slapd__tls_certificate }}"
             tls_key="{{ slapd__tls_private_key }}"
             tls_cipher_suite="{{ slapd__tls_cipher_suite }}"
      olcMirrorMode: 'TRUE'
    state: 'exact'

As a convenience, the above configuration is available as a separate file (examples/multi-master-replication.yml) in the debops.slapd role documentation stored in the DebOps monorepo.

  Configuration notes

•   The cluster configuration is defined in a separate variable, slapd__cluster_tasks, which will be included after the ACL tasks, but before the directory structure tasks. This should ensure that the cluster configuration is performed before initial directory structure creation.

•   The X-ORDERED LDAP extension (i.e. the ordered parameter) is not used here because the tasks contain attributes (e.g. olcMirrorMode) which are incompatible with X-ORDERED syntax and which have to be activated at the same time.

•   The olcServerID values and rid= values are unrelated to each other. Each OpenLDAP server needs a unique ServerID.

•   The rid= values need to be numbers from 000 to 999. A suggested way of using them is to use the first digit as a synchronization group (0 for multi-master nodes, 1 for normal Sync Replication, etc.), second digit as the database number (0 for the cn=config database, 1 for the main database, and so on), and third digit for the OpenLDAP server instance, starting from 0. You might want to design your own scheme of course.

•   The Sync Replication security depends on X.509 certificates and PKI. The debops.slapd role depends on the PKI environment managed by the debops.pki Ansible role to manage the certificates. Because the configuration will be shared between all of the masters in the cluster, they should use a similar configuration, including the name of the PKI realm used by the role.

  Deployment

After the configuration is in the Ansible inventory, you should apply it on all OpenLDAP servers in the cluster at once:

debops run service/slapd -l slapd_masters_cluster1 --diff

When the deployment is complete, the OpenLDAP configuration should be defined on the group level instead of on an individual host level in the inventory. The OpenLDAP servers will synchronize the configuration between the nodes in both cases, but it might be confusing if you see configuration defined for one host suddenly "show up" on other nodes in the cluster.

Some of the OpenLDAP configuration options, for example module loading and overlay setup should be done on only one node of the cluster at a time; the changes will be propagated automatically. Otherwise you will notice that during an Ansible run one or more nodes have finished with an error. This happens when the role tries to enable a functionality on multiple OpenLDAP cluster nodes at once, and the second time gets rejected by the cluster.

Remember that only the OpenLDAP configuration is synchronized automatically. Other Ansible roles involved in the debops.slapd configuration, for example firewall or TCP Wrappers configuration, still need to be applied on all hosts in the OpenLDAP cluster.

DEFAULT VARIABLE DETAILS

Some of the debops.slapd default variables have more extensive configuration than simple strings or lists, here you can find documentation and examples for them.

  slapd__tasks

The slapd__*_tasks variables define a custom set of ldap_entry and ldap_attrs Ansible module tasks that will be executed against the slapd cn=config configuration database, in the specified order. These variables provide an easy and intuitive way to manage the OpenLDAP configuration database, with some specific caveats due to how the cn=config configuration works. See the definition of the parameters for more details.

  Examples

Specify root credentials of the cn=config database. This entry ensures that only the specific instance of the object attributes exists.

slapd__tasks:
  - name: 'Set cn=config root credentials'
    dn: [ 'olcDatabase={0}config', 'cn=config' ]
    attributes:
      olcRootDN: 'cn=admin,cn=config'
      olcRootPW: 'secret'
    state: 'exact'
    no_log: '{{ debops__no_log | d(True) }}'

Ensure that specific object attributes in the main database are indexed by the LDAP directory. This entry will add the specified indexes if they are not configured, other existing sets of indexes will be left unchanged.

slapd__tasks:
  - name: 'Configure main database indexes'
    dn: [ 'olcDatabase={1}mdb', 'cn=config' ]
    attributes:
      olcDbIndex:
        - 'cn,uid eq'
        - 'member,memberUid eq'
        - 'objectClass eq'

Enable Sync Provider overlay <http://www.zytrax.com/books/ldap/ch6/syncprov.html> for the main database. This entry creates a new LDAP object if it doesn't exist, but it will not modify existing object or its attributes.

slapd__tasks:
  - name: 'Enable sync provider overlay in main database'
    dn: 'olcOverlay={0}syncprov,olcDatabase={1}mdb,cn=config'
    objectClass: [ 'olcOverlayConfig', 'olcSyncProvConfig' ]
    attributes:
      olcOverlay: '{0}syncprov'

Define a basic Access Control List, based on the example security policy <http://www.zytrax.com/books/ldap/ch5/step2.html#step2>. This is an example of an object with X-ORDERED <https://tools.ietf.org/html/draft-chu-ldap-xordered-00> type attributes, which will be added automatically by the ldap_attrs module included in DebOps.

slapd__tasks:
  - name: 'Set Access Control List in the main database'
    dn: 'olcDatabase={1}mdb,cn=config'
    attributes:
      olcAccess:
        - |-
          to attrs="userPassword"
          by self      write
          by anonymous auth
          by group.exact="cn=IT People,ou=Groups,dc=example,dc=com"
                       write
          by *         none
        - |-
          to attrs="carLicense,homePostalAddress,homePhone"
          by self       write
          by group.exact="cn=HR People,ou=Groups,dc=example,dc=com"
                        write
          by *          none
        - |-
          to *
          by self       write
          by group.exact="cn=HR People,ou=Groups,dc=example,dc=com"
                        write
          by users      read
          by *          none
    ordered: True
    state: 'exact'

  Syntax

The slapd__default_tasks, slapd__tasks, slapd__group_tasks and slapd__host_tasks define a list of YAML dictionaries, each list entry defines a ldap_entry or ldap_attrs task to perform on the local OpenLDAP cn=config database. The variables are merged together in the order specified by the slapd__combined_tasks variable. The entries with the same name parameter will affect each other, replacing the previously defined "instance" of a given task - this can be used to change previously defined tasks conditionally.

The list of task parameters supported by the role:

name
    Required. The name of a given task, displayed during Ansible execution. It's an equivalent of the name keyword in Ansible tasks lists. Its value does not affect the actions performed in the cn=config database. Entries with the same name are merged together.

dn  Required. The Distinguished Name of the LDAP directory object in the cn=config database which will be configured by a given entry. The value can be specified as a string or a YAML list, which will be joined by commas.

    This parameter is case-sensitive, if you use a wrong case here, the LDAP database will still most likely accept the configuration, but the task list will not be idempotent. When that happens, check the case of the DN value.

    This parameter can contain LDAP object names that use the X-ORDERED type syntax. The LDAP database will accept new objects that omit the X-ORDERED syntax prefix, but subsequent executions of the role can cause errors due to incorrect DN name. It's best to specify the object prefix number directly from the start. Remember that the LDAP database can modify the X-ORDERED prefix number on any modification of the list of objects; you should verify the current prefix numbering before applying any changes.

objectClass
    Optional. Specify a name or a YAML list of the LDAP Object Classes which should be used to define a new LDAP directory object.

    If this parameter is specified, the ldap_entry module will be used to perform the operation instead of ldap_attrs module. The ldap_entry Ansible module will not modify the attributes of any existing LDAP directory objects, you need to use a separate configuration entry to do that, which does not specify this parameter.

    This parameter is case-sensitive, if you use a wrong case here, the LDAP database will still most likely accept the configuration, but the task list will not be idempotent. When that happens, check the case of the objectClass value(s). The parameter name is case-sensitive as well.

attributes
    Required. YAML dictionary which defines the attributes and their values of a given LDAP object. Each dictionary key is a case-sensitive name of an attribute, and the value is either a string, or a list of strings, or a list of YAML text blocks. If list is used for the values, multiple attribute entries will be created automatically.

    If you create configuration entries with the same name parameter, the attributes parameter will replace entirely the same parameter defined in previous entries on the list. This is not the case in the LDAP directory itself, where multiple separate configuration entries can define the same objects and their attributes multiple times, as long as the state is not specified or is set as present. To ensure that a given set of attributes is specified only once in the LDAP database, you MUST define the state parameter with the exact value.

    The attribute names are case-sensitive, if you use a wrong case here, the LDAP database will still most likely accept the configuration, but the task list will not be idempotent. When that happens, check the case of the attribute names.

    The attributes can contain lists that use the X-ORDERED type syntax. The LDAP database will accept new attribute values that omit the X-ORDERED syntax prefix and a new prefix number will be assigned to them automatically by the LDAP directory. Subsequent executions of the role can create duplicate attribute values, if the prefix number is not specified. It's best to specify the attribute prefix number directly from the start. Remember that the LDAP database can modify the X-ORDERED prefix number on any modification of the list of attributes; you should verify the current prefix numbering before applying any changes.

ordered
    Optional, boolean. If defined and True, the ldap_attrs Ansible module will automatically add the X-ORDERED index numbers to lists of values in all attributes of a current task. This extension is used in the OpenLDAP cn=config configuration database to define order of object attributes which are normally unordered.

    The most prominent use of the X-ORDERED extension is in the olcAccess attribute, which defines the LDAP Access Control List. This attribute should be defined in a separate LDAP task, so that only its values will have the X-ORDERED index numbers inserted. Existing index values will be removed and replaced with the correct ordering defined by the YAML list.

state
    Optional. Possible values:
    State | Description
    present | Default. The role will ensure that a given configuration entry is present in the LDAP directory. There might be more more than one copy of a given entry present at the same time. To avoid creating duplicate entries, use exact instead of present.
    exact | The role will ensure that only the specified set of attributes of a given LDAP object is defined in the LDAP database. You MUST use this parameter when X-ORDERED type attributes are configured, otherwise the role cannot guarantee that only the specified set of attribute values, as well as their specified order, is defined in a given LDAP object.
    absent | The specified attributes of a given LDAP object will be removed.
    init | The role will prepare a task entry configuration but it will not be active - this can be used to activate prepared entries conditionally.
    ignore | A given configuration entry will not be evaluated by the role. This can be used to conditionally enable or disable entries.

entry_state
    Optional. This parameter should be present only if the entire LDAP object entry is to be removed. Set the entry state to absent to remove it.

no_log
    Optional, boolean. If True, a given task output will not be recorded to avoid emitting sensitive information like passwords. If not specified or False, the task will be recorded and logged.

run_once
    Optional, boolean. If defined and True, a given LDAP task will be executed only one time when the role is applied on multiple remote hosts at once. This might be important in cases where the LDAP directory is replicated, or values from different remote hosts can result in the same LDAP objects, e.g. objects with X-ORDERED index numbers, like LDAP schemas.

  slapd__slapacl_tests

The slapd__slapacl_*_tests variables define a set of slapacl(8) <https://manpages.debian.org/slapacl(8)> tests to perform against the OpenLDAP ACL configuration. The tests are generated and stored in a shell script which is then executed against the local slapd instance.

See LDAP Access Control List for more details about the default set of OpenLDAP Access Control Lists defined by the debops.slapd role. The Access Control List tests and validation section has more details about the ACL test infrastructure built into the debops.slapd role.

  Examples

Check the access rules of a given LDAP object. Non-existent objects don't generate an error. Existing objects will generate a list of ACL permissions. To see the rules, pipe the script output to a different command or to a file.

slapd__slapacl_tests:
  - name: 'Check access to ou=People,{{ slapd__basedn }}'
    dn: 'ou=People,{{ slapd__basedn }}'

Check the access rules of a given LDAP object by another LDAP object. To see the rules, pipe the script output to a different command or to a file.

slapd__slapacl_tests:
  - name: 'Check access to ou=People,{{ slapd__basedn }} by user'
    dn: 'ou=People,{{ slapd__basedn }}'
    authdn: 'uid=user,ou=People,{{ slapd__basedn }}'

Check the access rules of a given LDAP object by a SASL identity. To see the rules, pipe the script output to a different command or to a file.

slapd__slapacl_tests:
  - name: 'Check access to ou=People,{{ slapd__basedn }} by SASL user'
    dn: 'ou=People,{{ slapd__basedn }}'
    uid: 'user'

Test the access policy of a specific LDAP object, accessed by anonymous user. Failed policy test will result in an error.

slapd__slapacl_tests:
  - name: 'Test access to ou=People,{{ slapd__basedn }} by anonymous user'
    dn: 'ou=People,{{ slapd__basedn }}'
    authdn: ''
    policy: 'none(=0)'

Test the access policy of a specific LDAP object attribute, accessed by another LDAP entry. Failed policy test will result in an error.

slapd__slapacl_tests:
  - name: 'Test write access to ou=People,{{ slapd__basedn }} by a service'
    dn: 'ou=People,{{ slapd__basedn }}'
    authdn: 'uid=service,ou=Services,{{ slapd__basedn }}'
    query: 'entry/write'
    policy: 'deny'

Test the access policy of a specific LDAP object attribute, accessed on behalf of a LDAP user by a LDAP service entry via SASL proxy authorization. Failed policy test will result in an error.

slapd__slapacl_tests:
  - name: 'Test write access to ou=People,{{ slapd__basedn }} by another user'
    dn: 'ou=People,{{ slapd__basedn }}'
    authdn: 'uid=service,ou=Services,{{ slapd__basedn }}'
    authzid: 'dn:uid=user,ou=People,{{ slapd__basedn }}'
    query: 'entry/write'
    policy: 'allow'

Test the access rules of a LDAP object, accessed by another LDAP entry. Failed rule test will result in an error.

slapd__slapacl_tests:
  - name: 'Test access rules for ou=People,{{ slapd__basedn }} by user'
    dn: 'ou=People,{{ slapd__basedn }}'
    authdn: 'uid=user,ou=People,{{ slapd__basedn }}'
    queries:
      - name: 'description/write'
        result: 'write access to description: ALLOWED'
      - name: 'entry'
        result: 'entry: manage(=mwrscxd)'

  Syntax

The ACL tests are defined using a list of YAML dictionaries. Each dictionary describes one test using specific parameters:

name
    Required. The descriptive name of the test, not used otherwise. Each test has to have an unique name parameter. The configuration entries with the same name are merged together and can affect each other in order of appearance.

dn  Required. The Distinguished Name of the LDAP object to test.

authdn
    Optional. The Distinguished Name of the LDAP object which is used as the identity which accesses the tested object.

uid Optional. The SASL id (login name) which will be mapped to a LDAP object using the configured authz-regexp rules configured in the OpenLDAP server. This LDAP object will be used as the identity which accesses the tested object.

authzid
    Optional. The SASL proxy authorization <https://www.openldap.org/doc/admin24/sasl.html#SASL%20Proxy%20Authorization> id (login name) prefixed with u:, which will be mapped to a LDAP object using the authz-regexp rules configured in the OpenLDAP server, or Distinguished Name of an LDAP object prefixed with dn:. The authdn or uid LDAP object will use the authzid object to access the test subject.

options
    Optional. List of addiontal slapacl(8) <https://manpages.debian.org/slapacl(8)> option which should be used for a particular test, for example:

    options: [ 'ssf="128"' ]

    The option values are not quoted in the script template and need to be quoted in the parameter if necessary.

dry_run
    Optional, boolean. If True, instead of using a real dn LDAP object from the directory, the slapacl command will generate a fake DN entry with no attributes which can be used to simulate non-existent LDAP objects. This is required by certain backend databases, for example cn=config.

    ACL rules tested against the attributes of the fake DN entry might not be accurate, use this only with tests against entries themselves.

debug
    Optional. Enable debugging and set the desired log level as a string. Supported log levels can be seen by running the slapd -d ? command, or can be found in the slapd-config(5) <https://manpages.debian.org/slapd-config(5)> manual page in the olcLogLevel option documentation. In the case of the ACL tests, the useful log level can be acl.

state
    Optional. If not specified or present, a given test will be included in the generated script. If absent, the test will not be included in the script. If ignore, a given configuration entry will not be evaluated by the role.

    If init, a given configuration entry will be prepared, but not actually included in the generated script. Such entries can then be enabled conditionally by another entry with the same name parameter.

comment
    Optional. String or YAML text block with comments about a given tests. The comments will be included in the actual script and not displayed otherwise.

query
    Optional. Specify the attribute, optionally an access level and a value to which a given dn LDAP object should have access, for example cn/write or o/write:Example Org. You can also specify entry, to test access to a given LDAP object without specific attributes (this is the default if query parameter is not specified) or children to specify access to child objects of a given LDAP object.

policy
    Optional. Specify the access policy of a given ACL test. If a given test checks particular attribute with a specific access level, for example cn/write, you can define the policy using keywords allow, allowed, accept, grant, permit to allow access, or disallow, denied, reject, revoke, deny to deny access to a given attribute.

    If the test checks access policy for an entry, children, or if a query parameter is not specified, the policy parameter needs to have an exact permission set for the script to successfully compare the slapacl command output with the desired policy. For example, the read access needs to be specified as read(=rscxd). Check the script output to see the expected value if you are unsure what needs to be set as the policy value.

queries
    Optional. A list of YAML dictionaries, each dictionary defines an attribute test similar to the query/policy combination. This parameter can be used to define more complex ACL rule test cases where different object attributes have different access policies. Tests defined using the queries parameter use the diff command to compare the results.

    The queries parameters from tests with the same name parameters are merged together, and their entries can modify each other when they have the same name value.

    Each test case is defined using specific parameters:

name
    Required. The attribute to test, with optional access level and value, similar to the query parameter, for example ou/write.

result
    Optional. Specify the exact line that contains the result of a given test case. You can inspect the output of the test script to find what needs to be defined here.

state
    Optional. If not defined or present, a given test case will be included in the test script. If absent, a given test case will not be included in the test script.

AUTHOR
Maciej Delmanowski

COPYRIGHT
2014-2024, Maciej Delmanowski, Nick Janetakis, Robin Schneider and others