OpenLDAP Password Policy overlay (ppolicy)

OpenLDAP has a dynamically loadable module which can enforce password policies. It allows to define policies for the userPassword attribute. Policies can define the maximum login attempts with the wrong password, maximum age of a password and many more.

Here is a short introduction into this module. If you want to read about it in detail, see the link collection at the end of this page.

Note: The connection parameters and DN parameters deeply depend on your setup, the examples here need to be adjusted to your setup.

Configuration of the ppolicy overlay

The basic configuration depends on your OpenLDAP version. Newer versions store their configuration in a so-called Online Configuration Database (OLC), older ones use a configuration file called slapd.conf

OpenLDAP with OLC

  • Load the ppolicy schema into OLC: ldapmodify -D "cn=root,cn=config" -W -a -f /etc/openldap/schema/ppolicy.ldif
  • Load the module: ldapmodify -D "cn=root,cn=config" -W -a -f ppolicymodule.ldif
dn: cn=module{0},cn=config
objectClass: olcModuleList
cn: module{0}
  • Configure ppolicy overlay: ldapmodify -D "cn=root,cn=config" -W -a -f ppolicyoverlay.ldif
dn: olcOverlay=ppolicy,olcDatabase={1}bdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcPPolicyConfig
olcOverlay: ppolicy
olcPPolicyDefault: cn=passwordDefault,ou=Policies,dc=mydomain,dc=tld
olcPPolicyHashCleartext: FALSE
olcPPolicyUseLockout: FALSE
olcPPolicyForwardUpdates: FALSE

OpenLDAP with slapd.conf

If you have an older version of OpenLDAP, the configuration goes into slapd.conf:

#-- Load schema
include         /etc/openldap/schema/ppolicy.schema

#-- Load module

The next snippet should come somewhere after the database definition:

#-- Load overlay
overlay ppolicy
ppolicy_default "cn=passwordDefault,ou=Policies,dc=mydomain,dc=tld"

This means the default policy is located under cn=passwordDefault,ou=Policies,dc=mydomain,dc=tld

Definition of a password policy

In the overlay configuration we specified the default policy, so we add it now using the following LDIF:

dn: ou=Policies,dc=mydomain,dc=tld
ou: Policies
objectClass: organizationalUnit

dn: cn=passwordDefault,ou=Policies,dc=mydomain,dc=tld
objectClass: pwdPolicy
objectClass: person
objectClass: top
cn: passwordDefault
sn: passwordDefault
pwdAttribute: userPassword
pwdCheckQuality: 0
pwdMinAge: 0
pwdMaxAge: 0
pwdMinLength: 8
pwdInHistory: 5
pwdMaxFailure: 3
pwdFailureCountInterval: 0
pwdLockout: TRUE
pwdLockoutDuration: 0
pwdAllowUserChange: TRUE
pwdExpireWarning: 0
pwdGraceAuthNLimit: 0
pwdMustChange: FALSE
pwdSafeModify: FALSE

All these parameters are described in detail at Chapter 6 OpenLDAP password policy overlay / pwdPolicy ObjectClass and Attributes.

This policy applies to all userPassword attributes. If an object needs a different policy, just define the differing policy under another name and reference the policy with the pwdPolicySubentry attribute. Example:

dn: cn=My User,ou=People,dc=mydomain,dc=tld
changetype: modify
add: pwdPolicySubentry
pwdPolicySubentry: cn=passwordSpecial,ou=Policies,dc=mydomain,dc=tld

Usage and behaviour

Query all locked accounts

If an object has the pwdAccountLockedTime attribute: it is locked since then. Simply issue the following query:
ldapsearch <MYCONNECTIONPARAMS> -b "ou=People,dc=mydomain,dc=tld" "pwdAccountLockedTime=*" pwdAccountLockedTime

Unlock an account

There are two variants. For the first one you simply delete the pwdAccountLockedTime attribute which unlocks the account immediately:

dn: cn=My User,ou=People,dc=mydomain,dc=tld
changetype: modify
delete: pwdAccountLockedTime

The second variant adds the attribute pwdReset which basically means: The user can only login again after changing it's password:

dn: cn=My User,ou=People,dc=mydomain,dc=tld
changetype: modify
add: pwdReset
pwdReset: TRUE

If the user tries other operations than changing its password, the OpenLDAP server responds with bind: Operations are restricted to bind/unbind/abandon/StartTLS/modify password Changing an LDAP password can be done f.e. with the ldappasswd tool:

ldappasswd <MYCONNECTIONPARAMS> -D "cn=My User,ou=People,dc=mydomain,dc=tld" -W -S "cn=My User,ou=People,dc=mydomain,dc=tld"

Behaviour of some policy settings

A short overview of how some of the policies behave (not all covered here):


Result: Constraint violation (19)
Additional info: Password is too young to change


ldap_bind: Invalid credentials (49)
in the logfile: ppolicy_bind: Entry cn=My User,ou=People,dc=mydomain,dc=tld has an expired password: 0 grace logins


in the log: ppolicy_bind: Entry cn=My User,ou=People,dc=mydomain,dc=tld has an expired password: 1 grace logins


Result: Constraint violation (19)
Additional info: Password is in history of old passwords


Result: Insufficient access (50)
Additional info: User alteration of password is not allowed

Storage location of the policy data

Policy data (f.e. number of failed login attempts) is stored as Operational Attributes on each object. In a normal ldapsearch query operational attributes are not returned. To make them visible, add a "+" to the end of the query. Example: ldapsearch <MYCONNECTIONPARAMS> -b "ou=People,dc=mydomain,dc=tld" "+"

Considerations when using LDAP replication

If you replicate from an LDAP master to LDAP slave(s) and your users are authenticating against slaves, take into consideration that the policy data needs to be synced somehow back to the master (f.e. number of failed login attempts).

The ppolicy module already knows about it. You basically need to set the
configuration value olcPPolicyForwardUpdates (OLC style) / ppolicy_forward_updates (slapd.conf). Furthermore chaining must be configured, including syncrepl. This is very well documented at Linuxtopia.

Here are some links with more detailed information than this short overview:

You've successfully subscribed to Tobias Brunner aka tobru
Great! Next, complete checkout to get full access to all premium content.
Error! Could not sign up. invalid link.
Welcome back! You've successfully signed in.
Error! Could not sign in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.