SLAPO_PPOLICY(5)

πŸ’– SLAPO_PPOLICY(5) πŸ’–

so the manual name is SLAPO_PPOLICY(5) (File Formats Manual, bestie, we are in the file formats section) and the whole thing is about slapo-ppolicy β€” the Password Policy overlay for slapd. it's giving security queen πŸ‘‘

πŸ“œ SYNOPSIS

/etc/openldap/slapd.conf

✨ DESCRIPTION ✨

okay so first things first: the ppolicy overlay is an implementation of the most recent IETF Password Policy proposal for LDAP. the second you instantiate it, it intercepts, decodes and applies specific password policy controls to your overall use of a backend database, to changes to user password fields, etc. it's literally always watching 🫢

and the overlay serves up a whole buffet of password control mechanisms, no cap:

  • password aging β€” both minimum and maximum ages
  • password reuse and duplication control
  • account time-outs
  • mandatory password resets
  • acceptable password content
  • and even grace logins 🌸

different groups of users may be associated with different password policies, and there is no limit to the number of password policies that may be created. infinite policies, infinite vibes.

heads up bestie: some of the policies do not take effect when the operation is performed with the rootdn identity. all the operations, when performed with any other identity, may be subjected to constraints, like access control. also β€” this overlay requires a rootdn to be configured on the database. so set that up or it's not gonna fly πŸ™ˆ

during a password update, an identity with manage access to the userPassword attribute is considered a password administrator where relevant to the IETF Password Policy proposal.

another note, and this one's important: the IETF Password Policy proposal for LDAP makes sense when you're thinking about a single-valued password attribute, while the userPassword attribute allows multiple values. this implementation enforces a single value for the userPassword attribute, despite its specification. yes, it overrides the spec. she does what she wants πŸ’…

in addition to supporting the IETF Password Policy, this module supports the SunDS Account Usability control (1.3.6.1.4.1.42.2.27.9.5.8) on search requests, and it can send the Netscape Password validity controls when configured to do so. iconic behavior, honestly.


πŸ› οΈ CONFIGURATION πŸ› οΈ

these slapd.conf configuration options apply to the ppolicy overlay. they should appear after the overlay directive β€” order matters, bestie, don't be messy.

  • ppolicy_forward_updates β€” say that policy state changes that result from Bind operations (like recording failures, lockout, etc.) on a consumer should be forwarded to a provider instead of being written directly into the consumer's local database. this setting is only useful on a replication consumer, and it also requires the updateref setting and the chain overlay to be appropriately configured. lowkey a whole ensemble cast for this one.
  • ppolicy_hash_cleartext β€” say that cleartext passwords present in Add and Modify requests should be hashed before being stored in the database. this violates the X.500/LDAP information model, but may be needed to compensate for LDAP clients that don't use the Password Modify extended operation to manage passwords. it is recommended that when this option is used, that compare, search, and read access be denied to all directory users. fr fr, deny the access, protect the people.
  • ppolicy_use_lockout β€” a client will always receive an LDAP InvalidCredentials response when Binding to a locked account. by default, when a Password Policy control was provided on the Bind request, a Password Policy response will be included with no special error code set. this option changes the Password Policy response to include the AccountLocked error code.
⚠️ but bestie, note that sending the AccountLocked error code provides useful information to an attacker. sites that are sensitive to security issues should not enable this option. please think before you toggle. πŸ’€
  • ppolicy_send_netscape_controls β€” if set, ppolicy will send the password policy expired (2.16.840.1.113730.3.4.4) and password policy expiring (2.16.840.1.113730.3.4.5) controls when appropriate. the controls are not sent for bind requests where the Password policy control has already been requested. default is not to send the controls.
  • ppolicy_check_module <path> β€” specify the path of a loadable module containing a check_password() function for additional password quality checks. the use of this module is described further below in the description of the pwdPolicyChecker objectclass.
