Project

General

Profile

Actions

Bug #17046

open

LDAP shell authentication: pam_member_attribute is written from a user-side attribute, making the group DN check always fail on Active Directory

Added by Emre K 3 days ago.

Status:
New
Priority:
High
Assignee:
-
Category:
Authentication
Target version:
-
Start date:
Due date:
% Done:

0%

Estimated time:
Release Notes:
Default
Affected Plus Version:
26.03.1
Affected Architecture:

Description

Hi,
26.03.1
I can't SSH in at all with AD, even though my AD user is a direct member of the group set in Shell Authentication Group DN:

PS C:\Users\user> ssh user@firewall
(user@firewall) Password:
You must be a memberOf of CN=pfSense-Shell-Users,OU=User-Groups,OU=Groups,OU=Ev,DC=domain,DC=com to login.
Connection closed by 192.168.45.1 port 22

The password is accepted fine. It's the account phase that rejects me.

This is the follow-up I posted in note 8 of #16799. I've since worked out what's actually going on, so I'm opening it separately rather than leaving it buried in a closed ticket.

The fix for #16799 was right, but it uncovered an older bug that the sufficient flag had been hiding. The group check has never worked against AD. It used to fail quietly and let you in anyway, which is what I originally reported. Now it fails and blocks. So on AD we've gone from "the group setting does nothing" to "nobody can log in".

I'm fairly sure #16904 is the same thing. It was closed as a config problem.

Line 2553 of /etc/inc/auth.inc in 26.03.1:

$ldapconf .= "pam_member_attribute {$authcfg['ldap_attr_member']}\n";

ldap_attr_member is the Group member attribute field in the GUI. PADL pam_ldap feeds that into an ldap_compare_s() aimed at the group entry. In other words it asks the directory: does this group object have that attribute, with the user's DN as the value?

You can see this in the error text itself. pam_ldap builds it with "You must be a %s of %s to login.", substituting the attribute name and the group DN, which is where the odd "a memberOf of" phrasing comes from.

Here's the problem. On AD, that GUI field has to be memberOf or the web GUI can't resolve group membership, because the GUI reads the back-link off the user entry. But memberOf on a group object means something completely different: it's the groups that group is nested inside. Not its members.

So the compare is asking a question that can't come back true:

# ldapsearch -x -H ldaps://ldap.domain.com:636 -D bind@domain.com -W \
    -b "CN=pfSense-Shell-Users,OU=User-Groups,OU=Groups,OU=Ev,DC=domain,DC=com" \
    -s base member memberOf

dn: CN=pfSense-Shell-Users,OU=User-Groups,OU=Groups,OU=Ev,DC=domain,DC=com
member: CN=pfSense-Admins,OU=Admin-Groups,OU=User-Groups,OU=Groups,OU=Ev,DC=domain,DC=com
member:: <base64, my CN has non-ASCII characters in it>

The group has no memberOf attribute at all. Membership is in member. No AD user is ever going to satisfy this.

One GUI field, two consumers that want opposite things. The GUI needs the user-side back-link, pam_ldap needs the group-side forward link. Those only happen to be the same attribute when membership lives on the group object.

I think this is why #16904 got closed, and why nobody could reproduce my original report.

On OpenLDAP-style directories (groupOfNames, groupOfUniqueNames, posixGroup) membership sits on the group object, so that GUI field holds member, uniqueMember or memberUid. Both consumers read from the group side, one value works for both, and there's nothing to see.

On AD (RFC 2307bis) membership is read from the user side via memberOf, the two consumers want different things, and the check can't pass. Whether you hit this comes down to which directory you tested against, not whether the config was right.

pfSense already knows the difference, incidentally. The RFC 2307 Groups checkbox is described in the GUI as:

RFC 2307 style group membership has members listed on the group object rather than using groups listed on user object.

That's exactly the distinction pam_ldap cares about, and the pam_ldap.conf generation doesn't look at it.

