Migration Strategy From slapodynlist
Table of Contents
This article describes the autogroup, memberOf, nestgroup, and alias overlays, and which would be most appropriate based on the needs of the OpenLDAP installation. Most sites will not need all four. In particular, the alias overlay is likely not needed. We've included it because it's instructional and useful when more than one reverse lookup attribute is needed for the same mapping.
Releases
- 2.5.18 - slapo-memberOf (bug fixes only)
- 2.6.8 - slapo-memberOf(bug fixes+improvements), slapo-autogroup, slapo-nestgroup, slapo-alias
Usage History
Back when OpenLDAP 2.4 was still being maintained, the slapo-memberOf overlay was in wide use. The overlay would automatically maintain reverse group membership on user entries based on statically defined group memberships. Toward 2.4's end-of-life (EOL), complications with reliability in replicated environments led the project to deprecate its usage. A new approach, where reverse memberships are generated dynamically was introduced, called slapo-dynlist. It was recommended that all 2.5/2.6 installations use dynlist.
dynlist was more than just a replacement for memberOf. It also supports dynamic group and list maintenance. While this works fine, in large installations, having many thousands of groups (and members), reports of performance problems were received.
This is when Symas brought into service an existing contrib module, called slapo-autogroup. It renders dynamic groups into static entries during update processing. This overlay was improved and enabled within Symas OpenLDAP builds. In terms of being a complete slapo-dynlist replacement, another module was needed since 2.5 dynlist also supports group nesting. The slapo-nestgroup was created for this. At the same time, the reliability problems with memberOf were fixed and the entire solution was tested extensively by the Symas development team to ensure that its capabilities were consistent and inline with dynlist.
What we have in place since Symas OpenLDAP 2.6.8 is an alternative of the slapo-dynlist module. One that brings orders of magnitude improvement in search performance (of large groups) over dynlist. As always, in our business, there's no free lunch. The search performance improvement comes at the cost of increased time to create and update the group membership during administration. Thus we have the classic trade off scenario:
If you need fast updates and can tolerate slower search performance, dynlist is the right solution. Otherwise, use autogroup and memberOf.
The autogroup functionality suite includes the following overlays:
slapo-memberOf
The memberOf overlay to slapd(8) allows automatic reverse group membership maintenance. Any time a group entry is modified, its members are modified as appropriate in order to keep a DN-valued "is member of" attribute updated with the DN of the group.
slapo-autogroup
The autogroup overlay to slapd(8) allows automated management of group memberships which match the LDAP URI specified in the group, just like as specified in dynamic groups. Any time an object is added / deleted / updated, it is matched against the URIs, and its membership is then stored so that read operations, ACLs, etc., can reference it without additional processing. For searches and compares, it behaves like a static group. If the attribute part of the URI is filled, the group entry is populated by the values of this attribute in the entries resulting from the search.
slapo-nestgroup
The nestgroup overlay to slapd(8) supports evaluation of nested groups in search operations. Support consists of four possible features:
- inclusion of parent groups when searching with (member=) filters
- inclusion of child groups when searching with (memberOf=) filters
- expansion of child groups when returning member attributes
- expansion of parent groups when returning memberOf attributes
Each of these features may be enabled independently. By default, no features are enabled, so this overlay does nothing unless explicitly enabled.
slapo-alias
One last module, a contrib module now shipped by default, called slapo-alias, is sometimes useful. Its job is to mirror an attribute under a different name. For example, memberOf can be mapped to ismemberOf, an attribute commonly used within Microsoft installations. The alias overlay can actually mimic any attribute value to another (under certain restrictions), but here we'll just be using it with memberOf.
The alias overlay to slapd(8) allows migrations for existing attributes exposed through a name that is now deprecated where using slapo-rwm(5) is not applicable. For this reason, the aliased attributes are not writable in any way.
Migrating from dynlist
When converting from dynlist usage which generates the data dynamically vs. autogroup / memberOf which stores the values in the database, an export of the LDAP data must be performed before it's reloaded using ldapadd.
Pros vs. Cons
dynlist
- dynamic processing when entries are searched
- fast writes
- slow search
autogroup
- dynamic processing when entries are updated
- slow writes
- fast search
Assumptions, Limitations, and Requirements
- Cannot mix static and dynamic groups in the same entry.
- Placing dynamically generated attributes in a dynamic group filter, e.g. uniqueMember=, memberOf=, is not allowed.
- As with ordinary static groups, update operations on groups with a large number of members may be slow.
- When converting from dynlist DB reloads are required for expansion of group members and their memberOf relationships.
Excluding memberOf Values From Replication
The memberOf values are maintained by the slapd process and must be excluded from replication. This is done by setting a flag in the syncrepl section of the slapd configuration (see the configuration section of this document).
olcSyncrepl: ... exattrs="memberOf ismemberOf"
More Info
slapo-autogroup.5
slapo-memberOf(5)
slapo-nestgroup.5
slapo-alias.5
Test Case
These overlays work across a wide variety of data formats.
autogroup Setup
In this example we're enabling dynamic groups using entries with labeledURIObject
objectClass and labeledURI
attribute as its filter. The target entry DNs (matching that filter) will be stored on the group entry using the member attribute.
autogroup Configuration Example
overlay autogroup
autogroup-attrset labeledURIObject labeledURI member
memberOf Setup
The memberOf overlay must be configured to a particular group data structure. For example, group objectClass can be defined as groupOfNames
that stores entry DNs on the member attribute. Here, we've chosen a source groupOfMembers
objectClass that also uses the member attribute. This test setup will project reverse group membership onto user entries using the memberOf attribute. The addcheck ensures group membership is updated anytime a user is added. The refint option should not be enabled in replicated environments (it's false by default).
overlay memberOf
memberOf-group-oc groupOfMembers
memberOf-member-ad member
memberOf-memberOf-ad memberOf
memberOf-addcheck true
memberOf-refint false
alias Setup
In this example we've chosen to mirror the generated memberOf values to the ismemberOf attribute. The alias overlay can only work when the schema matches exactly. Because memberOf is an operational attribute, the mirrored attribute must also be operational.
attributetype ( 1.3.6.1.4.1.18060.17.2112
NAME 'isMemberOf'
DESC 'Used to test slapo-alias overlay and memberOf overlays'
EQUALITY distinguishedNameMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
X-ORDERED 'VALUES'
NO-USER-MODIFICATION
USAGE dSAOperation )
Because we're adding an operational attribute we have to bring in the Dsaschema module to load that schema definition. We show how that is done later in the configuration section of this document.
Mirror the memberOf values to the ismemberOf attribute.
overlay alias
alias_attribute memberOf ismemberOf
nestgroup Setup
We briefly touched on the topic of nested groups earlier. They are used when one group is nested inside of another.
Here we're enabling nested groups under the ou=groups tree. All four features are enabled:
- inclusion of parent groups when searching with (member=) filters
- inclusion of child groups when searching with (memberOf=) filters
- expansion of child groups when returning member attributes
- expansion of parent groups when returning memberOf attributes
overlay nestgroup
nestgroup-member member
nestgroup-memberOf memberOf
nestgroup-base ou=groups,dc=example,dc=com
nestgroup-flags member-values
nestgroup-flags member-filter
nestgroup-flags memberOf-values
nestgroup-flags memberOf-filter
Test Data
A sample of the test data after it has been rendered by the autogroup and memberOf overlays. Notice how the filter (labeledURI) matches the user's data.
dc=example,dc=com
├─ou=people
│ ├─(user#1)
│ ├─uid: load01-R1H1-100
│ ├─cn: load01-R1H1-1000
│ ├─memberOf: cn=load01-R1H1-100,ou=Groups,dc=example,dc=com
│ ├─ismemberOf: cn=load01-R1H1-100,ou=Groups,dc=example,dc=com
│ ├─(every other matching group)
├───────────────────────────────
│ ├─(user#2)
│ ├─uid: foo
│ ├─cn: foo
│ ├─memberOf: cn=load01-R1H1-100,ou=Groups,dc=example,dc=com
│ ├─memberOf: cn=load01-R1H1-10000000,ou=Groups,dc=example,dc=com
│ ├─ismemberOf: cn=load01-R1H1-100,ou=Groups,dc=example,dc=com
│ ├─ismemberOf: cn=load01-R1H1-10000000,ou=Groups,dc=example,dc=com
│ ├─(every other matching group)
├─ou=groups
│ ├─(group#1 dynamic)
│ ├─cn: load01-R1H1-100
│ ├─objectClass: groupOfMembers
│ ├─objectClass: labeledURIObject
│ ├─labeledURI: ldap:///dc=example,dc=com??sub?(cn=load01-R1H1-100*)
│ ├─member: uid=foo,ou=people,dc=example,dc=com
│ ├─member: uid=load01-R1H1-1000,ou=people,dc=example,dc=com
│ ├─member: cn=load01-R1H1-10000000,ou=groups,dc=example,dc=com
│ ├─(every other matching entry)
├───────────────────────────────
│ ├─(group#2 static)
│ ├─cn: load01-R1H1-10000000
│ ├─objectClass: groupOfMembers
│ ├─member: uid=foo,ou=people,dc=example,dc=com
Notice how we have a member of our dynamic group: cn=load01-R1H1-10000000 that's actually another group. In this case it happens to be a static group, although dynamic groups may also be nested. The nestgroup overlay will return uid=foo as a member via searches.
The user foo is also a member of many other groups due to it being a member of the static group, cn=load01-R1H1-10000000, that also happens to match the filter (in our dynamic group definitions.) Was that the intention? If not, perhaps a different filtered attribute, uid, or a different base dn, ou=groups,dc=example,dc=com, would provide a more precise result set.
Take care when defining dynamic group URIs to ensure precise matching as it would be very easy to have unintended consequences. Extensive testing is required after defining dynamic groups, especially when nesting has been enabled.
It's also important to point out in this scenario we're nesting memberOf and not ismemberOf. So, when viewing the data on user entries there will be more groups listed under memberOf than ismemberOf which reflects the inclusion of nested groups in the result set (or not.) We are able to achieve the aliasing without nesting by placing the slapo-alias before slapo-nestgroup in the slapd configuration. If we moved slapo-alias after slapo-nestgroup the two result sets of memberOf and ismemberOf would be the same.
Sample Dynamic Group Entry Using autogroup
# ldif extract:
objectClass: groupOfMembers (structural)
objectClass: labeledURIObject (auxiliary)
cn: load01-R1H1-1
# This is the filter:
labeledURI: ldap:///dc=example,dc=com??sub?(cn=load01-R1H1-100*)
Sample User that has reverse group membership
objectClass: inetOrgPerson (structural)
cn: load01-R1H1-1000
uid: load01-R1H1-1000
...
Config Excerpt
Static
+++ A. modules:
modulepath /opt/symas/lib/openldap
# Needed to define ismemberOf because it's an operational attribute:
moduleload dsaschema /opt/symas/etc/openldap/schema/ismemberOf.schema
moduleload autogroup.la
moduleload memberOf.la
moduleload nestgroup.la
moduleload alias
+++ B. replication:
syncrepl
...
# Exclude memberOf values from replication:
exattrs="memberOf ismemberOf"
+++ C. overlays:
overlay autogroup
autogroup-attrset labeledURIObject labeledURI member
overlay memberOf
memberOf-group-oc groupOfMembers
memberOf-member-ad member
memberOf-memberOf-ad memberOf
# Every Add operation will search for groups referencing the added entry and populate the memberOf attribute with the group DNs.
memberOf-addcheck true
memberOf-refint false
overlay alias
alias_attribute memberOf ismemberOf
overlay nestgroup
nestgroup-member member
nestgroup-memberOf memberOf
nestgroup-base ou=groups,dc=example,dc=com
nestgroup-flags member-values
nestgroup-flags member-filter
nestgroup-flags memberOf-values
nestgroup-flags memberOf-filter
Dynamic
...
+++ A. modules loads:
olcModuleLoad: {6}dsaschema /opt/symas/etc/openldap/schema/ismemberOf.schema
olcModuleLoad: {7}autogroup.la
olcModuleLoad: {8}memberOf.la
olcModuleLoad: {9}nestgroup.la
olcModuleLoad: {10}alias
+++ B. replication:
olcSyncrepl:
...
# Exclude memberOf values from replication:
exattrs=memberOf,ismemberOf
+++ C. overlays:
dn: olcOverlay={3}autogroup,olcDatabase={3}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcAutoGroupConfig
olcOverlay: {3}autogroup
olcAutoGroupAttrSet: {0}labeledURIObject labeledURI member
structuralObjectClass: olcAutoGroupConfig
dn: olcOverlay={4}memberOf,olcDatabase={3}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcMemberOfConfig
olcOverlay: {4}memberOf
olcMemberOfDangling: ignore
olcMemberOfRefInt: FALSE
olcMemberOfGroupOC: groupOfMembers
olcMemberOfMemberAD: member
olcMemberOfMemberOfAD: memberOf
olcMemberOfAddCheck: TRUE
olcMemberOfRefInt: FALSE
structuralObjectClass: olcMemberOfConfig
dn: olcOverlay={5}alias,olcDatabase={3}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcAliasConfig
olcOverlay: {5}alias
olcAliasMapping: memberOf isMemberOf
structuralObjectClass: olcAliasConfig
dn: olcOverlay={6}nestgroup,olcDatabase={3}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcNestGroupConfig
olcOverlay: {6}nestgroup
olcNestGroupBase: ou=groups,dc=example,dc=com
olcNestGroupFlags: member-values
olcNestGroupFlags: member-filter
olcNestGroupFlags: memberOf-values
olcNestGroupFlags: memberOf-filter
structuralObjectClass: olcNestGroupConfig
Perform test searches to verify
Ensure proper setup via running searches that cover use cases that are familiar. Start simple. Check the counts. If using replication, be sure to verify the counts are exactly the same on each node.
Search across users:
ldapsearch ... '(uid=*)' memberOf | grep -i memberOf: | wc -l
ldapsearch ... '(uid=*)' ismemberOf | grep -i ismemberOf: | wc -l
ldapsearch ... '(uid=*)' memberOf | grep -i memberOf: | wc -l
Search the groups:
ldapsearch ... '(cn=*)' member | grep -i member: | wc -l