πŸ“Ž Note: the user-defined loadable module must be in slapd's standard executable search PATH, or an absolute path must be provided.
πŸ“Ž Note: use of a ppolicy_check_module is a non-standard extension to the LDAP password policy proposal. she's custom, she's extra, we love her.
  • ppolicy_rules [dn[.<dnstyle>]=<DN/regex>] [require_password=yes|no] [filter=<filter str>] [group[.expand][/<objectclass>[/<attribute>]]=<DN/pattern>] no_policy|policy_dn=<policyDN>|policy_dn.expand=<pattern> [stop|continue] β€” specify which pwdPolicy object to use when no specific policy is set on a given user's entry. the rules are checked in order and the first one to match will apply (but see the continue action). if a rule that specifies no_policy is selected, or the selected policyDN is an entry with objectclass pwdPolicy but its contents are not valid, then no policies will be enforced. shrug emoji.

when checking whether a rule applies, ppolicy checks:

  • the entry's DN against the dn[.<dnstyle>]
  • if require_password is yes (the default), presence of the password attribute (currently only userPassword)
  • the entry's contents against the filter
  • whether it is considered a member of the group specified in group[.expand]

all of which are optional. matching and meaning of <dnstyle> and expand loosely follow that described in slapd.access(5), except that the expansions in the pattern space are slightly more limited β€” only the $<digit> form is supported at the moment, and only if dnstyle is regex. it then tries to retrieve policyDN and check that its objectclass is pwdPolicy.

if all of the rule checks pass, behaviour depends on the action keyword (stop|continue). if action is stop (the default), processing ends here with the policy that was just chosen (or the no_policy decision). if action is continue, the decision is remembered, processing continues, and it can be overridden by later rules. keep the receipts πŸ“

the way to configure this in cn=config is through child entries under the overlay entry with objectclass olcPPolicyRegexRule for regex-based DN matching and olcPPolicyScopedRule for any other <dnstyle>.

  • ppolicy_default <policyDN> β€” specify the DN of the pwdPolicy object to use when no specific policy is set on a given user's entry and none of the ppolicy_rules matched. if there is no specific policy for an entry and no default is given, then no policies will be enforced.
🚨 this option is deprecated in favour of ppolicy_rules and support for it will be removed in a future release. it behaves as if the following rule was the first rule in ppolicy_rules:
ppolicy_rules policyDN=<policyDN> continue

🌈 OBJECT CLASSES 🌈

the ppolicy overlay depends on the pwdPolicy object class. the definition of that class is as follows:

(  1.3.6.1.4.1.42.2.27.8.2.1
    NAME 'pwdPolicy'
    AUXILIARY
    SUP top
    MUST ( pwdAttribute )
    MAY (
        pwdMinAge $ pwdMaxAge $ pwdInHistory $
        pwdCheckQuality $ pwdMinLength $ pwdMaxLength $
        pwdExpireWarning $ pwdGraceAuthnLimit $
        pwdGraceExpiry $ pwdLockout $ pwdLockoutDuration $
        pwdMaxFailure $ pwdFailureCountInterval $
        pwdMustChange $ pwdAllowUserChange $
        pwdSafeModify $ pwdMaxRecordedFailure $
        pwdMinDelay $ pwdMaxDelay $ pwdMaxIdle ) )

the pwdPolicy class is not structural, so entries using it require another, structural, object class. the namedPolicy object class is a good choice πŸŽ€ β€” namedPolicy requires a cn attribute, suitable as the policy entry's rDN.

this implementation also provides two additional objectclasses:

pwdPolicyChecker objectclass β€” used for password quality checking πŸ•΅οΈβ™€οΈ

(  1.3.6.1.4.1.4754.2.99.1
    NAME 'pwdPolicyChecker'
    AUXILIARY
    SUP top
    MAY ( pwdCheckModule $ pwdCheckModuleArg $ pwdUseCheckModule ) )

and pwdHashingPolicy objectclass β€” for more fine-grained control over password hashing πŸ”

(  1.3.6.1.4.1.4754.2.99.2
    NAME 'pwdHashingPolicy'
    SUP pwdPolicy
    AUXILIARY
    MAY ( pwdDefaultHash $ pwdRehashOnBind ) )

see specific attributes below for usage ✨

every account that should be subject to password policy control should have a pwdPolicySubentry attribute containing the DN of a valid pwdPolicy entry β€” or they can simply use the configured default. this way different users may be managed according to different policies. custom policies per user? main character energy fr πŸ’…


🧬 OBJECT CLASS ATTRIBUTES 🧬

