OpenLDAP Faq-O-Matic : OpenLDAP Software FAQ : Configuration : SLAPD Configuration : Access Control : More information about Access Control : Specifying the subject : Sets as "reversed groups" | |
(Original title: More on sets and how they can be used as 'reversed groups',
by Ace Suares, 2004-02-02)
Introduction The following comments have been tested with openldap 2.1.25 First of all, read 'Sets in Access Controls' written by Mark Valence, very carefully. Then, pay heed to the notice added by 'hyc@highlandsun.com'. If you write set="user.cn & [Babs Jensen]"you won't get nowhere. Instead, you have to use: set="user/cn & [Babs Jensen]"Secondly, for 2.1.25, slapd.access(5) man pages do not contain documentation on sets, the man page simply states: The statement set=<pattern> is undocumented yet.SUFFIX I write SUFFIX a lot of times in this document. It means that you may fill that in as you see fit. I use SUFFIX = 'qwidoApp=qwido'but someone else might use SUFFIX = 'dc=yourdomain,dc=com'and others might use SUFFIX = 'o=Some University,c=US'The value of SUFFIX is not important for understanding sets. A Tree Consider a tree consisting of ISP's, in the following fashion: isp=isp001,SUFFIX isp=isp002,SUFFIX isp=isp003,SUFFIXEvery ISP may have several domains, as follows: domain=example.com,isp=isp001,SUFFIX domain=example.net,isp=isp001,SUFFIX domain=example.org,isp=isp001,SUFFIXevery domain may have several services, for example: service=mail,domain=example.com,isp=isp001,SUFFIX service=ftp,domain=example.com,isp=isp001,SUFFIX< service=news,domain=example.com,isp=isp001,SUFFIXindividual users of a service might be detonated as: user=Danny Dynamite,service=mail,domain=example.com,isp=isp001,SUFFIX user=Karen Kruidvat,service=mail,domain=example.com,isp=isp001,SUFFIX user=Danny Dynamite,service=ftp,domain=example.com,isp=isp001,SUFFIX user=Karen Kruidvat,service=ftp,domain=example.com,isp=isp001,SUFFIXevery domain may need service managers, who can manage (edit, delete, add) users for one or more services. We may put these servicemanagers in a seperate branch: servicemanager=Fred Mastairs,domain=example.com,isp=isp001,SUFFIX servicemanager=Julia Ruberts,domain=example.com,isp=isp001,SUFFIX servicemanager=Ava Gartner,domain=example.com,isp=isp001,SUFFIXUsing a Group Now, we could declare 'service=mail' a group, and put the full DN's of the desired managers in the member attribute. Let's assume we want Fred and Julia to be able to manage the service 'mail', and Julia and Ava to be able to manage 'ftp'. Let's also assume we have named the member attribute 'servicemanagers'. If you're lost at this point, please read up on groups. The following pseudo entries will help us fullfill that desire. In real life, you would need to make appropriate schema's for all of this to work, or adapt the naming of attributes and DN's to match existing schema definitions. If you don't understand this at this point, please read up on schema's, naming violations, attribute, objectclas, oid and syntax. For now, just assume everything will work magically. (Hint: it doesn't) The follwing pseudo entries will help us fullfill that desire, like I said. dn: service=mail,domain=example.com,isp=isp001 objectclass: domain servicemanager: servicemanager=Fred Mastairs,domain=example.com,isp=isp001 servicemanager: servicemanager=Julia Ruberts,domain=example.com,isp=isp001 dn: service=ftp,domain=example.com,isp=isp001 objectclass: domain servicemanager: servicemanager=Julia Ruberts,domain=example.com,isp=isp001 servicemanager: servicemanager=Ava Gartner,domain=example.com,isp=isp001(I omitted 'SUFFIX' to avoid line breaks in this document)
Now we need to set up an ACL to allow the servicemanagers to edit the users. # here goes: access to dn.regex="^service=(.+),domain=(.+),isp=(.+),SUFFIX$" attrs=children by group/domain/servicemanager.regex="^service=$1,domain=$2,isp=$3,SUFFIX$" write by * noneYou must understand, that this is not, by far, a ACL that will work 'standalone'. Actually you need to give access to various parts of the tree, for instance for the servicemanager to be authenticated, and even for the servicemanger to be able to find out if he or she is in the group... ACL's always need a larger context in which they function. Read up as much on ACL's as you can. Start with the examples in the openldap admin guide, browse and search the Internet, and ask questions... However, the important point here is that servicemanager=Fred Mastairs,domain=example.com,isp=isp001once successfully logged in, requesting write access to user=Danny Dynamite,service=mail,domain=example.com,isp=isp001will succeed, because: There will be three substitutions in the access part (set your debugging level to an appropriate number (I use 65535) and you can see that, watch for 'nsub:'). Those substitutions are:
service=$1,domain=$2,isp=$3but due to the replacments, that group will be rewritten (and matched) to: service=mail,domain=example.com,isp=isp001 servicemanager=Fred Mastairs,domain=example.com,isp=isp001and that value is in the value list mentioned in C., 'write' will be the access level. In other words, servicemanager=Fred Mastairs,domain=example.com,isp=isp001has write access to user=Danny Dynamite,service=mail,domain=example.com,isp=isp001and that's what we wanted all along.
Because we used regular exprerssions (regex) and replacements ($1, $2, etc), this works also if servicemanager=Ava Gartner,domain=example.com,isp=isp001wanted access to user=Karen Kruidvat,service=ftp,domain=example.com,isp=isp001Just follow the steps A through D and do all the substitutions yourself...
But I started this document with a mention of 'sets' and 'reverse groups'. So, let's assume we have the same tree as in the previous example. However, this time we don't want the 'service' to be a group, but we want to put the name of the service as an attribute in the servicemanagers entry. So, a (pseudo) entry for a 'service' would look like: dn: service=mail,domain=example.com,isp=isp001and an entry for a servicemanager would look like: servicemanager=Julia Ruberts,domain=example.com,isp=isp001 service: mail service: ftp(I omitted objectclass here). Clearly, the entry for a servicemanager does not contain a group, because the member attribute of a group should contain DN's, not strings. So, what kind of access do we want to give now? # wrong access to dn.regex="^service=(.+),domain=(.+),isp=(.+),SUFFIX$" attrs=children by dn.regex="^servicemanager=.+,domain=$2,isp=$3,SUFFIX$" write by * noneThis ACL would give ALL servicemanagers (servicemanager=.+) write access to the children of every service (as long as they are both in the same domain and the same isp).
Note that the first (.+) in the 'access to' part, is not referenced in any 'by' part. That's not a problem, and I will leave it this way because it comes in handy in later examples. But, you might just as well have written: # wrong, but explains backrefenrences access to dn.regex="^service=.+,domain=(.+),isp=(.+),SUFFIX$" attrs=children by dn.regex="^servicemanager=.+,domain=$1,isp=$2,SUFFIX$" write by * noneYou can see that by taking away the parenthesis around the '.+' in 'service=(.+)', moves that ordinal number of the backreferences ($2 is now $1, $3 is now $2). (Off-topic: That's why I believe that in openldap, backreferences should be counted backwards, so the least specific backreference will always be $1, and a lot less editing goes on if you change your ACL's.) Anyway, just keep with the previous example: # wrong access to dn.regex="^service=(.+),domain=(.+),isp=(.+),SUFFIX$" attrs=children by dn.regex="^servicemanager=.+,domain=$2,isp=$3,SUFFIX$" write by * noneIf you where a servicemanager 'John Doe' within the domain 'example.com' hosted by isp 'isp001', you'd be able to add, delete and modify entries under any 'service' within 'example.com' at 'isp001'. That's not what we want - we want only some service managers to have access to some services. Documenting non-existent features A possible solution would be: # non-existent access to dn.regex="^service=(.+),domain=(.+),isp=(.+),SUFFIX$" attrs=children by ldapurl.regex="ldap://localhost:389/^domain=$2,isp=$3,SUFFIX$?sub?(&(objectclass=servicemanager)(service=$1))" write by * nonebut there is no such thing as ldapurl.regex in openldap 2.1.25...
# using sets, and it works... well, almost. access to dn.regex="^service=(.+),domain=(.+),isp=(.+),SUFFIX$" attrs=children by set="user/service* & [$1]" write by * nonein this 'set' statement, 'user' means the logged in user (one of the servicemanagers in this case).
'[$1]' means that the sting in 'service=(.+)' will be used, so if we wanted access to 'service=mail,domain=example.com,isp=isp001', the by clause would actually look like: by set="user/service* & [mail]" write(and it's worth some praise that the developers made this work!)
One more problem... But there is one problem: this 'set' matches EVERY user who has an attribute named 'service' with the value 'mail' (if we were accessing 'service=mail'). It would match servicemanagers from other domains, from other isp's, and it would even match any entry that had a 'service' attribute. But there is another way of specifying 'user', namely as a dn, like this: [servicemanager=Joe Blog,domain=example.com,isp=isp001]/serviceThis will also work with replacements, like this: [servicemanager=Joe Blog,domain=$2,isp=$3]/serviceHowever, as far as I know, and hope to be proven wrong by the developers, it doesn't work with regular expressions: # doesn't work: set="[servicemanager=.+,domain=$2,isp=$3]/service & [$1]" writeThere might be other ways of solving this particular problem, but this document was intended to shed some light on sets and what you can do with them. I hope it is usefull.
| |
This feature is considered Experimental.
| |
Reading Sets in Access Controls, using new URI expansion this would work:
access to dn.regex="^service=(.+),domain=(.+),isp=(.+),SUFFIX$" attrs=children by set="([ldap:///domain=$2,isp=$3,SUFFIX??one?servicemanager=*]/entryDN & user)/service & [$1]" write by * none bdauvergne@entrouvert.com | |
[Append to This Answer] |
Previous: | Sets in Access Controls |
|