IPA has a plugin which replaces a known value (ipaUniqueID=autogenerate) with a randomly generated UUID (ipaUniqueID=autogenerate). It currently executes in preoperation and internalpreoperation.
In the case of preoperation, the access controls are evaluated after the UUID plugin makes the change. This means that the access controls are useless for any attribute randomly generated by the UUID plugin. You should be able to create an ACI that restricts access to the known value only.
Here is a real world case. To allow users to create tokens, we have an ACI like this:
aci: (target = "ldap:///ipatokenuniqueid=*,cn=otp,$SUFFIX")(targetfilter
"(objectClass=ipaToken)")(version 3.0; acl "Users can create self-managed
tokens"; allow (add) userattr = "ipatokenOwner#SELFDN" and userattr =
"managedBy#SELFDN";)
The problem is that this permits a user to create an entry with any value. If ACIs were evaluated before the UUID plugin and not afterward, we could use this ACI:
aci: (target = "ldap:///ipatokenuniqueid=autogenerate,cn=otp,$SUFFIX")(targetfilter
= "(objectClass=ipaToken)")(version 3.0; acl "Users can create self-managed tokens";
allow (add) userattr = "ipatokenOwner#SELFDN" and userattr = "managedBy#SELFDN";)
For add operations, ACIs are currently evaluated after bepreop but before betxnpreop. Unfortunately, we cannot change DN/ipaTokenUniqueID in betxnpreop. For modify operations, the ACIs are currently evaluated before bepreop. One possible solution would be to move the add operation behavior to be the same as the modify operation.
See also https://github.com/389ds/389-ds-base/issues/1256
389-ds-base code: main on 2015/11/11 nearly equivalent to 389-ds-base-1.3.4.4
We would like to have the consistent order for the plug-in hooks and the acl evaluation. Currently, just ldbm_modify has the requested order.
ldbm_add
320 rc = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_ADD_FN);
724 ldap_result_code = plugin_call_acl_plugin(pb, e, NULL, NULL, SLAPI_ACL_ADD, ACLPLUGIN_ACCESS_DEFAULT, &errbuf);
ldbm_modify
582 if ((ldap_result_code = plugin_call_acl_mods_access( pb, e->ep_entry, mods, &errbuf)) != LDAP_SUCCESS )
601 opreturn = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_MODIFY_FN);
ldbm_modrdn
445 rc = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_MODRDN_FN);
602 ldap_result_code = plugin_call_acl_plugin(pb, newparententry->ep_entry, NULL, NULL, SLAPI_ACL_MODDN, ACLPLUGIN_ACCESS_DEFAULT, &errbuf);
610 ldap_result_code = plugin_call_acl_plugin (pb, newparententry->ep_entry, NULL, NULL, SLAPI_ACL_ADD, ACLPLUGIN_ACCESS_DEFAULT, &errbuf );
663 ldap_result_code = plugin_call_acl_plugin(pb, ec->ep_entry, NULL /*attr*/, NULL /*value*/, SLAPI_ACL_WRITE, ACLPLUGIN_ACCESS_MODRDN, &errbuf);
ldbm_delete
307 retval = plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_DELETE_FN);
440 ldap_result_code = plugin_call_acl_plugin (pb, e->ep_entry, NULL, NULL, SLAPI_ACL_DELETE, ACLPLUGIN_ACCESS_DEFAULT, &errbuf );
ldbm_delete has this comment added to plugin_call_acl_plugin.
/* JCMACL - Shouldn't the access check be before the has children check...
* otherwise we're revealing the fact that an entry exists and has children */
This is considered as a justification of plugin_call_acl_plugin located at the position.
But the test results show the opposites.
Assume “uid=tuser0” has no right on ou=outest,dc=example,dc=com.
With the current order, which reveals the info the entry has descendants:
$ ldapdelete ... -D 'uid=tuser0,ou=People,dc=example,dc=com' -W
ou=outest,dc=example,dc=com
ldap_delete: Operation not allowed on non-leaf (66)
If plugin_call_acl_plugin is called prior to the children check and SLAPI_PLUGIN_BE_PRE_DELETE_FN as requested, which does not reveal the children info:
$ ldapdelete ... -D 'uid=tuser0,ou=People,dc=example,dc=com' -W
ldap_delete: Insufficient access (50)
additional info: Insufficient 'delete' privilege to delete the entry 'uid=tuser0,ou=outest,dc=example,dc=com'.
Question: Any other use cases that proves the current order is safer?
When the replication is enabled and the operation causes a conflict, the DN in the target entry could be modified in plugin_call_plugins(pb, SLAPI_PLUGIN_BE_PRE_ADD|MODRDN_FN). For instance, “uid=tuser,ou=subsubou,ou=subou,ou=ou,dc=suffix” could be converted to “nsuniqueid=######+uid=tuser,ou=subsubou,nsuniqueid=######+ou=subou,ou=ou,dc=suffix”. The conflict DN does not follow the ACI that the original DN is supposed to. Even if the DN is modified into the conflict DN, it should follow the planned access control.
Relocating “plugin_call_acl_plugin” call prior to BE_PRE plug-in.
The order change does not impact the acceptance test results.
Update: 2017-02-13
This has now been implemented in https://github.com/389ds/389-ds-base/issues/1256.
As per the notes, this does not change the acceptance test results.
We now execute ADD, MODIFY and DELETE with:
ACI Check
Call plugins
This means plugins are privileged and can act on behalf of a user: This is a good thing! It means a user doesn’t need access to other subtrees to create user private groups etc. It also means we exit sooner if an invalid add/mod/delete is provided, before we start work on plugins then having to roll back. This makes failures faster.