This post is part of a series on directory services. Current available installments are:
- Introduction
- Terminology
- Basic concepts
- Designing the DIT
- Setting up an LDAP server
- Securing your LDAP server
- Writing and testing ACLs
I apologise for the long delay between posts. Life took over for a while and I never got around to writing the rest of it.
Sitting down and thinking a bit about the DIT upfront can save you endless hours of furstration later on.
There’s few rules as to what to do and what not to do and lots of contradictory advice on the topic. This is my set of rules:
- Do NOT model organizational hierarchy as container types (i.e organizational units). Organizations are living organisms, they change and restructure regularly. You don’t want that kind of churn as it will cause the DNs of all kinds of objects to change with it.
- Ensure DN’s remain static for the lifetime of an object. This goes hand in hand with the above. Once a user object is created, their DN should not change. Same goes for any object. This ensures you can always and correctly refer to an object the same way and avoids all kinds of complications with external systems that might sync information from LDAP into a local database.
- Define what objectClasses entities under a specific part of the tree should have. This ensures that everyone in an organizational unit has the same set of objectClasses, avoiding all kinds of nastiness if you need to do programatic things on part of a hierarchy.
- Never, EVER EVER, allow or use
extensibleObject
. Adding it on an entity allows you to pick and use any attribute from any objectClass, without declaring that you use it. Essentially, attributes become invisible, you have to know that an attribute is available on an entity despite the objectClass not being there. All the useful stuff that you get by having a well defined entity with set objectClasses goes out the window.
This generally means that I structure a DIT like this. Adapt it to your needs, this is not set in stone:
dc=example,dc=com
, the root.-| cn=admin
, anorganizationalRole
whose members are grantedmanage
level access to everything in this DIT (more on ACLs later).-| ou=example
, anorganizationalUnit
with the name of the company or group. I usually reuse one of thedc
’s. I do this to ensure that if I ever need to combine multiple DITs together, I can root all objects at different pahts. Kinda how you’d have a host like example.com with /login, /about, /contact etc.---| ou=people,ou=accounts
, accounts for human beings, usuallyinetOrgPerson
withposixAccount
andshadowAccount
.---| ou=robots,ou=accounts
, accounts for system/applications, usuallyorganizationalRole
(this is not entirely semantically correct, but it’s the smallest object I could find that gives me thecn
attribute) andsimpleSecurityObject
(so I get access touserPassword
).---| ou=groups
, groups for either systems, humans or both,groupOfNames
paired withposixGroup
when necessary. One thing to note here is that if you run a regular OpenLDAP server without having loaded the RFC 2307bis schema, you won’t be able to pair aposixGroup
with agroupOfNames
as they are bothSTRUCTURAL
in that case. Most other directory services default to, or allow you to configure, the use of RFC 2307bis.---| ou=userPrivate,ou=groups
, *nix User Private Groups, one exists for every account inou=people,ou=accounts
.
As you can see no organizational structure is encoded in here. A user who starts
in finance isn’t creted in ou=Finance
, which would make that ou
part of their
DN
. Instead, they’re just uid=$username,ou=people,ou=accounts,ou=example,dc=example,dc=com
and no matter what role they have in the company, you can always universally refer
to their account that way.
Encoding organizational structure
So how do you represent organizational structure? There’s a multitude of ways you can do this.
You could have something like this:
ou=Finance,ou=deparments,ou=exampl,dc=example,dc=com
-| ou=some_sub_department,ou=Finance,...
However, the user accounts can’t exist in multiple parts of the tree, so the lowest
level you’ll end up with has to be a groupOfNames
or an organizationalRole
, so
you can make them members of that entity.
I personally prefer to solve this with nested groups. Most systems support this nowadays so instead I have something like this:
| ou=groups,ou=example,dc=example,dc=com
-| cn=finance,ou=groups,....
, agroupOfNames
-| cn=some_sub_department,ou=groups,...
, anothergroupOfNames
Then I make cn=some_sub_department
a member
of the finance
department. You
can choose to add users, groups or both at either level. Now if you want to know
who’s in finance you fetch cn=finance
and walk the tree, resolving all the
members.
Using groups has some other neat features, like being able to use the owner
attribute for example to encode who ‘owns’ this resource, a department head for
example.
One thing to be mindful of when taking this approach; you need to ensure you don’t create cyclic memberships. Essentially you want your DIT to be a Directed Acyclic Graph. Even if you don’t go for nested groups, this is a good thing to enforce.
Using nested groups isn’t terribly human friendly if you expect admins to be doing a lot of manual work in your DIT. The design here is very much geared towards assuming the lifecycle of most entities will be governed by automation. Storing the data in a directory service is largely an implementation detail (with some bonus features like easy integration with lots of enterprise software).
Roles or positions
Sometimes it’s useful to have a way to represent roles, say a group of managers,
engineers, an auditor or a financial controller. Often these can just be
represented as a groupOfNames
but I like keeping formal roles in a separte
part of the hierarchy, say ou=roles,ou=example,...
with organizationalRole
entities instead. These could also be groupOfNames
instead and they probably
should be if you intend to use these groups as access control groups for a
system. I would recommend not doing that and keep access groups for systems or
services entirely separate, represented by groups in ou=groups
. Ideally
depending on the role you have you’ll automatically be made member of certain
groups. This separates “who you are” from “what you have access to” and makes
it easier to have much more flexible access rules that are composed out of
different roles and other things.
Underneath roles it can be useful to mimick organizational structure, so you can
have the different business units, departments and what not in there. This makes
it pretty easy to be able to answer questions like “who’s the head of HR” by
getting cn=lead,ou=HR,ou=roles,ou=example,...
or “who are
managers in R&D"by looking at cn=managers,ou=R&D,ou=roles...
etc.
These roles would need to be maintained by automatic tooling, that takes care of the lifecycle of these entities. It should probably be kept in sync automatically with your payroll software, or be the thing that drives it.
Next up
Now that I’ve gone through what to take into account when designing a DIT it’s time to actually spin up a directory service!