The prometheus group system is based around two primitives – the role and the entitlement. We’ve been using a similar system in Informatics for years (we called entitlements “capabilities” – a term we’re trying to move away from, as a capability has a very specific meaning in authorisation literature). Simply put, a role is something you are (a person, a member of staff, a system administrator) and an entitlement is something that you can do (access webmail, log in to the compute cluster, edit the DNS).
A role contains a list of entitlements that having that role conveys upon that user (for example, the system administrator role might convey the “edit the DNS” entitlement). In addition, roles may contain other roles (a member of staff is also a person), in which case a user with that role also gains all of the entitlements (and roles) from the referenced role. Users may also be granted entitlements directly – this is implemented primarily to remove the need to create eponymous roles for single entitlements.
Services perform access control by checking a users possession of the appropriate entitlement.
In prometheus, roles and entitlements are represented as LDAP objects, with DN-valued attributes modelling the links between them. Entitlements maintain LDAP groupOfNames style lists of identities with that particular entitlement. In the current production DICE system entitlements are also made available as netgroups and, in a small number of cases, Unix groups. With prometheus, these additional entries will be produced through the conduit system into the production LDAP directory.
A diagram might make all of that a little clearer …
This shows a user, who has been given two roles (A & B), and who gains roles C and D through inheritance. The user gains a set of entitlements both through these roles, and also gains entitlement D by direct reference from the user object. I’ll come back to this layout as I discuss some of the challenges of maintaining this model.
The system has to convert this layout (and one containing many more users!) into a list, within each entitlement, of all of the users who have that entitlement. These lists have to be consistently maintained across all possible changes to the set of roles and user objects. Our current implementation performs this maintenance by regularly rebuilding the entire set using an external perl script. This results in significant propagation delays, as well as requiring a large amount of potentially unnecessary computation.
Complication 1: Negative Entitlements
The original proposal documents permit the creation of negative entitlements. A negative entry for a particular entitlement in a role, or user entry, blocks the user’s receipt of that entitlement, regardless of execution ordering. Any automatically generated entitlement list needs to take into account negative entitlements.
Implementation Issues
None of the OpenLDAP dynamic group, or memberOf, tools have sufficient power to solve this problem. memberOf comes close, but doesn’t have the nested group support we would require to evaluate down the entire tree. Even if member-of acquired nested group capabilities, it would still not be able to handle negation. So, any implementation is going to have to use custom tools. There are a number of options
- An independently triggered script which responds to applicable resource changes, and rebuilds the entire tree. This could be implemented as a prometheus conduit, and moves capability processing completely out of the main server loop. Pros are that this is relatively easy to implement, and it adheres to the prometheus model. Cons are that it adds an external dependency for ACL changes, and that it makes it impossible to reject changes which introduce loops when they are initially committed to the directory.
- An overlay which extends the current memberOf directive to support in-server updating of the entitlements list. Pros are that this would make it possible to reject changes which introduce loops, or break links, immediately they occur. Cons are that it is a significant body of code which would be running inside the directory server.
Regardless of the implementation mechanism, the question arises of how we perform the updates. The simplest way is to rebuild the entire tree with every change, but as noted earlier this is slow, and inefficient. A more efficient approach is to simply rebuild those sections of the tree which have been changed by the latest update. However, performing incremental changes without locking is really tricky. If we’re trying to rebuild the minimum in response to a change, then that requires that we know the state of the tree at the point that that change occurred. Both in, and out-of, directory solutions have the problem that other modifications may occur between the change, and us checking the state of the tree. An in-directory solution might be able to lock the tree in some way to queue further write actions behind it, but that’s a scary prospect.
More thought on this is definitely required. Initially, the temptation is to use an external rebuild everything on every change script, as that can be simply prototyped from the one currenly in use. As the project progresses it will be possible to experiment with better solutions.
Update on the way to dinner: A further thought that needs to be integrated into the above, is that roles and entitlements are just special types of nested group. A role is an internal group (that is, one which is actually nested), and an entitlement is a leaf group (that is, one at the edge of the tree, that contains no other groups). A memberof operator that works on nested groups would solve many of the problems outlined above.