LDAP Advanced Administration for Enterprises…

OpenDJ directory server has one default administrator that can manage all aspects of the server.

In an earlier post, I’ve described how to create multiple administrative accounts in OpenDJ, and in another one, I’ve talked about the Privilege system and how it can be used to tailor the administrative roles of each account.

In most enterprises, administrators are usually employees and therefore have their own entries and password. For auditing purpose, security processes require that a change or an administrative task on the directory be done as the true person and not the administrative account. But often there are multiple administrators, and they can change role frequently. So what is the best practice for granting employees some administrative privileges ?

An efficient and manageable way  is to create an Administrators’ group and grant the privileges to all members of that group. When an employee, is no longer administrator, simply remove him from the group and he will loose all privileges associated. Likewise, adding a new administrator is just adding a member in the group.

With OpenDJ, this can be done with 2 simple entries : a group and a privilege collective attribute subentry.

The Group :

dn: cn=Administrators,ou=Groups,dc=example,dc=com
objectClass: groupOfNames
objectClass: top
description: LDAP Administrators Group
cn: Administrators
member: uid=ludo,ou=People,dc=example,dc=com
member: uid=Matt,ou=people,dc=example,dc=com

The Collective Attribute Subentry :

dn: cn=Administrators Privilege,dc=example,dc=com
objectClass: extensibleObject
objectClass: collectiveAttributeSubentry
objectClass: top
objectClass: subentry
cn: Administrators Privilege
ds-privilege-name;collective: config-read
ds-privilege-name;collective: config-write
ds-privilege-name;collective: ldif-export
ds-privilege-name;collective: modify-acl
ds-privilege-name;collective: password-reset
ds-privilege-name;collective: proxied-auth
subtreeSpecification: {base "ou=people", specificationFilter
  "(isMemberOf=cn=Administrators,ou=groups,dc=example,dc=com)" }

How does it work ?

Collective Attributes is a standard based LDAP functionality that allows to define attributes and value that are defined once and appear in all entries that match the subtreeSpecification. Collective Attributes are defined in RFC 3671. For those who are familiar with Sun Directory Server’s Class Of Service, Collective Attributes provide a similar function, but based on industry approved standard.

OpenDJ collective attributes feature supports a few extensions to facilitate their use.

First, the standard way to define a collective attribute is to define it in the schema with a “c–” prefix. With OpenDJ, any existing attribute can be defined as collective with the ;collective attribute option.

Second, the scope of a Collective Attribute subentry as defined by the standard is a subtree, but the only filter possible is to specify the object class it applies to. We’ve extended the specificationFilter to accept an arbitrary LDAP filter, allowing a finer grained control of which entries are targeted.

In the example above, the filter is used to restrict the Privilege Collective Attribute subentry to apply only to entries that have the isMemberOf attribute with the value “cn=Administrators,ou=Groups,dc=example,dc=com”.

IsMemberOf is an operational read-only attribute (virtual) that is a back-link to the groups a user belongs to. OpenDJ does support the isMemberOf attribute for static groups, nested static groups and dynamic groups.

The subtreeSpecification also contains a base “ou=people” to restrict the targeted entries to the ou=people subtree. There are additional field allowed in the subtreeSpecification to indicate a depth in the tree for example.

As a result, collective attribute subentries, combined with groups, provide a flexible way to “inject” attributes and values to a specified set of entries, either to grant them specific privileges like in our example, or to decorate entries based on some common properties.

This said, remember that privileges are set in addition to the Access Controls. So giving a user the password-reset privilege for example, will be useless if there is no ACI allowing him or her to modify the userPassword attribute of other users. Granting access through an ACI to a group is as simple as using groupdn=”ldap:///cn=Administrators,dc=example,dc=com”;  to designate the authorized identities.