each one of the sections below details the meaning and use of a particular attribute of this pwdPolicy object class. buckle up bestie, it's a long list and we're doing all of it.

pwdAttribute

this attribute contains the name of the attribute to which the password policy is applied. for example, the password policy may be applied to the userPassword attribute.

πŸ“Ž Note: in this implementation, the only value accepted for pwdAttribute is userPassword. just the one. periodt.
(  1.3.6.1.4.1.42.2.27.8.1.1
   NAME 'pwdAttribute'
   EQUALITY objectIdentifierMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )

pwdMinAge

this attribute contains the number of seconds that must elapse between modifications allowed to the password. if this attribute is not present, zero seconds is assumed (i.e. the password may be modified whenever and however often is desired). go off, i guess.

(  1.3.6.1.4.1.42.2.27.8.1.2
   NAME 'pwdMinAge'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdMaxAge

this attribute contains the number of seconds after which a modified password will expire. if this attribute is not present, or if its value is zero (0), then passwords will not expire. immortal password era 🌸

(  1.3.6.1.4.1.42.2.27.8.1.3
   NAME 'pwdMaxAge'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdInHistory

this attribute is used to specify the maximum number of used passwords that will be stored in the pwdHistory attribute. if the pwdInHistory attribute is not present, or if its value is zero (0), used passwords will not be stored in pwdHistory and thus any previously-used password may be reused. no history checking occurs if the password is being modified by the rootdn, although the password is saved in the history. so rootdn gets a free pass but the history still keeps the receipt πŸ‘€

(  1.3.6.1.4.1.42.2.27.8.1.4
   NAME 'pwdInHistory'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdCheckQuality 🌸

okay so this one is literally the vibe check for your password's whole syntax situation. It says if and how password syntax gets checked while a password is being modified or added.

  • Not there at all, or set to zero (0)? Then no syntax checking happens, periodt. Do whatever bestie, nobody's looking.
  • Set to one (1)? The server will check the syntax, but if it genuinely can't β€” say, a client-side hashed password, or some other reason β€” it'll just accept it anyway. Forgiving queen.
  • Set to two (2)? Server checks the syntax, and if it can't check it, it returns an error and refuses the password. Strict! No notes! πŸ’…
(  1.3.6.1.4.1.42.2.27.8.1.5
   NAME 'pwdCheckQuality'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdMinLength πŸŽ€

When syntax checking is on (peep the pwdCheckQuality attribute, they're besties), this holds the minimum length in bytes that'll be accepted in a password. No attribute? Then min length is not enforced, live your life.

If the server can't check the length of the password β€” client-side hashed password, other chaos, whatever β€” then it depends on pwdCheckQuality: it either accepts the password without checking it (when pwdCheckQuality is zero (0) or one (1)) or refuses it (when pwdCheckQuality is two (2)).

And hmm, if you want the number of characters enforced with regards to a particular encoding, you need an appropriate ppolicy_check_module. That's the move. ✨

(  1.3.6.1.4.1.42.2.27.8.1.6
   NAME 'pwdMinLength'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdMaxLength πŸ¦‹

Same energy, opposite direction: when syntax checking is enabled (yes, pwdCheckQuality again), this one holds the maximum length in bytes accepted in a password. If it's not present, the max isn't enforced, no cap bestie.

And fr fr, if the server can't check the password's length β€” client-side hashed, some other reason, the usual β€” then depending on pwdCheckQuality it either accepts without checking (pwdCheckQuality equals zero (0) or one (1)) or refuses it (pwdCheckQuality equals two (2)).

Want the character count enforced according to a specific encoding? Then you need an appropriate ppolicy_check_module, iykyk. 🌈

(  1.3.6.1.4.1.42.2.27.8.1.31
   NAME 'pwdMaxLength'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdExpireWarning ⏰

This is the maximum number of seconds before a password is due to expire that the server still slides those expiration warning messages into the face of a user who's authenticating to the directory. If the attribute isn't present, or the value is zero (0), no warnings get sent. Silent treatment, lowkey rude but okay.

(  1.3.6.1.4.1.42.2.27.8.1.7
   NAME 'pwdExpireWarning'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdGraceAuthnLimit πŸ•°οΈ

The number of times an expired password can still be used to authenticate a user to the directory. Basically the grace-period pass. If this attribute isn't present, or its value is zero (0), then nope β€” users with expired passwords won't be allowed to authenticate to the directory at all. πŸšͺ

(  1.3.6.1.4.1.42.2.27.8.1.8
   NAME 'pwdGraceAuthnLimit'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdGraceExpiry πŸŒ™

how long those grace authentications are actually valid: this attribute specifies the number of seconds they stay good for. Not present, or value of zero (0)? Then there's no time limit on the grace authentications at all. Go off, no rush.

(  1.3.6.1.4.1.42.2.27.8.1.30
   NAME 'pwdGraceExpiry'
   EQUALITY integerMatch
   ORDERING integerOrderingMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdLockout πŸ”’

This one decides what the directory does when someone's been out here failing to authenticate over and over. If pwdLockout is set (value "TRUE"), then once there've been a specified number of consecutive failed bind attempts, the user won't be allowed to even attempt authenticating to the directory. That max number is what pwdMaxFailure says. If pwdLockout isn't present, or its value is "FALSE", then the password can be used to authenticate no matter how many consecutive failed binds have stacked up. Infinite attempts, chaotic but free. πŸ’€

(  1.3.6.1.4.1.42.2.27.8.1.9
   NAME 'pwdLockout'
   EQUALITY booleanMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
   SINGLE-VALUE )

pwdLockoutDuration βŒ›

The number of seconds during which the password cannot be used to authenticate that user to the directory, because of too many consecutive failed bind attempts. (Go read pwdLockout and pwdMaxFailure too, they're all one storyline.)

If pwdLockoutDuration isn't present, or its value is zero (0), then the password can't be used to authenticate that user again until an administrator resets it. Not a timeout, a whole situation. 😭

(  1.3.6.1.4.1.42.2.27.8.1.10
   NAME 'pwdLockoutDuration'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdMaxFailure πŸ”’

The number of consecutive failed bind attempts after which the password may no longer be used to authenticate a user to the directory. If pwdMaxFailure isn't present, or its value is zero (0), then the user gets to keep trying forever, no matter how many consecutive failed bind attempts have happened with that user's DN. (See also pwdLockout and pwdLockoutDuration β€” the whole trio eats together.) πŸ“

(  1.3.6.1.4.1.42.2.27.8.1.11
   NAME 'pwdMaxFailure'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdMaxRecordedFailure πŸ“

The maximum number of failed bind attempts to actually store in a user's entry. If pwdMaxRecordedFailure isn't present, or its value is zero (0), then it just defaults to whatever pwdMaxFailure is. And if that value is also 0, the default becomes 5. Ripple-effect defaults, iconic honestly.

(  1.3.6.1.4.1.42.2.27.8.1.32
   NAME 'pwdMaxRecordedFailure'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdFailureCountInterval 🧹

The number of seconds after which old consecutive failed bind attempts get purged from the failure counter β€” even though nobody successfully authenticated. Just a lil cleanup. If pwdFailureCountInterval isn't present, or its value is zero (0), then that failure counter only ever gets reset by a successful authentication. Nothing else saves you bestie. 🫢

(  1.3.6.1.4.1.42.2.27.8.1.12
   NAME 'pwdFailureCountInterval'
   EQUALITY integerMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdMustChange πŸ”

Whether users have to change their passwords when they first bind to the directory after an administrator sets or resets it. If pwdMustChange is "TRUE", yeah β€” users must change their password on that first bind after the admin set or reset it. If pwdMustChange isn't present, or its value is "FALSE", then no, users aren't required to change anything when they bind after the admin sets or resets the password. Old password stays, main character energy. πŸ‘‘

(  1.3.6.1.4.1.42.2.27.8.1.13
   NAME 'pwdMustChange'
   EQUALITY booleanMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
   SINGLE-VALUE )

pwdAllowUserChange πŸ™‹

Whether users are allowed to change their own passwords. If pwdAllowUserChange is set to "TRUE" β€” or if the attribute isn't even present β€” then yes, users can change their own passwords. If its value is "FALSE", then users cannot change their own passwords.

Note: this implies that when pwdAllowUserChange is "TRUE", users will still be able to change the password of another user, subject to access control. This restriction only applies to modifications of one's own password.

Also worth clocking: pwdAllowUserChange was defined in the spec to provide rough access control to the password attribute, in implementations that don't do fine-grain access control. Since OpenLDAP does provide fine-grain access control, using this attribute is discouraged β€” use ACLs instead, bestie. Peep slapd.access(5) for the details. ✨

(  1.3.6.1.4.1.42.2.27.8.1.14
   NAME 'pwdAllowUserChange'
   EQUALITY booleanMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
   SINGLE-VALUE )

pwdSafeModify πŸ›‘οΈ

Denotes whether the user's existing password has to be sent along with their new password when they change it. If pwdSafeModify is set to "TRUE", then yes β€” old password must come along with the new one. If the attribute isn't present, or its value is "FALSE", then nah, the existing password doesn't need to be sent with the new one.

(  1.3.6.1.4.1.42.2.27.8.1.15
   NAME 'pwdSafeModify'
   EQUALITY booleanMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
   SINGLE-VALUE )

pwdMinDelay 🐒

The number of seconds to delay responding to the first failed authentication attempt. If this attribute isn't set, or is zero (0), then no delays get used at all. And heads up: pwdMaxDelay must also be specified if pwdMinDelay is set. They're a package deal. 🎁

Note: this implementation uses a variable lockout instead of delaying the bind response. So don't expect the literal delay, okay?
(  1.3.6.1.4.1.42.2.27.8.1.24
   NAME 'pwdMinDelay'
   EQUALITY integerMatch
   ORDERING integerOrderingMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

pwdMaxDelay 🐌

The maximum number of seconds to delay when responding to a failed authentication attempt. Here's the tea: the time in pwdMinDelay is the starting point, and then it doubles on every failure until the delay time is greater than or equal to pwdMaxDelay β€” or until a successful authentication happens, which resets that failure counter. And pwdMinDelay must also be specified if pwdMaxDelay is set, no solo acts here.

Note: this implementation uses a variable lockout instead of delaying the bind response. Same as before, don't come looking for the literal delay. πŸ™ˆ

🌸 pwdMaxDelay (schema, continued)

(  1.3.6.1.4.1.42.2.27.8.1.25
   NAME 'pwdMaxDelay'
   EQUALITY integerMatch
   ORDERING integerOrderingMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

πŸ’œ pwdMaxIdle

okay so this one is the clock-watcher of the policy πŸ’… β€” it says how many seconds an account can just sit there, untouched and unused, before it gets locked. Not set? Or set to zero (0)? Then no check happens at all, it just vibes forever.

⚠️ But bestie, listen β€” for this to actually be enforced, the lastbind functionality has to be turned on for the database. That means olcLastBind is set to TRUE. No lastbind, no enforcement, periodt.

And here's its whole lil schema glow-up ✨:

(  1.3.6.1.4.1.42.2.27.8.1.26
   NAME 'pwdMaxIdle'
   EQUALITY integerMatch
   ORDERING integerOrderingMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
   SINGLE-VALUE )

🧚 pwdUseCheckModule / pwdCheckModuleArg

omg this one is giving plug-in era main character energy πŸ”₯. The pwdUseCheckModule attribute turns on a loadable module that you already set up earlier with ppolicy_check_module β€” but only for this policy, iykyk.

That module must instantiate the check_password() function. So when does it get called? Only when pwdCheckQuality is set to one (1) or two (2), and only after every single built-in password compliance check has already passed. So it's the final boss check, not the first one πŸ’€.

It gets called according to this function prototype, literally character for character:

int check_password (char *pPasswd, struct berval *pErrmsg, Entry *pEntry, struct berval *pArg);

Breakdown, bestie:

  • pPasswd β€” the clear-text user password, right there in the flesh.
  • pErrmsg β€” points to a struct berval with space to hand back human-readable details about any error it runs into. The bv_len field must contain the size of the space that bv_val gives you. (No lying about your buffer size, that's so not it.)
  • pEntry β€” optional. If it's non-NULL, it carries a pointer to the entry whose password is being checked.
  • pArg β€” optional. Points to a struct berval holding the value of pwdCheckModuleArg in the effective password policy, if that's set. Otherwise it's NULL.
⚠️ If pErrmsg is NULL, then funcName must NOT attempt to use it. Like, at all. Zero. Don't do it.

Return value rules: LDAP_SUCCESS from the called function = the password is ok βœ…. Any other value = the password is unacceptable ❌. If it's unacceptable, the server returns an error to the client, and pErrmsg may be used to return a human-readable textual explanation of the error.

And if the space the caller passed in is too small? The function may replace it with a dynamically allocated buffer β€” which slapd will free() for you. So cute of it 🫢.

πŸ’€ Heads up: the pwdCheckModule attribute is now obsolete and is ignored. RIP to a real one.

All three schemas, coming right up ✨:

(  1.3.6.1.4.1.4754.1.99.1
   NAME 'pwdCheckModule'
   EQUALITY caseExactIA5Match
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
   OBSOLETE
   SINGLE-VALUE )
( 1.3.6.1.4.1.4754.1.99.2
   NAME 'pwdCheckModuleArg'
   EQUALITY octetStringMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
   DESC 'Argument to pass to check_password() function'
   SINGLE-VALUE )
(  1.3.6.1.4.1.4754.1.99.3
   NAME 'pwdUseCheckModule'
   EQUALITY booleanMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
   SINGLE-VALUE )

πŸ“ pwdDefaultHash

If you set this, it overrides the configured default password hash β€” but only for the objects governed by this policy. So it's per-policy, very exclusive, very "this is my table" πŸ’….

(  1.3.6.1.4.1.4754.1.99.4
   NAME 'pwdDefaultHash'
   EQUALITY caseIgnoreMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
   DESC 'Per policy default hash setting'
   SINGLE-VALUE )

πŸ¦‹ pwdRehashOnBind

This one says whether the user's existing password should get rehashed. Here's the tea: if pwdRehashOnBind is set to "TRUE", pwdDefaultHash is set to a known password hash, and a Simple Bind succeeds β€” then the entry's userPassword gets replaced with a version using that hash. Glow-up on login, obsessed ✨.

(  1.3.6.1.4.1.4754.1.99.5
   NAME 'pwdRehashOnBind'
   EQUALITY booleanMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
   DESC 'On successful Simple Bind, rehash password
        with default hash if different'
   SINGLE-VALUE )

✨ OPERATIONAL ATTRIBUTES ✨

okay so these operational attributes live inside the user's entry, bestie. most of them are not for users to poke at directly β€” they're there to track what the user's been doing. they're documented here so admins AND users can both actually understand how the ppolicy module is doing its thing πŸ’…

real talk though: the current IETF Password Policy proposal does not define how these operational attributes are supposed to behave in a replication setup. so in general, authentication attempts on a replica only touch the copy of the operational attributes on that replica, and won't affect any of the attributes on the provider's copy of the user's entry. meanwhile, operational attribute changes that come from authentication attempts on a provider will usually replicate out to the replicas (and will also overwrite any changes that started on the replica). these behaviors are not guaranteed and are subject to change once a formal spec shows up. 😭


userPassword

this one isn't strictly part of the ppolicy module, ngl β€” but it is the attribute that the module tracks and controls. its definition lives in the standard OpenLDAP schema, so go look there. 🫢

pwdPolicySubentry

this attribute points directly at the pwdPolicy subentry that's meant to be used for this particular directory user. every account that should be subject to password policy control will have a pwdPolicySubentry attribute holding the DN of a pwdPolicy entry. the module uses the one stored in the DB if it exists, otherwise it'll try to derive the right policy to apply based on the rules it's been configured with β€” see the ppolicy_rules / olcPPolicyScopedRule / olcPPolicyRegexRule configuration options. that's how different users can be managed under different configurable policies, iconic behavior honestly. the value of the effective policy DN gets returned if you ask for it in a search request, but heads up: this is a virtual attribute and it is not usable in a filter. Modifying/adding this attribute directly is deprecated, and the ability to do it will be removed in a future release. πŸ’€

   (  1.3.6.1.4.1.42.2.27.8.1.23
      NAME 'pwdPolicySubentry'
      DESC 'The pwdPolicy subentry in effect for
          this object'
      EQUALITY distinguishedNameMatch
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
      SINGLE-VALUE
      USAGE directoryOperation)

pwdChangedTime

this one is the timestamp of the last time the entry's password got changed. the password expiration policy reads this value to figure out whether the password is too old to still be allowed for user authentication. and here's the tea: if pwdChangedTime doesn't exist, the user's password will not expire. πŸŽ€

   (  1.3.6.1.4.1.42.2.27.8.1.16
      NAME 'pwdChangedTime'
      DESC 'The time the password was last changed'
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
      EQUALITY generalizedTimeMatch
      ORDERING generalizedTimeOrderingMatch
      SINGLE-VALUE
      NO-USER-MODIFICATION
      USAGE directoryOperation)

pwdAccountLockedTime

this attribute holds the time the user's account got locked. once the account is locked, that password can no longer be used to authenticate the user to the directory. and if pwdAccountLockedTime is set to 000001010000Z, the account is permanently locked and only an administrator can unlock it 😭. quick note: account locking only kicks in when the pwdLockout password policy attribute is set to "TRUE".

   (  1.3.6.1.4.1.42.2.27.8.1.17
      NAME 'pwdAccountLockedTime'
      DESC 'The time an user account was locked'
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
      EQUALITY generalizedTimeMatch
      ORDERING generalizedTimeOrderingMatch
      SINGLE-VALUE
      USAGE directoryOperation)

pwdFailureTime

this one holds the timestamps of every one of the consecutive authentication failures made on attempted authentication to this DN (i.e. this account). if too many timestamps pile up in here (peep the pwdMaxFailure password policy attribute for the details) and the pwdLockout password policy attribute is set to "TRUE", the account may get locked. (go look at the pwdLockout password policy attribute too, bestie.) excess timestamps beyond what pwdMaxFailure or pwdMaxRecordedFailure allows may also get purged. and if a successful authentication happens on this DN (i.e. this user account), then pwdFailureTime gets cleansed of entries. glow-up ✨

   (  1.3.6.1.4.1.42.2.27.8.1.19
      NAME 'pwdFailureTime'
      DESC 'The timestamps of the last consecutive
          authentication failures'
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
      EQUALITY generalizedTimeMatch
      ORDERING generalizedTimeOrderingMatch
      NO-USER-MODIFICATION
      USAGE directoryOperation )

pwdHistory

this attribute is the history of previously used passwords for this DN (i.e. this user account). the values get stored in string format, like this:

   pwdHistory=
       time "#" syntaxOID "#" length "#" data

   time=
       GeneralizedTime as specified in section 3.3.13 of [RFC4517]

   syntaxOID = numericoid
       This is the string representation of the dotted-decimal OID that
       defines the syntax used to store the password.  numericoid is
       described in section 1.4 of [RFC4512].

   length = NumericString
       The number of octets in the data.  NumericString is described in
       section 3.3.23 of [RFC4517].

   data =
       Octets representing the password in the format specified by
       syntaxOID.

this format lets the server store and transmit a history of passwords that have been used. and for equality matching on the values in this attribute to actually work properly, the time field has to be in GMT format. no cap.

   (  1.3.6.1.4.1.42.2.27.8.1.20
      NAME 'pwdHistory'
      DESC 'The history of user passwords'
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.40
      EQUALITY octetStringMatch
      NO-USER-MODIFICATION
      USAGE directoryOperation)

pwdGraceUseTime

this attribute holds the list of timestamps of logins made after the user password in the DN has already expired. these post-expiration logins are known as "grace logins". if too many grace logins get used (peep the pwdGraceAuthnLimit password policy attribute), then the DN can no longer be used to authenticate the user to the directory until the administrator changes the DN's userPassword attribute. πŸ™ˆ

   (  1.3.6.1.4.1.42.2.27.8.1.21
      NAME 'pwdGraceUseTime'
      DESC 'The timestamps of the grace login once the password has
   expired'
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
      EQUALITY generalizedTimeMatch
      NO-USER-MODIFICATION
      USAGE directoryOperation)

pwdReset

this attribute tells you whether the user's password got reset by the administrator and therefore must be changed on the first use of this DN to authenticate to the directory. if pwdReset is set to "TRUE", then yeah β€” the password was reset and the user must change it on first authentication. if the attribute doesn't exist, or it's set to "FALSE", then the user does not need to change their password due to an administrative reset. easy. πŸ’…

   (  1.3.6.1.4.1.42.2.27.8.1.22
      NAME 'pwdReset'
      DESC 'The indication that the password has
          been reset'
      EQUALITY booleanMatch
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
      SINGLE-VALUE
      USAGE directoryOperation)

pwdStartTime

this attribute says when the entry's password becomes valid for authentication. any authentication attempt made before this time will fail. and if this attribute doesn't exist, then no restriction applies at all. ⭐

   (  1.3.6.1.4.1.42.2.27.8.1.27
      NAME 'pwdStartTime'
      DESC 'The time the password becomes enabled'
      EQUALITY generalizedTimeMatch
      ORDERING generalizedTimeOrderingMatch
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
      SINGLE-VALUE
      USAGE directoryOperation )

pwdEndTime

this attribute says when the entry's password becomes invalid for authentication. any authentication attempt made after this time will fail, no matter what the expiration or grace settings say. and if this attribute doesn't exist, then this restriction doesn't apply. πŸ¦‹

   (  1.3.6.1.4.1.42.2.27.8.1.28
      NAME 'pwdEndTime'
      DESC 'The time the password becomes disabled'
      EQUALITY generalizedTimeMatch
      ORDERING generalizedTimeOrderingMatch
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
      SINGLE-VALUE
      USAGE directoryOperation )

lowkey important note: pwdStartTime may be set to a time that's greater than or equal to pwdEndTime β€” and what that does is simply disable the account. 😭

pwdAccountTmpLockoutEnd

this attribute is that the user's password has been locked out temporarily according to the pwdMinDelay policy option, and when the lockout ends.

   (  1.3.6.1.4.1.42.2.27.8.1.33
      NAME 'pwdAccountTmpLockoutEnd'
      DESC 'Temporary lockout end'
      EQUALITY generalizedTimeMatch
      ORDERING generalizedTimeOrderingMatch
      SYNTAX  1.3.6.1.4.1.1466.115.121.1.24
      SINGLE-VALUE
      NO-USER-MODIFICATION
      USAGE directoryOperation )

πŸ‘‘ SUNDS ACCOUNT USABILITY CONTROL πŸ‘‘

so if the SunDS Account Usability control is used with a search request, the overlay will attach validity information to each entry β€” but only if all of these things are true, bestie:

  • there is a password policy that applies to the entry
  • the user has compare access to the entry's password attribute.
  • the configured password attribute is present in the entry

🌸 EXAMPLES 🌸

okay here's what it actually looks like in a config, slay:

   database mdb
   suffix dc=example,dc=com
   ...
   overlay ppolicy
   ppolicy_default "cn=Standard,ou=Policies,dc=example,dc=com"

πŸ”₯ SEE ALSO πŸ”₯


πŸ’€ BUGS πŸ’€

the LDAP Password Policy specification is not yet an approved standard, and it is still evolving. this code will continue to be in flux until the specification is finalized. so yeah… it's giving "we're so back / it's so over" at the same time, fr fr. 😭

✨ ACKNOWLEDGEMENTS ✨

okay so acknowledgements time, bestie, because we gotta give credit where credit is due πŸ’–

This module was written in 2004 by Howard Chu of Symas Corporation, with significant input from Neil Dunbar and Kartik Subbarao of Hewlett-Packard. iconic behavior, no cap πŸ‘‘

This manual page borrows heavily and shamelessly from the specification upon which the password policy module it describes is based. the audacity? love that for us πŸ’… This source is the IETF LDAP password policy proposal by P. Behera, L. Poitou and J. Sermersheim. The proposal is fully documented in the IETF document named draft-behera-ldap-password-policy-10.txt, written in August of 2009 πŸ“œβœ¨

OpenLDAP Software is developed and maintained by The OpenLDAP Project (<http://www.openldap.org/>) 🫢 OpenLDAP Software is derived from the University of Michigan LDAP 3.3 Release. periodt 🌈


okay bestie, that's the whole entire man page, and we ATE πŸ’… no crumbs. This was OpenLDAP 2.7.2, dated 2026/10/21, and you've been reading SLAPO_PPOLICY(5) πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from