RFC2307 was written in 1998 to define a schema for representing NIS information (such as Unix account attributes such as UID, home directory, etc) in an LDAP-based directory. A later draft called RFC2307bis was introduced and adopted by some major Unix vendors, but never left draft stage.
Microsoft’s Active Directory adopted RFC2307 attributes for Unix clients with Server 2003, but currently supports RFC2307bis.
ACI administrators and network engineers usually don’t care about the differences between these two LDAP schemas….until you need to configure authentication of some network device against your central LDAP servers.
While there are a number of differences between the two (posixGroup as a structural class instead of an auxiliary class), the main difference we’re interested in how group membership is identified, since we need to map LDAP group membership to APIC controller policies.
In RFC2307, group objects contain memberUID attributes identifying the user object(s) associated with that group. However, the users’ attributes do NOT contain information about group membership.
In RFC2307bis, user objects do contain a group membership attribute (memberOf), while also listing the members in the group object (usually “member”).
This graphic illustrates the difference:

In the first illustration (RFC2307 posixGroup), there are three users named Andrew, Jody, and Steve, and two groups called Admins and Schmucks. The group objects have an attribute called “memberUID” that reference the user each member of the group.
In the second illustration (RFC2307bis memberOf), the same three users and two groups exist, but now each user’s object contains an attribute that identifies group membership. The group still shows the membership, although it actually returns the full DN (not shown) instead of a relative DN like RFC2307 .The advantage here is that a group policy can be identified based on a user’s attributes (all three users are admins, but Jody gets additional policies reserved specifically for Schmucks).
The examples below are taken from our lab’s FreeIPA server, which defaults to RFC2307bis and uses a compatibility layer to provide RFC2307 attributes when queried for them.
Examples: RFC2307bis
This first example shows queries our LDAP server (-h 10.18.188.184) and authenticates with a valid Bind DN for that directory (-D uid=admin,cn=users,cn=accounts,dc=coastlab,dc=local), and uses the Base DN of our domain (dc=coastlab,dc=local) for the search tree. The actual query is for an object with an attribute of “uid” that has the value “ldapy” (the username). The -W indicates that we wish to be prompted for a password rather than pass one on the command line.
[root@labsvc ~]# ldapsearch -x -h 10.18.188.184 -D "uid=admin,cn=users,cn=accounts,dc=coastlab,dc=local" -b "dc=coastlab,dc=local" -W "uid=ldapy"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <dc=coastlab,dc=local> with scope subtree
# filter: uid=ldapy
# requesting: ALL
# ldapy, users, compat, coastlab.local
dn: uid=ldapy,cn=users,cn=compat,dc=coastlab,dc=local
objectClass: posixAccount
objectClass: ipaOverrideTarget
objectClass: top
gecos: Testy McTestFace
cn: Testy McTestFace
uidNumber: 1197200003
gidNumber: 1197200003
loginShell: /bin/sh
homeDirectory: /home/ldapy
ipaAnchorUUID:: OklQQTpjb2FzdGxhYi5sb2NhbDo5MzRkMjMzYS1hYzg5LTExZTktOWNhNi0wMD
UwNTY4OTdkMTU=
uid: ldapy
# ldapy, users, accounts, coastlab.local
dn: uid=ldapy,cn=users,cn=accounts,dc=coastlab,dc=local
displayName: Testy McTestFace
uid: ldapy
krbCanonicalName: ldapy@COASTLAB.LOCAL
objectClass: top
objectClass: person
objectClass: organizationalperson
objectClass: inetorgperson
objectClass: inetuser
objectClass: posixaccount
objectClass: krbprincipalaux
objectClass: krbticketpolicyaux
objectClass: ipaobject
objectClass: ipasshuser
objectClass: ipaSshGroupOfPubKeys
objectClass: mepOriginEntry
loginShell: /bin/sh
initials: TM
gecos: Testy McTestFace
sn: McTestFace
homeDirectory: /home/ldapy
mail: ldapy@coastlab.local
krbPrincipalName: ldapy@COASTLAB.LOCAL
givenName: Testy
cn: Testy McTestFace
ipaUniqueID: 934d233a-ac89-11e9-9ca6-005056897d15
uidNumber: 1197200003
gidNumber: 1197200003
krbPasswordExpiration: 20190722144638Z
krbLastPwdChange: 20190722144638Z
krbExtraData:: AAJOzDVdcm9vdC9hZG1pbkBDT0FTVExBQi5MT0NBTAA=
mepManagedEntry: cn=ldapy,cn=groups,cn=accounts,dc=coastlab,dc=local
memberOf: cn=ipausers,cn=groups,cn=accounts,dc=coastlab,dc=local
memberOf: cn=apic,cn=groups,cn=accounts,dc=coastlab,dc=local
krbLoginFailedCount: 0
# search result
search: 2
result: 0 Success
There are two objects returned from this query, one of which has a DN of “uid=ldapy,cn=users,cn=compat,dc=coastlab,dc=local” and a second which has the DN of “uid=ldapy,cn=users,cn=accounts,dc=coastlab,dc=local”.
Both use uid=ldapy, but you’ll notice the full object path is different. The first entry uses “cn=users,cn=compat”, which is actually an RFC2307 compatibility layer, which we’ll explore shortly. The second entry is the one we want, as the path includes the container “cn=users,cn=accounts”.
Notice in the second entry labeled “dn: uid=ldapy,cn=users,cn=compat,dc=coastlab,dc=local”, there are two attributes toward the bottom:
- memberOf: cn=ipausers,cn=groups,cn=accounts,dc=coastlab,dc=local
- memberOf: cn=apic,cn=groups,cn=accounts,dc=coastlab,dc=local.
Notice the first entry contained no such attributes for group information.
In our APIC / LDAP configuration HOWTO, we used the cn=apic group to identify an ACI admin role in our ACI lab. The other entry (cn=ipausers) is a default group that FreeIPA uses out-of-the-box.
If we query for the cn=apic group, we see the following:
[root@labsvc ~]# ldapsearch -x -h 10.18.188.184 -D "uid=admin,cn=users,cn=accounts,dc=coastlab,dc=local" -b "dc=coastlab,dc=local" -W "cn=apic"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <dc=coastlab,dc=local> with scope subtree
# filter: cn=apic
# requesting: ALL
#
# apic, groups, compat, coastlab.local
dn: cn=apic,cn=groups,cn=compat,dc=coastlab,dc=local
objectClass: posixGroup
objectClass: ipaOverrideTarget
objectClass: top
gidNumber: 1197200004
memberUid: ldapy
ipaAnchorUUID:: OklQQTpjb2FzdGxhYi5sb2NhbDplNWM4OTlhNC1hYzhhLTExZTktYjA2NC0wMD
UwNTY4OTdkMTU=
cn: apic
# apic, groups, accounts, coastlab.local
dn: cn=apic,cn=groups,cn=accounts,dc=coastlab,dc=local
objectClass: top
objectClass: groupofnames
objectClass: nestedgroup
objectClass: ipausergroup
objectClass: ipaobject
objectClass: posixgroup
cn: apic
ipaUniqueID: e5c899a4-ac8a-11e9-b064-005056897d15
gidNumber: 1197200004
member: uid=ldapy,cn=users,cn=accounts,dc=coastlab,dc=local
# search result
search: 2
result: 0 Success
Let’s compare the two results.
The first entry, for RFC2307 compatibility, shows the memberUID attribute using the relative distinguished name:
# apic, groups, compat, coastlab.local
dn: cn=apic,cn=groups,cn=compat,dc=coastlab,dc=local
memberUid: ldapy
Meanwhile, the second entry contains a different attribute (member, not memberUID), and includes the full distinguished name of the member.
# apic, groups, accounts, coastlab.local
dn: cn=apic,cn=groups,cn=accounts,dc=coastlab,dc=local
member: uid=ldapy,cn=users,cn=accounts,dc=coastlab,dc=local
Let’s take another look at the user objects.
In the RFC2307 compatibility layer, we have no memberOf or anything similar to use for identifying group membership. But we do have the “uid=ldapy” attribute. If we wanted to set ACI policy based on this information, we would have to authenticate the user, grab the UID attribute, then compare it to a specific group’s list of memberUIDs (or search through all groups to find which one has the correct memberUID).
[user@labsvc ~]# ldapsearch -x -h 10.18.188.184 -D "uid=admin,cn=users,cn=accounts,dc=coastlab,dc=local" -b "cn=compat,dc=coastlab,dc=local" -W "uid=ldapy"
# ldapy, users, compat, coastlab.local
dn: uid=ldapy,cn=users,cn=compat,dc=coastlab,dc=local
objectClass: posixAccount
objectClass: ipaOverrideTarget
objectClass: top
gecos: Testy McTestFace
cn: Testy McTestFace
uidNumber: 1197200003
gidNumber: 1197200003
loginShell: /bin/sh
homeDirectory: /home/ldapy
ipaAnchorUUID:: OklQQTpjb2FzdGxhYi5sb2NhbDo5MzRkMjMzYS1hYzg5LTExZTktOWNhNi0wMD
UwNTY4OTdkMTU=
uid: ldapy
Notice I changed my Base DN (-b) to explicitly follow the cn=compat subtree. This way, FreeIPA returns only the original
The APIC Controller doesn’t do that, by the way. I’m guessing the reason is that there could be too many groups to search through for policy mappings, since the policy model in ACI is somewhat granular. Instead, we need to configure the APIC based on the RFC2307bis schema that provides the memberOf attribute:
[user@labsvc ~]# ldapsearch -x -h 10.18.188.184 -D "uid=admin,cn=users,cn=accounts,dc=coastlab,dc=local" -b "cn=accounts,dc=coastlab,dc=local" -W "uid=ldapy"
# ldapy, users, accounts, coastlab.local
dn: uid=ldapy,cn=users,cn=accounts,dc=coastlab,dc=local
displayName: Testy McTestFace
uid: ldapy
krbCanonicalName: ldapy@COASTLAB.LOCAL
objectClass: top
objectClass: person
objectClass: organizationalperson
objectClass: inetorgperson
objectClass: inetuser
objectClass: posixaccount
objectClass: krbprincipalaux
objectClass: krbticketpolicyaux
objectClass: ipaobject
objectClass: ipasshuser
objectClass: ipaSshGroupOfPubKeys
objectClass: mepOriginEntry
loginShell: /bin/sh
initials: TM
gecos: Testy McTestFace
sn: McTestFace
homeDirectory: /home/ldapy
mail: ldapy@coastlab.local
krbPrincipalName: ldapy@COASTLAB.LOCAL
givenName: Testy
cn: Testy McTestFace
ipaUniqueID: 934d233a-ac89-11e9-9ca6-005056897d15
uidNumber: 1197200003
gidNumber: 1197200003
krbPasswordExpiration: 20190722144638Z
krbLastPwdChange: 20190722144638Z
krbExtraData:: AAJOzDVdcm9vdC9hZG1pbkBDT0FTVExBQi5MT0NBTAA=
mepManagedEntry: cn=ldapy,cn=groups,cn=accounts,dc=coastlab,dc=local
memberOf: cn=ipausers,cn=groups,cn=accounts,dc=coastlab,dc=local
memberOf: cn=apic,cn=groups,cn=accounts,dc=coastlab,dc=local
krbLoginFailedCount: 0
The APIC can now immediately identify which group(s) a user is a member of during authentication without an additional search/lookup.