Managing Password Policies in the Directory
Introduction
Background
OpenLDAP’s password policy module follows the specifications contained in the draft RFC titled draft-behera-ldap-password-policy-09. While the draft itself is expired, it has been implemented in several directory servers, including OpenLDAP. Nonetheless, it is important to note that it is a draft, meaning that it is subject to change and is a work-in-progress.
The key abilities of OpenLDAP’s password policy module are as follows:
- Enforce a minimum length for new passwords
- Make sure passwords are not changed too frequently
- Cause passwords to expire, provide warnings before they need to be changed, and allow a fixed number of ‘grace’ logins to allow them to be changed after they have expired
- Maintain a history of passwords to prevent password re-use
- Prevent password guessing by locking a password for a specified period of time after repeated authentication failures
- Force a password to be changed at the next authentication
- Set an administrative lock on an account
- Support multiple password policies on a default or a per-object basis.
- Perform arbitrary quality checks using an external loadable module. This is a non-standard extension of the draft RFC.
OpenLDAP’s password policy implementation is contained in the module slapo-ppolicy, which is described in the OpenLDAP man page slapo-ppolicy(5). This article draws heavily from concepts discussed in that page and assumes that the reader has reviewed it.
Setting up the slapo-ppolicy Module
The following examples are based on the example LDAP database and server configuration discussed in the article A Really Quick Example LDAP Database. If you are new to OpenLDAP we suggest you set up this example database before attempting these examples.
Setting up slapo-ppolicy happens in three stages:
- Configure the LDAP server to use the module. In this example we will be editing the slapd configuration file /opt/symas/etc/openldap/slapd.conf.
- Create a set of LDAP objects to specify one or more password policies. Password policies are represented by a series of LDAP objects of the object class ‘pwdPolicy’. This object class is defined in the schema file ppolicy.schema described in the slapo-ppolicy(5)man page.
- Apply the policies to the individual objects. The “pwdPolicySubentry” attribute in a user’s object contains the DN of the password policy object that applies to that user. In addition, the DN specified with the “ppolicy_default” keyword specifies the policy that will be applied if none is specified in a user’s object.
Configuring the LDAP Server
- Load the schema file by adding the following line to the global section of slapd.conf:
include /opt/symas/etc/openldap/schema/ppolicy.schema
- Load the module by adding the following line in the global section of slapd.conf:
- Instantiate the module in the database where it will be used. The following example shows the ppolicy module being added to the database that handles the naming context “dc=example,dc=com”. In this example we are also specifying the DN of a policy object to use if none other is specified in a user’s object.
database bdb
suffix "dc=example,dc=com"
[...additional database configuration directives go here...]overlay ppolicy
ppolicy_default “cn=default,ou=policies,dc=example,dc=com”
Creating a set of LDAP objects to specify one or more password policies
- Create the container for the policy objects. In our example the password policy objects are going to be placed in a section of the tree called “ou=policies,dc=example,dc=com”, so the first thing we need to do is add the “ou=policies” container to the directory. You can do this by pasting the following code into a shell prompt (some editing may be required to match your particular system):
/opt/symas/bin/ldapadd -x -D dc=example,dc=com -w secret <<EOF
dn: ou=policies,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: policies
EOF
- Create the default policy object. Note that the object class pwdPolicy is auxiliary rather than structural, so a structural object class also needs to be included in order to make it a legal LDAP object. This example includes the “person” object class because it contains the “cn” attribute, which we’re using as the naming attribute for the policy object and it only requires an additional “sn” attribute. This is only out of convenience, and any other appropriate structural object class can be used. The default policy object defines the following policies:
- The user is allowed to change his own password. Note that the directory ACLs for this attribute can also affect this ability (pwdAllowUserChange: TRUE).
- The name of the password attribute is “userPassword” (pwdAttribute: userPassword). Note that this is the only value that is accepted by OpenLDAP for this attribute.
- The server will check the syntax of the password. If the server is unable to check the syntax (i.e., it was hashed or otherwise encoded by the client) it will return an error refusing the password (pwdCheckQuality: 2).
- When a client includes the Password Policy Request control with a bind request, the server will respond with a password expiration warning if it is going to expire in ten minutes or less (pwdExpireWarning: 600). The warnings themselves are returned in a Password Policy Response control.
- When the password for a DN has expired, the server will allow five additional “grace” logins (pwdGraceAuthNLimit: 5).
- The server will maintain a history of the last five passwords that were used for a DN (pwdInHistory: 5).
- The server will lock the account after the maximum number of failed bind attempts has been exceeded (pwdLockout: TRUE).
- When the server has locked an account, the server will keep it locked until an administrator unlocks it (pwdLockoutDuration: 0)
- The server will reset its failed bind count after a period of 30 seconds.
- Passwords will not expire (pwdMaxAge: 0).
- Passwords can be changed as often as desired (pwdMinAge: 0).
- Passwords must be at least 5 characters in length (pwdMinLength: 5).
- The password does not need to be changed at the first bind or when the administrator has reset the password (pwdMustChange: FALSE)
- The current password does not need to be included with password change requests (pwdSafeModify: FALSE)
- The server will only allow five failed binds in a row for a particular DN (pwdMaxFailure: 5).
You can add this object to the directory by pasting the following code into a shell prompt (some editing may be required to match your particular system):
/opt/symas/bin/ldapadd -x -D dc=example,dc=com -w secret <<EOF
dn: cn=default,ou=policies,dc=example,dc=com
cn: default
objectClass: pwdPolicy
objectClass: person
objectClass: top
pwdAllowUserChange: TRUE
pwdAttribute: userPassword
pwdCheckQuality: 2
pwdExpireWarning: 600
pwdFailureCountInterval: 30
pwdGraceAuthNLimit: 5
pwdInHistory: 5
pwdLockout: TRUE
pwdLockoutDuration: 0
pwdMaxAge: 0
pwdMaxFailure: 5
pwdMinAge: 0
pwdMinLength: 5
pwdMustChange: FALSE
pwdSafeModify: FALSE
sn: dummy value
EOF
- Create additional policy objects as needed. This is basically a repetition of the previous step, using a different cn and different password policy attribute values.
Applying the Policies to Individual Objects
There are two ways password policy can be applied to individual objects:
- The pwdPolicySubentry in a user’s object - If a user’s object contains a pwdPolicySubEntry attribute, and if the object specified by that attribute exists, then the policy defined by that object is applied.
- Default password policy - If no specific policy was specified in the user’s object, and as in the example above, the password policy module was configured with the DN of a default policy object and if that object exists, then the policy defined in that object is applied.
Password Management Issues
Hashing the Values of the userPassword Attribute in the Directory Server.
A guiding philosophy for OpenLDAP and directory servers in general has been that they always hand back exactly what they were given, without modification. For example, if the ‘cn’ attribute of an object was set to ‘fOObaR’, the server will return that exact string during a search. Values of attributes of a sensitive nature, such as userPassword, are often hashed to conceal their values. Since the userPassword values are used internally by the directory server to authenticate users, any hash algorithm that is applied to the value must be compatible with the directory server. Historically this problem has been solved by making the LDAP client application be able to hash the userPassword attribute value in a way that is compatible with the directory server, but this solution has the obvious drawback of requiring tight coupling between the LDAP client and server, and limits the choices of usable hashing algorithms to those that are accommodated by both. This is clearly a sub-optimal solution.
In 2001 RFC 3062 became a standard that specified an LDAP extended operation for cases like this. Extended operations are not bound by the ‘return-what-you-are-given’ philosophy and so are free to do things to attribute values that the add and modify operations cannot. The change password extended operation accepts a plaintext password and hashes it based on a specification that is contained in the server. This allows the server to be in control of the hashing algorithm which, in turn, insures that any hashes applied to userPassword attribute values will not prevent users from being authenticated. Sadly, few directory servers and LDAP clients to date have implemented this method of setting the userPassword attribute.
The password policy module’s ppolicy_hash_cleartext flag addresses this problem by intercepting LDAP modify operations that include the userPassword attribute and converting them to change password extended operations so they can be hashed according to the specification contained in slapd’s configuration file. When this flag is set, LDAP applications that modify the userPassword attribute can send the password in cleartext form to the server using a standard LDAP modify command and the server will hash the value according to the password-hash directive before storing it. It goes without saying that steps need to be taken to protect the cleartext password in transit, such as using SSL, TLS, or some other link encryption method.
The following example shows the ppolicy module configured to hash cleartext passwords:
database bdb
suffix "dc=example,dc=com"
[...additional database configuration directives go here...]overlay ppolicy
ppolicy_default “cn=default,ou=policies,dc=example,dc=com”
ppolicy_hash_cleartext
Returning an ‘Account Locked’ Response to Bind Requests
When a user binds to an account that has been locked, the password policy module will return an INVALID CREDENTIALS (49) error, even if the password policy request control was included with the bind request. This is done because providing an ACCOUNT LOCKED return code would provide useful information to an attacker. However, there are cases when returning ACCOUNT LOCKED is the preferred behavior. The ppolicy_use_lockout flag changes the behavior of the password policy module to return ACCOUNT LOCKED in cases where an account is actually locked. Use this flag with caution, though.
The following example shows the ppolicy module configured to hash cleartext passwords:
database bdb
suffix "dc=example,dc=com"
[...additional database configuration directives go here...]overlay ppolicy
ppolicy_default “cn=default,ou=policies,dc=example,dc=com”
ppolicy_hash_cleartext
ppolicy_use_lockout
Applying Additional Password Quality Checks
The draft-behera-ldap-password-policy-09 RFC is silent on issues of password quality beyond allowing the specification of a minimum password length and maintaining a password history. Nonetheless, it is often desirable to perform additional checks against proposed passwords to help insure that brute-force attacks against the directory are less likely to succeed. OpenLDAP’s password policy implementation addresses the need for extensible password quality checking by allowing a module to be loaded that will perform additional password quality checks.
The name of the password quality checking module is specified in the pwdCheckModule attribute of password policy object that applies to a particular user object. Since this is a non-standard extension, the pwdCheckModule attribute is defined in the auxiliary object class ‘pwdPolicyChecker’. Thus, to use an external module called ‘crackcheck.so’, our policy object from the previous example would look like this:
dn: cn=default,ou=policies,dc=example,dc=com
cn: default
objectClass: pwdChecker
objectClass: pwdPolicy
objectClass: person
objectClass: top
pwdAllowUserChange: TRUE
pwdAttribute: userPassword
pwdCheckQuality: 2
pwdExpireWarning: 600
pwdFailureCountInterval: 30
pwdGraceAuthNLimit: 5
pwdInHistory: 5
pwdLockout: TRUE
pwdLockoutDuration: 0
pwdMaxAge: 0
pwdMaxFailure: 5
pwdMinAge: 0
pwdMinLength: 5
pwdMustChange: FALSE
pwdSafeModify: FALSE
pwdCheckModule: crackcheck.so
sn: dummy value
Note that the module must appear in slapd’s standard module path (usually /opt/symas/lib/openldap) for it to be loaded. Alternatively, a complete pathname can be specified in the attribute value, allowing the module to be put anywhere.
The specifics for developing a custom module are discussed in the slapo-ppolicy(5) man page. The Symas package ’symas-openldap-devel’ contains the header files necessary to compile such a module, and the Freeware section of Symas’s download site includes a sample module that performs a set of cracklib-style checks.