Slapi Membership


Overview


In LDAP an entry that is a static group has a name and a list of members. The members are the values of a membership attribute (e.g. ‘member’, ‘uniquemember’, ‘manager’,…) with DistinguishedName syntax. For many client applications it is convenient to retrieve the groups (direct or indirect) that a given entry belongs to. As a consequence the server implements a way to retrieve those groups (DN) in an efficient way, via memberof attribute managed by memberof plugin. Not only client applications need to retrieves those groups but also others plugins. For example, referential integrity plugin, ACI plugin and of course memberof plugin also need that. At the moment each plugin implement its own mechanism to retrieve those groups, this design is to create a new plugin API interface slapi_memberof() to return those groups (DN) and evaluate if/how existing plugins (memberof, referential integrity and ACI) can reuse it.

This new plugin API function is also required by a futur implementation of Inchain plugin (1.2.840.113556.1.4.1941)

Use cases


A plugin uses slapi_memberof to retrieve the list of entries that refer to a given entry. It specifies

Upon success, slapi_memberof returns a couple of valueset with nsuniqueid/dn of the referring entries.

Configuration changes


N/A

Design


membership

In LDAP a group entry has a name and contains a membership relation to its members via attributes (e.g. ‘member’, ‘uniquemember’, ‘manager’,…) with DistinguishedName syntax. The reverse relation from a member to the set of groups it belongs to (directly or indirectly) is also maintained via an DistinguishedName syntax attribute (e.g. ‘memberof’, ‘ismemberOf’).

In 389ds the reverse relation use a single attribute memberof, although we can imagine in other implementations to subtype it to reflect the original relation like ‘memberof;member’ or ‘memberof;uniquemember’.

base membership

Let call base membership the relation between a single given (base) entry to the groups it belongs to.

base membership uses a filter like ‘(membership_attr=target_entry)’.

subtree direct membership

In some specific cases (referential integrity) we want to retrieve all the groups that a set of entries belongs to.

When an entry is renamed, the entries in the subtree of that top entry are also renamed. The subtree direct membership is to retrieve, for a set of entries in a given subtree, the groups that the set of entries is direct member.

subree direct membership uses a filter like ‘(membership_attr=* target_entry)’.

Write vs read

In 389DS, the reverse membership relation (i.e. memberOf, ismemberOf) is stored in the attribute values of the member entry. Another approach is to compute the values on the fly (via virtual attribute) for example in ODSEE (ismemberOf).

Storing the value improves READ response time but slow down WRITE. Computing the value improve WRITE and slow down READ. Reverse membership needs to be computed anyway. Both approaches have benefit/loss. This design assumes that ‘memberof’ attribute is a real attribute stored in the entry.

limits and risks

The computation of all the groups, referencing a given entry, can be expensive. It uses internal searches, caches intermediate data, compare DN/scopes… all this eats CPU and memory.

The server protects itself from too large set of groups referencing an entry. If the server reach the limit of 100.000 groups it interrupts the computation. This limit can be tune with nsslapd-maxgroup-membership.

The caller can also limit the cost of the call and specify the maximum number of groups that will be returned. Caller specifies maxgroups in Slapi_MemberOfConfig, so that when it reaches the limit then the computation is interrupted when .

When the computation is interrupted because it hits a limit, the caller is notified with maxgroupsReached=True boolean in Slapi_MemberOfResult.

Except the risk of high cost of computation, another risk is that it exists loops in references. The loop can be direct A –> B –> A or indirect A –> B –> C –>… –> A. This risk is solved with the use of an ancestors cache (already_seen_ndn_vals) that keeps, for any membership paths all the already visited groups. This cache is currently implemented in memberof plugin.

slapi_memberof interface

typedef enum {
    REUSE_ONLY,
    REUSE_IF_POSSIBLE,
    RECOMPUTE
} memberof_flag_t;

typedef struct _slapi_memberofresult {
    Slapi_ValueSet *nsuniqueid_vals;
    Slapi_ValueSet *dn_vals;
    PRBool maxgroupsReached;
} Slapi_MemberOfResult;

typedef struct _slapi_memberofconfig
{
    char *groupattr;
    PRBool subtree_search;
    PRBool allBackends;
    Slapi_DN **entryScopes;
    Slapi_DN **entryScopeExcludeSubtrees;
    PRBool recursive;
    int maxgroups;
    memberof_flag_t flag;
    char *error_msg;
    int errot_msg_lenght;
} Slapi_MemberOfConfig;



