(Answer) (Category) OpenLDAP Faq-O-Matic : (Category) OpenLDAP Software FAQ : (Category) Configuration : (Category) SLAPD Configuration : (Category) Access Control : (Category) More information about Access Control : (Category) Specifying the subject : (Answer) 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,SUFFIX
Every ISP may have several domains, as follows:
domain=example.com,isp=isp001,SUFFIX
domain=example.net,isp=isp001,SUFFIX
domain=example.org,isp=isp001,SUFFIX
every 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,SUFFIX
individual 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,SUFFIX
every 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,SUFFIX
Using 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.
The best way to do it is first try to define, in 'normal' language, what you want to achive, like this:

  • we want to give access to some service (service=.+) for some domain (domain=.+) within some isp (isp=.+).
  • the access that we need is that servicemanagers (those that are in the appropriate group) may create, delete and modify anything 'under' the service (attr=children).
  • we want to write 'one rule that fits all' so we are using regex (regular expressions) and replacements here (anything between parenthesis in the 'access to' part can be references by '$1', '$2' etc in the 'by' parts)
    # 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 * none
    You 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=isp001
    once successfully logged in, requesting write access to
    user=Danny Dynamite,service=mail,domain=example.com,isp=isp001
    will succeed, because:

  • A. user=Danny Dynamite is a child of service=mail,domain=example.com,isp=isp001, which makes the request match the 'access to' part.
    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:
    • 1. mail
    • 2. example.com
    • 3. isp001

  • B. there is a group that matches the regex part in the 'by' part. That group is:
    service=$1,domain=$2,isp=$3
    but due to the replacments, that group will be rewritten (and matched) to:
    service=mail,domain=example.com,isp=isp001
  • C. inside 'service=mail,domain=example.com,isp=isp001' resides an objectclass named 'domain' and an attribute named 'servicemanager', therefor group/domain/servicemanager will result in the follwoing values: servicemanager=Fred Mastairs,domain=example.com,isp=isp001 servicemanager=Julia Ruberts,domain=example.com,isp=isp001

  • D. since our friend Fred Mastairs logged in as
    servicemanager=Fred Mastairs,domain=example.com,isp=isp001
    and 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=isp001
    has write access to
    user=Danny Dynamite,service=mail,domain=example.com,isp=isp001
    and that's what we wanted all along.


    Replacements I

    Because we used regular exprerssions (regex) and replacements ($1, $2, etc), this works also if

    servicemanager=Ava Gartner,domain=example.com,isp=isp001
    wanted access to
    user=Karen Kruidvat,service=ftp,domain=example.com,isp=isp001
    Just follow the steps A through D and do all the substitutions yourself...


    Sets and revesre groups

    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=isp001
    and 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?

  • we want to give access to some service (service=.+) for some domain (domain=.+) within some isp (isp=.+).
  • the access that we need is that servicemanagers (those that have the proper string in the 'service' attribute) may create, delete and modify anything 'under' the service (attr=children).
  • we want to write 'one rule that fits all'... One way of doing this, could be:
    # wrong
    access to dn.regex="^service=(.+),domain=(.+),isp=(.+),SUFFIX$"
     attrs=children
     by dn.regex="^servicemanager=.+,domain=$2,isp=$3,SUFFIX$"
     write
     by * none
    This 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).


    Replacements II

    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 * none
    You 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 * none
    If 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 * none
    but there is no such thing as ldapurl.regex in openldap 2.1.25...


    Sets to the rescue...

    # using sets, and it works... well, almost.
    access to dn.regex="^service=(.+),domain=(.+),isp=(.+),SUFFIX$"
     attrs=children
     by set="user/service* & [$1]" write
     by * none
    in this 'set' statement, 'user' means the logged in user (one of the servicemanagers in this case).


    '/service' means to look at the 'service' attribute.

    '[$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!)


    the whole 'pattern' (also called 'setspec' in some later slapd.access man pages) can be translated into normal english as:

  • look at the attribute 'service' in the entry from the user requesting the access, and match it with the string 'mail', and if it matches, give write access.


    So, using set in this way, we have created 'reverse groups'.

    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]/service
    This will also work with replacements, like this:
    [servicemanager=Joe Blog,domain=$2,isp=$3]/service
    However, 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]" write
    There 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.


    Ace Suares (www.qwido.com).



    ando@sys-net.it

  • 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: (Answer) Sets in Access Controls
    This document is: http://www.openldap.org/faq/index.cgi?file=1134
    [Search] [Appearance]
    This is a Faq-O-Matic 2.721.test.
    © Copyright 1998-2013, OpenLDAP Foundation, info@OpenLDAP.org