Credentials pass, the tunnel begins to come up, and the session then fails with a message about the group rather than about the password. That combination places the failure in the second half of the LDAP plugin's work: the bind and the password check succeeded, and the membership decision that follows did not. Both halves read from the same configuration stanza, which is why a fix in one place can leave the other untouched. Start from the log line that names the step, not from the account you happen to be testing with.

Treat the rejection as an authorization answer

The plugin performs a service bind, searches for the account, verifies the supplied password, and then asks whether that account belongs to a permitted group. Those are four distinct operations, and only the last one is about groups. A rejection that arrives after a successful bind therefore says nothing about the password, even when the client displays a generic authentication error. Raising the plugin's log level before changing the configuration is what separates the four in the output.

Look for the group evaluation itself. When no group query appears at all, the authorization block is not being reached, which usually means a directive sits outside the stanza that owns it or a required option is missing. When the query appears and returns nothing, the endpoint or the filter is answering a different question than the one you meant.

Decide whether membership lives in the filter or in a group block

There are two supported shapes, and mixing them is how a working configuration stops working. The first pushes membership into the user search by asking for an attribute on the user object; the second leaves the user search alone and has the plugin look up the group object afterwards. Pick one per deployment and write down which one you picked.

<Authorization>
    BaseDN       "OU=People,DC=example,DC=org"
    SearchFilter "(&(sAMAccountName=%u)(memberOf=CN=VPN Access,OU=Groups,DC=example,DC=org))"
    RequireGroup false
</Authorization>

With that shape the filter decides everything, so RequireGroup has to be false or the plugin goes looking for a group object the filter never mentioned. The attribute in the filter must exist on the user object and be populated, which is not guaranteed once groups are nested.

The other shape keeps the search simple and adds a group lookup after it. The group object is named explicitly, and the plugin compares the user against the members the directory returns for it. Nothing about the user filter changes, which makes this shape easier to audit when another team manages the group.

<Authorization>
    BaseDN       "OU=People,DC=example,DC=org"
    SearchFilter "(sAMAccountName=%u)"
    RequireGroup true
    Group        "CN=VPN Access,OU=Groups,DC=example,DC=org"
</Authorization>

The blind spot is shared: a plain membership comparison does not expand nested groups, so an account whose access arrives through a parent group fails while the console still shows the membership. Active Directory exposes member on the group, other schemas use uniqueMember, and pointing at the wrong attribute returns an empty result instead of an error.

Choose an endpoint that holds the group object

The URL decides which directory the plugin asks, and a domain controller on port 389 answers only for its own naming context. A global catalog on port 3268 answers for the whole forest, read-only and with a partial attribute set. When the accounts sit in one domain and the VPN group sits in another, a single-domain query can find the user and still miss the membership. That is the case where moving the URL to the catalog port resolves the rejection, and it is also the case people generalise too far.

The partial attribute set is the limit: an attribute the catalog does not hold cannot be filtered on, so the same change can make one deployment work and another return nothing. Confirm what the endpoint actually returns for the real user and group before rewriting the stanza. Referrals and TLS are separate concerns again, since a referral has to be followed and an endpoint that requires TLS needs the matching trust files.

Keep account state and search scope in their own fields

The disabled flag belongs in the filter, not in the group check. In Active Directory it is the second bit of userAccountControl, and without that condition a disabled account with a valid password authenticates and then fails at the group step, which reads like an authorization bug. Search scope belongs in the base DN, because starting at the domain root is slower and can match objects outside the organizational unit you meant to cover.

Keep the bind account's rights narrow as well. Broad directory rights turn a misconfigured filter into a successful query and hide the bug until someone audits the account. Change membership in the directory itself, never in a local exception list, so disabling the account is enough to remove access. If another team manages the group, agree on the attribute they populate before writing a filter against it.

Prove the result with three accounts

Run the configuration against three identities: an allowed member, a valid account outside the group, and an account that has been disabled. If all three are rejected, the bind or the group query is broken; if all three are accepted, no group condition is being applied at all. A mixed result is the only outcome that proves the configuration distinguishes the cases you care about.

Read the same three answers directly from the directory before blaming the plugin. The first query shows the group attribute the filter depends on, and the second shows the account-control value. Substitute your own base DN, account and endpoint:

ldapsearch -H ldap://dc01.example.org:3268 -x -D "[email protected]" -W -b "DC=example,DC=org" "(sAMAccountName=alice)" memberOf
ldapsearch -H ldap://dc01.example.org:3268 -x -D "[email protected]" -W -b "DC=example,DC=org" "(sAMAccountName=bob)" userAccountControl

A user whose memberOf comes back empty is a directory fact, not a plugin bug, and an account that still authenticates after being disabled means the filter is missing the account-control condition. Fix that evidence first, then re-run the three-account test. Keep the log line that named the failing step next to the configuration change, so the next person can separate authorization from authentication without repeating the search.

ተዛማጅ ጽሑፎች