int slapi_memberof(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, Slapi_MemberOfResult *result)

description

Slapi_memberof retrieve all entries that are directly or indirectly referring to

arguments

groupattr is the attribute name. It is used, along with subtree, to build the filter matching the referring entries. If subtree is False the filter looks like “=”. If **subtree_search** is True then the filter is ”=*”.

For a given member_sdn, Slapi_memberof retrieves referring entries in the same suffix where member_sdn is. If allBackend is True, it retrieves referring entries in all the backends. entryScopes and entryScopeExcludeSubtree are used to check that entries (referred and referring) are considered or not. If the entry is in entryScopeExcludeSubtree, it is ignored. Else the server search referring entries in the full suffix or in entryScopes if it only part of the suffix.

When recursive is False, it retrieves the directly referring entries to member_sdn. Else it retrieves the directly and indirectly referring entries. For example U1 is referenced by G1 and G1 is referenced by G2, if recursive=True then it returns G1 and G2 else it returns G1.

If slapi_memberof is called with groupattr that is identical to memberofgroupattr in memberof plugin then there is a possibility to speed up slapi_memberof computation using memberof attribute from member_sdn entry. The flag is used to specify if slapi_memberof should use or not memberof to retrieve referencing entries. It exists limitations to reuse memberof attribute:

The flag values are:

error_msg and error_msg_length are used to store a message explaining the reason of the failure of slapi_memberof

result

The result contains a valuset nsuniqueid and dn of the matching entries. The valuesets are ordered similarly, so the first nsuniqueid and first dn are related to the first entry. The second nsuniqueid and second dnare related to the second entry,… Those valuesets are allocated by slapi_memberof and the caller is responsible to free them. If needed the plugin can retrieve Slapi_Entry using slapi_search_get_entry.

returned code

Plugins potentially using slapi_memberof

Referential Integrity

When a target entry is deleted or renamed, the plugin updates the entries (groups) that are Directly refering to the target entry. The reference is stored in a DistinguishedName attribute. The plugin supports several refering attribute names. For example, by default member, uniquemember, owner and seeAlso.

referint update is done within original transaction or done later with a dedicated thread. Weither it is insync or not it uses update_integrity() to update the refering groups. slapi_memberof is called by update_integrity()

membership attributes

If entry DN_A is DEL, the plugins does as many internal searches as there are refering attributes. Then for each entries refering to DN_A, it MOD_DEL the value ‘refering_attribute_name: DN_A’)

If entry DN_A is MODDN into DN_B, the plugins does as many internal searches as there are refering attributes. Then for each entries refering to DN_A or some of entries is DN_A subtree, it MOD_DEL the value ‘refering_attribute_name: DN_A’ then *MOD_ADD the ‘refering_attribute_name: DN_B’)

So referencing entries are searched with a single membership attribute at a time

Scopes

The plugin updates references to the target entry at the condition the target entry belongs to target scopes and if referencing entries (aka static groups) belong to referencing scopes.

target scopes are related to Target entry. They are configured with ‘nsslapd-pluginEntryScope’ (multi-valued) and ‘nsslapd-pluginExcludeEntryScope’ (single valued). A target entry belongs to target scopes if it is not into ‘nsslapd-pluginExcludeEntryScope’ and in ‘nsslapd-pluginEntryScope’. By default, there is no ‘nsslapd-pluginExcludeEntryScope’ and any entry is in ‘nsslapd-pluginEntryScope’, so any target entry belongs to target scopes. Those configuration attributes are enforced before the call to update_integrity(). So target scopes are enforced before the call to update_integrity() and slapi_memberof.

referencing scopes are related to referencing entries. They are configured with ‘nsslapd-pluginContainerScope’ (single-valued). A referencing entries belongs to referencing scopes if it is under ‘nsslapd-pluginContainerScope’. By default, there is no ‘nsslapd-pluginContainerScope’ and scopes covers all suffixes, so any referencing entries belongs to referencing scopes. So if ‘nsslapd-pluginContainerScope’ is defined ‘entryScopes’ contains it unique value and ‘allBackends’ is false.

So membership is computed when searching for referencing entries and the scope is either defined by ‘nsslapd-pluginContainerScope’ or by the set of suffixes. The scope is used as base search during internal search.

key considerations