12 thoughts on “LDAP Advanced Administration for Enterprises…

  1. Matt 04 May 2011 / 12:03

    Thanks Ludovic, great timing. Just had an issue crop up because we had individual users permissioned as a tempoary measure. Now we can use this post as reference with our existing group structure.

    • Ludo 04 May 2011 / 17:03

      You’re welcome Matt. I’m happy to be of help. I think I had a similar organization as yours when I wrote that article (and I’ve just successfully tested the solution after some discussion with another user of our OpenDJ LDAP directory server).

  2. Vladimir Dzhuvinov 07 May 2011 / 12:51

    Thank you Ludo for this educational piece 🙂

    I’ve got an off topic question about “extensibleObject”. I consider using it to allow web clients to store various config/preference data in user entries without having to define a custom schema for that. Is this a good use of extensibleObject? Any implications I should bear in mind?

    PS: base being just “ou=people”, is this meant to mean relative to the collective object DN? Can “full” DNs be entered too?

    • Ludo 10 May 2011 / 15:00

      Yes, the base being just “ou=people” means it’s relative to the Collective object DN (actually it’s parent). Full DNs are not supported in order to avoid defining collective attributes that are outside the administrative domain.

      With regards to “extensibleObject”, I tend to dislike its use, as it defeats the goal of a schema, i.e. structuring and being able to understand the content of objects and attributes. Also, when using extensibleObject, the entry may contain any attribute, but attributes still need to be defined to have their syntax checked or to be indexed (although undefined attributes default to a DirectoryString with Equality matching).
      Sun products overcome this challenge by using sunkeyvalue attribute where each value was made of the key=value pair. May be this is a preferred model.

  3. Vladimir Dzhuvinov 12 May 2011 / 19:15

    Having key/value pairs inside a value doesn’t look particularly beautiful to me 🙂

    Schemas are good for enforcing structure and typing. But at the same time my general observations over the software world show that less structured approaches are not only diminishing, but actually doing quite well. In many aspects.

    Java, for instance, was meant do bring a lot of structure and safety to programming by being nicely typed. But at the same time weakly typed languages like JavaScript and Python are also proliferating.

    Look also at the increasing use of JSON for APIs and messages on web, despite the fact that XML was meant to be the universal format.

    The semantic web is also having trouble replacing the current messy and “unstructured” web 🙂

    • Ludo 12 May 2011 / 19:38

      I agree with you on both point. The key/value pairs inside a value is not the best design, and prevents from differentiating different applications for example.
      The less structured approaches are definitely increasing, and we’re definitely looking into that and JSON…

      However, the extensibleObject is also influencing the way you can query the objects via LDAP, and forces the application to read the full object as it’s impossible to know what attributes are in the entry. It also prevents from using the @ObjectClass notation as well (See RFC 4529).
      One application will know what it stores in the entry with the extensibleObject, but the other apps cannot leverage nor understand the data.
      It also prevents applications that are strict on schema to update these entries with extensibleObject. As a result, I think it is fine to use extensibleObject for dedicated entries, but do not use it to decorate structured objects like users or hosts.

  4. Achim Reckeweg 01 February 2012 / 12:56

    Hi Ludo,

    I wonder if the following behaviour is an error or if I missed something.
    I set up the following example:

    dc=example,dc=com
    ou=people
    uid=U0001

    ou=peopleSub
    uid=peopleSubAdmin

    o=testOrg
    ou=groups
    cn: read-write-access
    member: uid=rw_admin,ou=technical,ou=users,o=testorg,dc=example,dc=com

    cn=personalizedAdmin
    member: uid=U0001,ou=people,dc=example,dc=com
    member: uid=peopleSubAdmin,ou=peopleSub,ou=people,dc=example,dc=com
    ou=users
    ou=technical
    cn: rw_admin

    I added an extensibleObject to the directory base
    dn: cn=personalizedAdmin Privilege,dc=example,dc=com
    cn: personalizedAdmin Privilege
    ds-privilege-name;collective: config-read
    ds-privilege-name;collective: config-write
    ds-privilege-name;collective: ldif-export
    ds-privilege-name;collective: modify-acl
    ds-privilege-name;collective: password-reset
    ds-privilege-name;collective: proxied-auth
    ds-privilege-name;collective: unindexed-search
    ds-rlim-lookthrough-limit;collective: 10000
    ds-rlim-size-limit;collective: 10000
    objectClass: extensibleObject
    objectClass: collectiveAttributeSubentry
    objectClass: subentry
    objectClass: top
    subtreeSpecification: {base “ou=people”, specificationFilter “(isMemberOf=cn=personalizedAdmin,ou=groups,o=testorg,dc=example,dc=com)” }

    and another extensibleObject to o=testorg,dc=example,dc=com
    dn: cn=Remove Administrator Search Limits,o=testorg,dc=example,dc=com
    cn: Remove Administrator Search Limits
    ds-privilege-name;collective: password-reset
    ds-privilege-name;collective: unindexed-search
    ds-privilege-name;collective: config-read
    ds-privilege-name;collective: config-write
    ds-privilege-name;collective: ldif-export
    ds-privilege-name;collective: modify-acl
    ds-privilege-name;collective: proxied-auth
    ds-rlim-lookthrough-limit;collective: 0
    ds-rlim-size-limit;collective: 0
    ds-rlim-time-limit;collective: 0
    objectClass: extensibleObject
    objectClass: collectiveAttributeSubentry
    objectClass: subentry
    objectClass: top
    subtreeSpecification: {base “ou=users”, specificationFilter ” (|(isMemberOf=cn=technical,ou=groups,o=testorg,dc=example,dc=com) (isMemberOf=cn=system,ou=groups,o=testorg,dc=example,dc=com) (isMemberOf=cn=read-only-access,ou=groups,o=testorg,dc=example,dc=com) (isMemberOf=cn=read-write-access,ou=groups,o=testorg,dc=example,dc=com)) ” }

    Now the accounts “uid=U0001” and “uid=peopleSubAdmin” from “ou=people” and below do get the permissions assigned by the personalizedAdmin Privilege extensibleObject where the “uid=rw_admin” from “ou=technical,ou=users,o=testorg,dc=example,dc=com” don’t get the permissions from the extensibleObject “cn=Remove Administrator Search Limits” assigned.

    I browsed through the RFC 3671 already and my understanding is that I can define a subentry in every node and it is by default assigned to the whole tree addressed by “base” below it.

    Any hint?

    Achim

    • Ludo 01 February 2012 / 15:45

      Hi Achim,

      Yes you should be able to define a SubEntry on any node, and it’s by default assigned to the whole subtree addressed by “base”.
      However, you constrained the SubEntry to match another criteria within that subtree, that the users must be a member of one of a set of groups.
      I did notice some “typo”, somewhere you mention uid=rw_admin and in the tree description you refered to it as cn=rw_admin.
      Could it be the problem ?
      Have you tried to read the rw_admin entry with its “isMemberOf” attribute ?

      Kind regards,

      Ludovic.

      • Achim Reckeweg 01 February 2012 / 20:37

        Hi Ludo,

        thanks for the quick reply. I assume the typo to be a copy and paste error.

        But I double checked:

        rw_admin is the admin user
        dn: uid=rw_admin,ou=technical,ou=users,o=testorg,dc=example,dc=com
        cn: rw_admin
        uid: rw_admin
        isMemberOf: cn=read-write-access,ou=groups,o=testorg,dc=example,dc=com

        He is member in the above group:
        dn: cn=read-write-access,ou=groups,o=testorg,dc=example,dc=com
        cn: read-write-access
        member: uid=rw_admin,ou=technical,ou=users,o=testorg,dc=example,dc=com

        and my constraint in the
        subtreeSpecification: {base “ou=users”, specificationFilter ”
        (|(isMemberOf=cn=technical,ou=groups,o=testorg,dc=example,dc=com)
        (isMemberOf=cn=system,ou=groups,o=testorg,dc=example,dc=com)
        (isMemberOf=cn=read-only-access,ou=groups,o=testorg,dc=example,dc=com)
        (isMemberOf=cn=read-write-access,ou=groups,o=testorg,dc=example,dc=com)) ”
        }
        matches the group

        What really drives me crazy is that the other extensibleObject in the directory base is working as expected.

        Achim

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s