fix

Pick the attribute from the RFC 2307 Groups flag instead of passing the GUI field straight through. That flag already records the one thing pam_ldap needs to know: whether members are listed on the group object.

There's a second, unrelated bug five lines up in the same block, so I've rolled both into one diff. Line 2548 has a bracket in the wrong place:

$scope = ($authcfg['ldap_scope' == 'one']) ? 'one' : 'sub';

The comparison ends up inside the array subscript, so this evaluates $authcfg[false] and the ternary always returns 'sub'. Search scope > Level has never made it into pam_ldap.conf. Shell auth searches the whole subtree no matter what you set. Easy to check on its own: set Search scope to One Level, save the auth server, and grep '^scope' /var/etc/pam_ldap.conf before and after.

Both changes together:

--- a/src/etc/inc/auth.inc
+++ b/src/etc/inc/auth.inc
@@ -2545,9 +2545,14 @@
             $ldapconf .= "ldap_version {$authcfg['ldap_protver']}\n";
             $ldapconf .= "timelimit {$authcfg['ldap_timeout']}\n";
             $ldapconf .= "base {$authcfg['ldap_basedn']}\n";
-            $scope = ($authcfg['ldap_scope' == 'one']) ? 'one' : 'sub';
+            $scope = ($authcfg['ldap_scope'] == 'one') ? 'one' : 'sub';
             $ldapconf .= "scope {$scope}\n";
             $ldapconf .= "binddn {$authcfg['ldap_binddn']}\n";
             $ldapconf .= "bindpw {$authcfg['ldap_bindpw']}\n";
             $ldapconf .= "pam_login_attribute {$authcfg['ldap_attr_user']}\n";
-            $ldapconf .= "pam_member_attribute {$authcfg['ldap_attr_member']}\n";
+            $pam_member_attr = 'member';
+            if (isset($authcfg['ldap_rfc2307']) &&
+                !empty($authcfg['ldap_attr_member'])) {
+                $pam_member_attr = $authcfg['ldap_attr_member'];
+            }
+            $ldapconf .= "pam_member_attribute {$pam_member_attr}\n";
             //$ldapconf .= "pam_filter objectclass={$authcfg['ldap_attr_user']}\n";
             $ldapconf .= "pam_groupdn {$authcfg['ldap_pam_groupdn']}\n";

What the member attribute change produces:

RFC 2307 Groups Group member attribute Emitted Change
checked set field value none, identical to today
checked empty member today this emits an empty attribute name
unchecked memberOf (AD) member fixes this bug

There's no combination where this is worse than the current output, so it shouldn't regress anyone who has shell auth working today.

I've been running this diff as a System Patch on 26.03.1. It applies and reverts cleanly against a stock file, verified with pkg check -s pfSense-system-26.03.1 either side. With it applied and the auth server re-saved so /var/etc/pam_ldap.conf regenerates:

# grep -E '^(scope|pam_member|pam_groupdn)' /var/etc/pam_ldap.conf
scope sub
pam_member_attribute member
pam_groupdn CN=pfSense-Shell-Users,OU=User-Groups,OU=Groups,OU=Ev,DC=domain,DC=com

SSH then works for a member of the group and is refused for a non-member. The restriction does what the docs say it does. I can't test the RFC 2307 branch since I don't have an OpenLDAP server to point at, but that branch emits exactly what the current code emits.

Two things I've deliberately left alone:

  • RFC 2307 with memberUid. The group holds bare usernames there, while pam_ldap compares a DN. I don't know whether pam_ldap falls back to the bare uid in that case and I can't test it, so the patch keeps the existing behaviour rather than guessing. Someone with the pam_ldap source to hand can probably settle it in a minute.
  • ldap_compare is an exact match with no recursion, so nested groups aren't honoured. That's existing behaviour and I'm not asking for it here.

Line numbers above are from 26.03.1 and will have moved since.

No data to display

Actions

Also available in: Atom