So for referential integrity plugins key considerations are

In conclusion, referential integrity can use slapi_memberof.

ACL Plugin

When access control is evaluated, it selects which ACI apply before evaluating them. During the selection it checks the bind rule against the bound entry. bind rule ‘groupdn’ requires to evaluate if the bound entry is member of groups. If ‘groupdn’ uses a filter several groups are evaluated, else only one. DS_LASGroupDnEval check (acllas_eval_one_group/acllas__user_ismember_of_group) if the bound entry is a direct member. It iterates with some limitations of nesting (5 by default SLAPD_DEFAULT_GROUPNESTLEVEL) and the total number of evaluated groups (50 by default ACLLAS_MAX_GRP_MEMBER). When a group is retrieved, it is stored into a per operation cache.

It iterates through members and nested members using internal search with the following fixed constraints.

The membership for ACI is defined with static definitions. membership attributes are: member uniquemember memberURL memberCertificateDescription. The static groups matches this filter “( (objectclass=groupOfNames) (objectclass=groupOfUniqueNames)(objectclass=groupOfCertificates)(objectclass=groupOfURLs))”.

Matching entries are added into a cache info->memberInfo ( acllas__handle_group_entry)

Referencing entries are all stored into a cache (per operation) and not using internal search.

So for acl group key considerations are

In conclusion: For the following reasons, even if it is theoretically possible to use slapi_memberof, I think we should not use slapi_membership in ACL plugin.

Memberof Plugin

For FIXUP task, the server retrieves the entries to fixup with the filter set in the fixup task. For each of them it computes the groups it belongs to (memberof_get_groups) and finally fixup the members.

For MOD (memberof_modop_one_replace_r/memberof_fix_memberof_callback) the server finds any entry impacted by the MOD (look down). For each of them it computes the groups it belongs to (memberof_get_groups) and finally fixup the members.

For ADD of a new group, for each membership attribute, the servers finds (memberof_add_attr_list/memberof_fix_memberof_callback) all new members of the group including indirect members (look down). For each of them it computes the groups it belongs to (memberof_get_groups) and finally fixup the members.

For DEL of a group, the plugin acts first as referential integrity then fixup the membership. In a first phase (memberof_del_dn_from_groups) the server removes the reference to the target entry in all the groups that have the target entry as direct member (for all membership attributes). In a second phase, for each membership attribute, the server finds (memberof_del_attr_list/memberof_fix_memberof_callback) any member of the group including indirect members(look down). For each of them it computes the groups it belongs to (memberof_get_groups) and finally fixup the member.

For MODRDN of a group, the plugin fixup the membership then acts as referential integrity. In a first phase, for each membership attribute, the server finds (memberof_moddn_attr_list/memberof_fix_memberof_callback) any member of the renamed group including indirect members (look down). For each of them it computes the groups it belongs to (memberof_get_groups) and finally fixup the member. In a second phase (memberof_del_dn_from_groups/memberof_replace_dn_from_groups) the servers updates the reference to the target entry in all the groups that have the target entry as direct member (for all membership attributes).

membership attributes

The membership attributes can contain several attributes that are defined in the configuration entry ‘memberOfGroupAttr’.

scopes

scopes are related to a group entry (entry that is listing its members). By defaults scopes are limited to the backend where the target group entry is located, so if there are several backends then membership is not updated if target group entry and referred member entry are in different backends. In order to extend the scopes to all backends (not only the backend of the target entry), the configuration attribute ‘memberOfAllBackends’ must be set to ‘on’ (it is ‘off’ by default). If a backend/suffix is a sub-suffix of parent suffix and if the target group entry is in parent suffix then referred member entries are updated even if they are into the sub-suffix.

scopes They are configured with ‘memberOfEntryScope’ (multi-valued) and ‘memberOfEntryScopeExcludeSubtree’ (multi-valued). A group entry belongs to scopes if it is not into ‘memberOfEntryScopeExcludeSubtree’ and in ‘memberOfEntryScope’. By default, there is no ‘memberOfEntryScope’ and no ‘memberOfEntryScopeExcludeSubtree’, so by default scopes covers all suffixes and any group entry belongs to the scopes.

key considerations

For memberof plugin key considerations are

In conclusion memberof plugin can use slapi_memberof with

Tests


Should run successfully

Reference


Author


tbordaz@redhat.com

Last modified on 24 January 2023