We've looked at connection security and authentication. Now we are ready to look at the last aspect of security: authorization. What we are specifically interested in is controlling access to information in our directory tree. Who should be able to access a record? Under what conditions? And how much of that record should they be able to see? These are the sorts of questions that we will address in this section.
The primary way that OpenLDAP controls access to directory data is through Access Control Lists (ACLs). When the SLAPD server processes a request from a client, it evaluates whether the client has permissions to access the information it has requested. To do this evaluation SLAPD sequentially evaluates each of the ACLs in the configuration files, applying the appropriate rules to the incoming request.
ACLs were introduced in Chapter 2 in the section entitled ACLs. This section will develop the basic examples discussed there.
An ACL is just a fancy configuration directive (the access
directive) for SLAPD. Like certain other directives, the access
directive can be used multiple times. There are two different places in the SLAPD configuration where ACLs can be placed. Firstly, they can be placed in the global configuration outside of a database section (that is, near the top of the configuration file). Rules that are placed at this level will apply globally to all backends. In the next chapter we will look at the case where a single directory has multiple backends.
Secondly, ACLs may be placed within a backend section (somewhere beneath a database
directive). In this case, the ACLs will only be used when handling requests for information within database. In Chapter 2, we put our ACLs within the backend section, and we did not create any global access
directives.
How does all of this work out in practice? When are global rules used, and when are backend-specific rules used? If a backend has no specific ACLs, then the global rules will apply. If a backend does have ACLs, then the global rules will only be applied if none of the backend-specific rules apply. If the request is for a record which is not stored in any backend, such as the Root DSE or the cn=subschema
entry, then only the global rules will be applied.
Within their context ACLs are evaluated top-down, from the first directive in the configuration file to the last. So, when backend-specific rules are tested, SLAPD begins testing with the first rule on the list and continues sequentially until either a stopping rule matches or SLAPD reaches the end of the list.
In Chapter 2 we put the ACLs directly in the slapd.conf
configuration file. In this section we will put them in their own file and use the include
directive in slapd.conf
to direct SLAPD to load the ACL file. This will allow us to separate the potentially lengthy ACLs from the rest of the configuration file.
Let's take a quick look at the format of an ACL, and then we will move on to some examples which will help clarify the intricacies of the ACL method.
An access directive looks like this:
access to
[resources]
by
[who] [type of access granted] [control]
by
[who] [type of access granted] [control]
An access
directive can have one to
phrase, and any number of by
phrases. We will take a look at the access to
phrase first, then the by
phrase.
In the access to
part, an ACL specifies what is to be restricted in the directory tree by this rule. In the given rule we used [resources]
as a placeholder for this section. An ACL can restrict by DN, by attribute, by filter, or by a combination of these. We will first look at restricting by DN.
To restrict access to a particular DN, we would use something like this:
access to dn="uid=matt,ou=Users,dc=example,dc=com" by * none
The rule would restrict access to that specific DN. Any time a request is received that needs access to the DN uid=matt,ou=Users,dc=example,dc=com
, SLAPD would evaluate this rule to determine whether that request is authorized to access this record.
Restricting access to a specific DN can be useful at times, but there are several other supported options to the DN access specifier that come in useful for more general rule-making.
It is possible to restrict access to subtrees of a DN, or even by DN patterns. For example, if we wanted to write a rule that restricted access to entries beneath the Users OU, we could use an access
clause like this:
In this example the rule restricts access to the OU and any records subordinate to it. This is accomplished by using dn.subtree
(or the synonym dn.sub
). In our directory information tree there are a number of user records in the Users OU subtree. These records are children of the Users OU. The DN uid=matt,ou=Users,dc=example,dc=com
, for example, is in the subtree, and an attempt to access the record would trigger this rule.
Along with dn.subtree
, there are three other keywords for adding structural restrictions to the DN access specifier:
dn.base
: Restrict access to this particular DN. This is the default, and dn.exact
and dn.baselevel
are synonyms of dn.base
.dn.one
: Restrict access to any entries immediately below this DN. dn.onelevel
is a synonym.dn.children
: Restrict access to the children (subordinate) entries of this DN. This is similar to subtree, except that the given DN itself is not restricted by the rule.The dn
clause accepts one other modifier that can be used to do sophisticated pattern matching: dn.regex
. The dn.regex
access specifier can process POSIX extended regular expressions. Here is an example of a simple regular expression in dn.regex
:
access to dn.regex="uid=[^,]+,ou=Users,dc=example,dc=com" by * none
This example would restrict access to any DN with the pattern uid=SOMETHING,ou=Users,dc=example,dc=com
, where SOMETHING
can be any string that is at least one character long and has no commas (,
) in it. Regular expressions are a powerful tool for writing ACLs. We will discuss them more in the section Getting More from Regular Expressions after we look at the by
phrase.
In addition to restricting access to records by DN, we can also restrict access to one or more attributes within records. This is done using the attrs
access specifier.
In the examples we've seen, when we restricted access we were restricting access at a record level. The attrs
restriction works at a finer-grained level: it restricts access to particular attributes within records.
For example, consider a case where we want to limit access to the homePhone
attribute of all records in our directory information tree. This can be done with the following access phrase:
access to attrs=homePhone by * none
The attrs
specifier takes a list of one or more attributes. In the given example, we just restricted access to the homePhone
attribute. If we wanted to block access to homePostalAddress
as well, we could modify the attrs
list accordingly:
access to attrs=homePhone,homePostalAddress by * none
Let's say that we wanted to restrict access to all of the attributes in the organizationalPerson
object class. One way of doing this would be to create one long list: attrs
=title
, x121Address
, registeredAddress
, destinationIndicator
,.... But such a method would be time-consuming, difficult to read, and clumsy.
Instead, there is a convenient shorthand notation for this:
This notation should be used carefully, however. This code does not just restrict access to the attributes explicitly defined in organizationalPerson
, but also all of the attributes already defined in the person
object class. Why? Because the organizationalPerson
object class is a subclass of person
. Therefore, all of the attributes of person
are attributes of organizationalPerson
.
Sometimes it useful to restrict access to all attributes not required or allowed by a particular object class. For example, consider the case where the only attributes we want to restrict are those that are not specified in the organizationalPerson
object class. We can do that by replacing the at sign (@
) with an exclamation point (!
):
access to attrs=!organizationalPerson by * none
This will restrict access to any attributes unless they are allowed or required by the organizationalPerson
object class.
There are two special names that can be specified in the attributes list but that do not actually match an attribute. These two names are entry
and children
. So we have two cases:
attrs=entry
is specified, then the record itself is restricted.attrs=children
, then the records that are children of this record are restricted.These two key words are not particularly useful in cases where only an attrs
specifier is used, but they can be much more useful when attrs
and dn
specifiers are used in conjunction.
Sometimes it is useful to restrict by the value of an attribute (rather than by an attribute name). For example, we may want to restrict access to any givenName
attribute that has the value Matt
. This sort of thing can be accomplished using the val
(value) specifier:
Like the dn
specifier, the val
specifier has regex
, subtree
, base
, one
, exact
, and children
styles.
With val.regex
you can use regular expressions for matching. We can modify the last example to restrict access to any givenName
that starts with the letter M
:
access to attrs=givenName val.regex="M.*" by * none
In cases where the attribute value is a DN (like the member
attribute for a groupOfNames
object), the regex
, subtree
, base
, one
, and children
styles can be used to restrict access based on the DN in the attribute value.
access to attrs=member val.children="ou=Users,dc=example,dc=com" by * none
One of the lesser used but surprisingly powerful features of the access
phrase is support for LDAP search filters as a means of restricting access to records. We looked at the LDAP filter syntax at the beginning of Chapter 3 when we discussed the search operation. Here we will use filters to restrict access to parts of a record.
Filters provide a way to support value matching for entire records (instead of just attribute values, as is done with attrs
). For example, using filters we can restrict access to all records that contain the object class simpleSecurityObject
:
access to filter="(objectClass=simpleSecurityObject)" by * none
This will restrict access to any record in the directory information tree that has the object class simpleSecurityObject
. Any legal LDAP filter can be used in a filter specifier. For example, we could restrict access to all records that have the given name Matt, the given name Barbara, or the surname Kant:
access to filter="(|(|(givenName=Matt)(givenName=Barbara))(sn=Kant))" by * none
This code uses the "or" (disjunction) operator to indicate that if the request needs access to records that have given names with the values of Matt or Barbara, or if the request needs access to a record with the surname Kant, this rule should be applied.
We have looked at three different access specifiers: dn
, attrs
, and filter
. And in the previous sections we have used each. Now we will combine them to create even more specific access rules.
The order of combination is as follows:
access to
[dn] [filter] [attrs] [val]
The dn
and filter
specifiers come first, as they both deal with records as a whole. Then attrs
(and val
), which function at the attribute level, come next. Let's say that we want to restrict access to records in the Users OU just in the cases where the record has an employeeNumber
attribute. To do this we can use a combination of a DN specifier and a filter:
access to dn.subtree="ou=Users,dc=example,dc=com" filter="(employeeNumber=*)" by * none
This ACL will only restrict access when the request is for records in the ou=Users,dc=example,dc=com
subtree and the employeeNumber
field exists and has some value.
In a similar fashion, we can limit access to attributes for records in a certain subtree. For example, consider the case where we want to restrict access to the description
attribute, but only for records that are in the the System OU. We can do this by combining the DN and attribute specifiers:
access to dn.subtree="ou=System,dc=example,dc=com" attrs=description by * none
By this rule, a client could access the record with DN uid=authenticate,ou=System,dc=example,dc=com
, but it would not be able to access the description
attribute of that record.
By carefully combining these access specifiers it is possible to articulate exact access restrictions. We will see some more in action as we continue on to the by
phrase.
The by
phrase contains three parts:
To get the gist of this distinction, consider the by
phrase that we have been working with in the previous sections: by * none
. In this by
phrase, the who
field is *
(an asterisk character), and the access field is none
. The control field is omitted in this example.
The *
is the universal wildcard. It matches any entity, including anonymous and all DNs. The none
access type indicates that no permissions at all should be granted to the entity identified in the who
specifier. In other words, by * none
means that no access should be granted to anyone.
We will explore the who
field in detail, but before getting to that, let's examine the access field.
There are six distinct privileges that a client can have, in regards to an entry or attribute. There is also a seventh privilege specifier that equates to the removal of all privileges:
w
: Writes access to a record or attribute.r
: Reads access to a record or attribute.s
: Searches access to a record or attribute.c
: Accesses to run a comparison operation on a record or attribute.x
: Accesses to perform a server-side authentication operation on a record or attribute.d
: Accesses to information about whether or not a record or attribute exists ('d' stands for 'disclose').0
: Does not allow access to the record or attribute. This is equivalent to -wrscxd
.These seven privileges can be specified in a by
clause. To set one or more of these access privileges, use the =
(equals) sign.
For example, to allow the server to compare a record's givenName
field to a givenName
specified by a client, we could use the following ACL:
access to attrs=givenName by * =c
This will allow any client to attempt a compare operation. But that is the only operation it will allow. By this rule, no one can read from or write to this attribute. How does this work out in practice? When we use the ldapsearch
client to attempt to read the value of the givenName
attribute, we do not get any information about the givenName
:
$ ldapsearch -LLL -U matt "(uid=matt)" givenName
SASL/DIGEST-MD5 authentication started
Please enter your password:
SASL username: matt
SASL SSF: 128
SASL installing layers
dn: uid=matt,ou=Users,dc=example,dc=com
The only thing the server returns for our query is the DN of the record that matches the filter. No givenName
attribute is returned.
However, if we use the ldapcompare
client, we can ask the server to tell us whether or not the DN has a givenName
field with the value 'Matt':
$ ldapcompare -U matt uid=matt,ou=Users,dc=example,dc=com \ "givenName: Matt" SASL/DIGEST-MD5 authentication started Please enter your password: SASL username: matt SASL SSF: 128 SASL installing layers TRUE
The ldapcompare
client sends a DN and an attribute/value pair to the server, and asks the server to compare the supplied attribute value with the server's copy of the attribute value for the record with the given DN.
Here the ldapcompare
client will request that the SLAPD server look up the record for uid=matt,ou=Users,dc=example,dc=com
and check to see if the givenName
attribute has the value 'Matt'. The server will answer TRUE
, FALSE
, or (if there is an error) UNDEFINED
.
In this case, the server responded TRUE
. This indicates that the server performed the comparison, and the values matched. The combination of the ldapsearch
and ldapcompare
examples should illustrate how the ACL worked: while the server-side compare operation is permitted, the client does not have access to read the attribute value.
Multiple access privileges can be granted in one by
phrase. To modify in order to allow reading (r
), comparing (c
), and disclosing (d
) on the givenName
attribute, we can use the following ACL:
access to attrs=givenName by * =rcd
Now, both the ldapsearch
and ldapcompare
commands that we ran should succeed.
There are cases where permissions are inherited from other ACLs (we will look at some later). In such cases, we can selectively add or remove specific permissions by using +
(plus sign) to add and –
(minus sign) to remove.
For example, if we know that all the users already have compare (c
) and disclose (d
) on all the attributes, but we want to add read privileges just for the givenName
attribute, we can use the following ACL:
access to attrs=givenName by * +r
An access control that grants compare and disclose, and then continues processing might look something like this: access to attrs=givenName,sn by * =cd break
. This uses the break
control to instruct SLAPD to continue processing ACLs. If this rule appeared in the SLAPD configuration above the rule access to attrs=giveName by * +r
, then a request to the givenName
attribute would have the effective permissions =rcd
.
Likewise, if we needed to remove the compare operation just for the givenName
attribute, we could use a by
clause like by * -c
.
The 0
access privilege removes all privileges. It cannot be used with the +
or –
operators, it can only be used with the =
operator. The following ACL removes all privileges for all users to the givenName
attribute:
access to attrs=givenName by * =0
This is the same as the by
clause: by * -wrscdx
.
These access controls are good for fine-grained control, but sometimes it is nice to have shortcuts. OpenLDAP has seven shortcuts that handle common configurations of access controls:
Keyword |
Privileges |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The none
keyword we have seen before and it is the same as =0
. Looking at the other keywords and their associated privilege, a pattern emerges: each keyword adds one new privilege, to the privileges of the previous keyword. Thus, auth
has the =d
privilege from disclose
, plus the x
privilege, and compare
has =xd
from auth
and adds the c
privilege. The write
keyword at the bottom has all privileges.
Because this general accumulation of privileges captures the usual use cases while remaining more readable, keywords are used more frequently than privilege strings. In most of our examples from here on, we will use the keyword unless there is a specific reason to use the privilege string instead.
Of the seven keywords, disclose
, auth
, compare
, search
, read
, and write
can be prefixed with one of two prefixes: self
and realself
. The self
prefix indicates that if the value in question refers to the user's DN, then the user may have certain privileges. Thus selfwrite
indicates that the user has =wrscxd
permissions if and only if the value of the attribute in question is the user's DN.
The realself
prefix is similar, but it carries the additional stipulation that the DN not be proxied. These prefixes are particularly useful when dealing with groups and other membership-based records.
For example, the following ACL allows a user write
access to the uniqueMember
attribute only if the uniqueMember
attribute contains that user's DN: access to attrs=uniqueMember by users selfwrite
.
Now that we have covered the access field we will move on to the who
field.
We have always used *
in the who
field. However, the who
field is the richest of the ACL fields, providing twenty-three distinct forms, most of which can be used in combinations. In order to efficiently cover ground, we will cover the major forms on their own, and then group similar forms together and treat them as units.
The five most frequently used forms are *
, anonymous
, self
, users
, and dn
.
The *
specifier, as we have already seen, is a global match. It matches any client, including the anonymous user.
The anonymous
specifier matches only clients that bind to the directory as the Anonymous user (see Chapter 3 for details on the Anonymous user). This refers, then, to clients that have not authenticated to the directory. Since the process of authentication requires that the client connect Anonymously, and then attempt to bind as a DN with a specific password, the anonymous user almost always needs permissions to perform an auth
operation, in which the client sends the DN and password to the directory and asks the directory to verify that the information is correct. For that reason, you will likely need an ACL that looks like this:
access to attrs=userPassword by anonymous auth
This grants the Anonymous user the ability to do an auth operation. Note that every ACL ends with an implicit phrase: by * none
. In other words, if permissions are not explicitly specified none are granted.
Note that the ACL above does not allow users to modify their own passwords. That's where the self
specifier comes in.
The self
specifier is used to specify access controls for a DN on its own record. Thus, we can use the self
specifier to allow a user to modify her or his own userPassword
value:
access to attrs=userPassword by anonymous auth by self write
If we log in as uid=matt,ou=Users,dc=example,dc=com
and try to modify the userPassword
value of our own record (dn: uid=matt,ou=Users,dc=example,dc=com
), SLAPD will allow us to change the password. But it will not (according to the rule above) allow us to modify anyone else's userPassword
value.
The self
specifier can be further modified with a level
style. The level
style indicates whether (and how many) parent records or child records are to be treated as if they were part of self
. The level
style takes an integer index. Positive integers refer to parents, while negative integers refer to children.
Thus access to
ou
by
self.level{1}
write
indicates that the current DN has write permissions to the ou
of its parent. Likewise, access
to
ou
by
self.level{-1}
write
indicates that the current DN has write permission to the ou
of any of its immediate children.
The users
specifier refers to any authenticated client. The anonymous user is not included in users
because it represents a client that has not authenticated.
This specifier comes in very handy when you need to allow anyone who has authenticated access to some resources. For example, in an enterprise directory we would likely want to allow all users the ability to see each other's names, telephone numbers, and email addresses:
access to attrs=sn,givenName,displayName,telephoneNumber,mail by self write by users read
The dn
specifier performs similarly in the by
phrase to the role it plays in the access
to
phrase. It specifies one or more DNs. The dn
has the regex
, base
, one
, subtree
, and children
modifiers, all of which perform the same way here as they did in the access
to
phrase. Here's an example using a few different DN patterns:
access to dn.subtree="ou=System,dc=example,dc=com" attrs=description by dn="uid=barbara,ou=Users,dc=example,dc=com" write by dn.children="ou=System,dc=example,dc=com" read by dn.regex="uid=[^,]+,ou=Users,dc=example,dc=com" read
This rule restricts access to the description attributes of anything in the System OU subtree. The user uid=barbara,ou=Users,dc=example,dc=com
has write permissions to the description, while any child users of the System OU have read permissions. Users with DNs of the form uid=SOMETHING,ou=Users,dc=example,dc=com
also have read access to the description.
In addition to the regular DN modifiers, a dn
in the by
clause can also have a level
modifier. Level allows the ACL author to specify exactly how many levels down a by
phrase should go. Recall that the dn.one
specifier indicates that any record directly below the specified DN is to be granted the specified permissions. For example by
dn.one="ou=Users,dc=example,dc=com"
read
grants any direct descendant of the Users OU read permissions. So uid=matt,ou=Users,dc=example,dc=com
would be granted read access, but uid=jake,ou=Temp,ou=Users,dc=example,dc=com
would not be granted such access because he is two levels down. The dn.level
specifier lets us arbitrarily specify how many levels to descend. For example, by
dn.level{2}="ou=Users,dc=example,dc=com"
read
would allow both matt
and jake
read access.
Proxy Authentication and Real DNs
If SLAPD is set up to allow Proxy Authentication, in which case one DN is used for authentication, and then another DN is used for performing other directory operations, it is sometimes useful to write ACLs based on the DN used for authentication (the real DN). The realdn
specifier can be used for this. It functions just like the dn
specifier, except that it operates on the real DN. Also, realanonymous
, realusers
, realdnattr
, and realself
can be used to restrict based on the real DN. See the slapd.access
man page for more: man
slapd.access
.
Sometimes it is useful to grant group members the access to an object. For example, if you have an Administrators group, you may wish to grant any member of that group write access to all of the records in the System OU.
One might expect that the way to set permissions for group members is simply to use the group as the value of a dn
specifier in an ACL. However, that is not the case since the dn
specifier refers to the group record as a whole, and has nothing at all to do with the members of the group, each of which has its own record elsewhere in the directory.
Instead, what we need is a way to search the member attributes of a particular group record, and then grant access to the DNs listed in the record. The group specifier provides exactly this sort of capability.
Group evaluation can be done with the group
specifier. In its simplest form it is used like this:
access to dn.subtree="ou=System,dc=example,dc=com"
by group="cn=Admins,ou=Groups,dc=example,dc=com" write
by users read
This ACL will grant members of the cn=Admins,ou=Groups,dc=example,dc=com
group write access to anything in the System OU, while giving all other users read-only permissions.
Order Matters
ACL by phrase are evaluated sequentially, and by default SLAPD will stop processing by
phrases when it hits a match. In other words, if the by phrases in the above rule were reversed, members of LDAP Admins would never be given write permission because they would always match the by
users
read
phrase. Evaluation of the ACL would stop before group membership was checked.
But the ACL above will only work on groups whose object class is groupOfNames
, and whose membership attribute is member
. This is because groupOfNames is the default grouping object class, and member is the default membership attribute.
When we created our LDAP Admins group in Chapter 3, it was not groupOfNames
, nor did it use the member
attribute for membership. Our record looked like this:
dn: cn=LDAP Admins,ou=Groups,dc=example,dc=com cn: LDAP Admins ou: Groups description: Users who are LDAP administrators uniqueMember: uid=barbara,ou=Users,dc=example,dc=com uniqueMember: uid=matt,ou=Users,dc=example,dc=com objectClass: groupOfUniqueNames
We used the groupOfUniqueNames
object class and the uniqueMember
membership attribute. In order to get the ACL to match these constraints we will need to specify the object class and membership attribute in the group
specifier:
access to dn.subtree="ou=System,dc=example,dc=com" by group/groupOfUniqueNames/uniqueMember= "cn=LDAP Admins,ou=Groups,dc=example,dc=com" write by users read
Note the change in the highlighted line. Using slashes (/
) we have specified first the object class then the membership attribute that should be used to determine who what entries represent members. When this by
phrase is evaluated, SLAPD will find the DN cn=LDAP
Admins,ou=Groups,dc=example,dc=com
, check to see if it has object class groupOfUniqueMembers
, and then grant write permissions to a DN if it is specified in a uniqueMember attribute.
Using this expanded notation, you can use other membership-based records as groups. For example, you can use the organizationalRole
object class with the roleOccupant
membership attribute.
Like many other specifiers, the group specifier also supports regular expressions with the regex
style. Thus, we could create a rule that would allow members of any group in OU Groups write access to the System OU by expanding our last example:
access to dn.subtree="ou=System,dc=example,dc=com" by group/groupOfUniqueNames/uniqueMember.regex= "cn=[^,]+,ou=Groups,dc=example,dc=com" write by users read
The second and third lines should be combined into one long line in slapd.conf
. The regular expression in the group specifier would match any DN with a CN component at the beginning. For all such entries, if the object class is groupOfUniqueMembers
, then the SLAPD will grant membership to a user who is a uniqueMember
of one of those groups.
What if a group member needs to modify the record of the group to whom she or he belongs? One way to allow this is with the dnattr
specifier. The dnattr
specifier grants access to a record only if the client's DN appears in a certain attribute of the record. For example, the following example allows a group member (uniqueMember
) of a group (which is a groupOfUniqueNames
object) access to the group record:
access to dn.exact="cn=LDAP Admins,ou=Groups,dc=example,dc=com" by dnattr=uniqueMember write by users read
The second line specifies that if the client's DN is in the list of values for the uniqueMember
attribute, then that client should be given write access to the entire group record. Other users, according to the third line, will have read access.
SLAPD can use information about the client's connection (including network and security information) in access control lists. This feature provides an additional layer of network security that complements SSL/TLS and SASL.
The following are network or connection level specifiers:
peername
: This is used to specify a range of IP addresses (for ldap://
and ldaps://
).sockname
: This is used to specify a socket file for an LDAPI listener (ldapi://
).domain
: This is used to specify a domain name for ldap://
and ldaps://
listeners.sockurl
: This is used to specify a socket file in URL format (ldapi://var/run/ldapi
) for an LDAPI listener.ssf
: The overall security strength factor (SSF) of the connection.transport_ssf
: The SSF for the underlying transport layer of the network.tls_ssf
: The SSF for the SSL/TLS connection. This works with SSL/TLS connections on LDAPS listeners and Start TLS on LDAP listeners.sasl_ssf
: The SSF of the SASL connection.The SSF specifiers (ssf
, transport_ssf
, tls_ssf
, and sasl_ssf
) perform the same checks as the SSF parameters to the SLAPD security
directive (discussed in the first part of this chapter). In this case, however, SSFs may be used to selectively restrict (or grant) access to portions of the directory information tree. SSF specifiers require an integer value for the level of security desired. For example, using ssf=256
will require that the overall SSF of a connection be 256. But tls_ssf=56
will require that the SSF of the TLS/SSL layer be at least 56, regardless of what the SSF of the SASL configuration is. For more information on SSFs, see the section earlier in this chapter entitled Using Security Strength Factors.
For example, the following ACL will only grant write access to the specified DN when the client has connected with a strong SASL cipher:
access to dn.subtree="ou=users,dc=example,dc=com"
by self sasl_ssf=128 write
by users read
This rule allows users to modify their own records only if they have authenticated with SASL using a security mechanism with a strength of 128 (DIGEST-MD5) or more. All other users would only get read access.
The peername
specifier is used for setting restrictions based on information about the IP connection. It can be used to complement other components in network security, like SSL/TLS. The peername
specifier can take an IP address or a range of IP addresses (using subnet masks) and can also specify a source port.
The following rule grants write access to local connections, read access to connections on the local LAN (address from 10.40.0.0 through 10.40.0.255), and denies access to all other clients. Remember, every rule ends with an implicit by
*
none
.
access to * by peername.ip=127.0.0.1 write by peername.ip=10.40.0.0%255.255.255.0 read
Note that the peername
specifier requires the ip style for specifying an IP address. It also supports the regex
style (access
to
*
by
peername.regex="^IP=10\
.40\
.0\
.[0-9]+:[0-9]+$"
write
) and the path
specifier to replicate the behavior of sockname
.
Regular Expressions for IP Addresses
For an IP address, the format of the string used in regular expression evaluation is this: IP=<address>:<port>
. If you are creating a precise regular expression make sure to deal with the IP=
prefix and the port information. A regular expression like this will fail: peername.regex="^10.40.12[0-9]$"
. Why? Because it is missing the IP=
and port information.
A more useful version of the rule above would deny access to anything in the directory if it was not in the particular ranges, but would leave further access controls to rules appearing later in the ACL list. This can be done using the special break
control described in the next section. We could also added SSF information, so connections coming over non-local connections must also use strong SSL/TLS encryption. Here is the rule:
access to * by peername.ip=127.0.0.1 break by peername.ip=10.40.0.0%255.255.255.0 tls_ssf=128 break
The above rule might appear difficult to read, but here is what it does:
localhost
), then SLAPD allows further processing of the ACL list (that's what break
does). Whether or not the user then gets access to resources is dependent on other rules.by
*
none
phrase.For more on the break
control, see the section called The Control Field.
Sometimes it is more useful to be able to specify which domain names (rather than which IP addresses) should be granted access. This can be done with the domain
specifier:
access to * by domain.exact="main.example.com" write by domain.sub="example.com" read
In the example above, the second line provides write access to any client connection coming from the domain name main.example.com
. The third line grants read access to the domain example.com
, and any subdomain of example.com
. So, if a server with the domain name test2.example.com
made a request, it would be granted access under the third rule. However, testexample.com
would not match because it is not a subdomain of example.com
—it is a different domain altogether.
When SLAPD encounters a domain specifier in an ACL, it takes the IP address of the client connection and does a reverse DNS lookup to get the host name. In light of this there are two things to keep in mind when using the domain specifier.
First, the name returned by a reverse DNS lookup may not be what you expect based on a forward DNS lookup. For example, doing a DNS lookup on ldap.example.com
returns the address 10.40.0.23. However, doing a reverse DNS lookup on 10.40.0.23 returns mercury.example.com
. Why?
It is because ldap.example.com
is in DNS parlance, a CNAME record, and mercury.example.com
is an A record. Practically speaking, what this means is that ldap.example.com
is an alias to the server's real (canonical) name, which is mercury.example.com
. The practical consequence is this: when you write an ACL using the domain
specifier, make sure you use the A record domain name, not the CNAME record name. Otherwise, SLAPD will apply the rule to the wrong domain name.
Looking up DNS Information
There are many tools for looking up DNS information. Most Linux distributions, including Ubuntu Linux, provide the host
and dig
commands for command-line DNS lookups. The host
command gives brief sentence-like information like this: ldap.example.com
is
an
alias
for
mercury.example.com
. The dig
command, in contrast, gives detailed technical information.
The second thing to keep in mind when considering the domain specifier is that it is less reliable than using IP address information. DNS addresses can be spoofed, which means another server on the network can claim to be ldap.example.com
and send traffic that looks, to SLAPD, like it is coming from the real ldap.example.com
.
One way to diminish the risk of this is to use client-side SSL/TLS certificates and configure SLAPD to require that the client send a signed certificate to authenticate before it can perform any other directory operations. Unfortunately, client-side certificates cannot be selectively required through ACLs. Instead you will have to use the directive TLSVerifyClient
demand
in the slapd.conf
file.
The sockname
and sockurl
specifiers are used for servers that run with UNIX local socket Inter Process Communication (IPC) instead of network sockets. These directives can be used to restrict local connections that use the IPC layer instead of connecting through the IP network.
For example, we could use the following ACL to allow only local (LDAPI) connections to write to the record, while users who connected through a different mechanism could only read the record:
access to dn.exact="uid=matt,ou=Users,dc=example,dc=com" by sockurl="ldapi://var/run/ldapi" write by users read
The second line indicates that only LDAPI connections that connect through a particular LDAPI socket file should gain write access to the DN. All other clients (users
) will get read permissions.
In addition to the syntax we have examined just now, there is an experimental type of by
phrase—the set syntax. The set
syntax can be used to create a compact and powerful set of conditions for access. Since it allows Boolean operators, and has a method for accessing attribute values, a single rule in the set
syntax can accomplish what would otherwise take tremendously complex ACLs.
The basic idea behind the set
syntax is this. By using a rule composed of conditions joined by operators, SLAPD creates a set of objects which have access to the record in question. If the result of an evaluation of a set
specifier is a set that contains one or more members, then the by
phrase is considered a match and permissions are applied. If, on the other hand, the set is empty, then SLAPD will continue evaluating the by
phrases for that rule to see if it can find another match.
Here is a simple ACL using a set
specifier to replicate the behavior of the group
specifier. It provides write access to records in the System OU only to clients in the LDAP Admins group. All others get read access only:
access to dn.subtree="ou=System,dc=example,dc=com" by set="[cn=ldap admins,ou=groups,dc=example,dc=com]/ uniqueMember & user" write by users none
The second line, highlighted above, contains the set
specifier, which contains a set
statement. The text in the square brackets specifies a DN, which is the DN of the LDAP Admins group. To access the values of the uniqueMember
attribute we append /uniqueMember
to the DN. When SLAPD expands this, it will contain the set of all uniqueMembers
in the LDAP Admins
group
. In set-theoretic notation (which is not used by OpenLDAP, but which is helpful to understand what is happening), the set of group members would look like this:
{ uid=matt,ou=users,dc=example,dc=com ; uid=barbara,ou=users,dc=example,dc=com }
There are two members (the two uniqueMembers
) for the LDAP Admins group.
The &
(ampersand) operator performs a union operation on two sets. The user keyword expands to the set that contains one member: the DN of the current client. So, if I perform a search, binding as uid=matt,ou=users,dc=example,dc=com
, then the user set will contain one record:
{ uid=matt,ou=users,dc=example,dc=com }
When the &
operator is applied, it will generate the intersection of the two sets. That is, the resulting set will contain only members that are in both of the original sets. Since only the record for UID matt
is in both, the resulting set will contain just the DN for matt
:
{ uid=matt,ou=users,dc=example,dc=com }
The resulting set is not empty so it is considered a match. The result of the set evaluation, then, is that the uid=matt,ou=users,dc=example,dc=com
will be granted access based on the set
specifier.
Consider a case though, when the user is not a member of the LDAP Admins group. If uid=david,ou=users,dc=example,dc=com
binds, can he perform read and write operations? When the set specifier is run, the first of the two sets (group membership) will evaluate to the same thing it did above:
{ uid=matt,ou=users,dc=example,dc=com ; uid=barbara,ou=users,dc=example,dc=com }
But the user keyword will expand to this:
{ uid=david,ou=users,dc=example,dc=com }
There are no items in the intersection of these two sets, so the resulting set, after the &
operator is applied, is an empty set:
{ }
There are no matches, so this by
phrase fails to apply. The last line in our ACL (by
users
none
) will then apply, and the uid=david
will be given no access permissions.
Let's look at another example. We will use the set specifier to implement a rule where, when a client DN tries to access a record DN, it is given write access only if the two DNs are the same, or else it is given read access if they are in the same OU. Otherwise, the client DN is denied access to the record DN. Here's the ACL:
access to dn.subtree="dc=example,dc=com" by set="this & user" write by set="this/ou & user/ou" read
The first line indicates that this rule will apply to the record dc=example,dc=com
and everything under it.
The second line takes the intersection of the sets generated by two keywords: this
and user
. The this
keyword expands to the set containing the DN of the requested record. The user
keyword, as we saw, expands to the DN of the client.
So, if the client uid=david,ou=users,dc=example,dc=com
requests access to its own record, the resulting set operation will be as follows:
{ uid=david,ou=users,dc=exampls,dc=com } & { uid=david,ou=users,dc=example,dc=com }
Since both sets contain the same member, the resulting set (the intersection of the two) is {
uid=david,ou=users,dc=example,dc=com
}
. The end set is not empty, so the user will be granted write access.
Now let's look at the third line of the given ACL. This rule will return a non-empty set whenever the requested DN and the client DN both have the same value for the ou
attribute. If uid=david,ou=users,dc=example,dc=com
requests the record for uid=matt,ou=users,dc=example,dc=com
, then SLAPD will check the values of their respective OU attributes.
The set identified by this/ou
will be expanded to contain the values of all of the OU attributes in the requested record (the record for uid=matt,ou=users,dc=example,dc=com
). This set is:
{ 'Users' }
Note that in this case the value is not a DN, but a string. Sets can perform matching operations on strings as well as DNs.
The set identified by user/ou
will be expanded to contain the values of all of the OU attributes in the client's record. The record for uid=david,ou=users,dc=example,dc=com
contains one value for the ou
attribute, and the resulting set will contain that one attribute value:
SLAPD will compute the intersection of {
'Users'
}
&
{
'Users'
}, which is {
'Users'
}
. Since the set is not empty, uid=david,ou=users,dc=example,dc=com
will be granted access to read the record of uid=matt,ou=users,dc=example,dc=com
.
The set
specifier provides one way of granting access to a record only in the case that a record contains a certain attribute. If we only want to grant write access to records with the title attribute, we can use the following rule:
access to dn.child="ou=Users,dc=example,dc=com" by set="this/title" write
In this ACL, if the requested record has a single title
attribute, then the result of the evaluation of the above rule will be a set containing one element. However, if the record attribute has no title attribute, then the resulting set will be empty, and write access will not be granted.
In our directory the record of uid=matt,ou=users,dc=example,dc=com
has the following title attribute:
title: Systems Integrator
But the record uid=barbara,ou=users,dc=example,dc=com
does not have a title attribute at all. So if the record for uid=matt
was requested, then the resulting set, based on the above ACL, would be:
{ 'Systems Integrator' }
So if an authenticated user attempted to access the record for uid=matt
, SLAPD would grant access. In contrast, the set for uid=barbara
would be {}
, the empty set. So a user trying to access the record having uid=barbara
would be denied access.
Using a similar set specifier, we could grant access to a record depending not only on the existence of an attribute, but on its value too:
access to dn.child="ou=Users,dc=example,dc=com" by set="this/objectclass & [person]" write
According to the above rule, write access will be granted for anything in the Users OU only if the entry has an objectclass
attribute with the value person
. Note that in this case the square brackets are used to define a string literal.
If a client were to access the record uid=barbara,ou=users,dc=example,dc=com
, the first part of our set
statement would evaluate to the following set:
{ 'person' ; 'organizationalPerson' ; 'inetOrgPerson' }
Those are the three object classes for the uid=barbara
record. The other part, [person]
, would be expanded to this set:
{ 'person' }
When the union is computed, the result would be the set {'person'}
and so write access would be granted.
These are just a few of the basic operations that can be done with the set
specifier. Unfortunately, set
is not documented in the slapd.access
man page. However, there is a lengthy and informative article on using set in the OpenLDAP official FAQ-O-Matic: http://www.openldap.org/faq/data/cache/1133.html.
The last field in the by
phrase is the control field. There are only three possible values for the control field: stop
, break
, and continue
. If no control field is specified, stop
is assumed. For example, by
*
none
is the same as by
*
none
stop
.
The first value, stop
, indicates that if that particular by clause matches, no further checking of ACLs for matching should occur. Consider the following (admittedly contrived) case:
access to attr=employeeNumber, employeeType, departmentNumber by users=cd by dn="uid=matt,ou=Users,dc=example,dc=com" +r access to attr=employeeNumber by users +w
If I bind as uid=matt,ou=Users,dc=example,dc=com
and try to modify my employeeNumber
, will I be allowed to? No, I will not.
The reason I will not be able to modify the record is because I will only have the permissions granted by the first by
phrase: by
users
=cd
(remember, by
users
=cd
is the same as by
users=cd
stop
). As soon as SLAPD sees that I match the first by
phrase of the first ACL, it will stop testing ACLs. Thus it will never reach the rule that grants my DN +r
access, nor will it reach the rule that grants all users +w
to the employeeNumber
attribute.
This is an example of the stop
control, which is used implicitly by all three rules.
Now, if I wanted to make sure that after the first by
phrase SLAPD continues to evaluate phrases within the ACL, I could re-write the ACLs using the continue
control:
access to attr=employeeNumber, employeeType, departmentNumber by users-=cd continue by dn="uid=matt,ou=Users,dc=example,dc=com" +r access to attr=employeeNumber by users +w
After running the same test on these rules, the DN uid=matt,ou=Users,dc=example,dc=com
would have the permissions =cdr
.
The continue
control instructs SLAPD to continue processing all of the by
phrases in the current ACL. Once it is done evaluating that ACL though, it will not continue to look for matches in other ACLs.
In order to tell SLAPD to look at different rules for matches, we would have to use the break
control. When SLAPD encounters an applicable clause that ends with a break
control, it stops processing the current ACL but continues looking at other ACLs to see if they apply.
Thus, to get write permissions with our ACL we would want the following ACLs:
access to attr=employeeNumber, employeeType, departmentNumber by users=cd continue by dn="uid=matt,ou=Users,dc=example,dc=com" +r break access to attr=employeeNumber by users +w stop
Now what will happen when the user with UID matt
attempts accesses an employeeNumber
?
First, the by
phrase of the first ACL will be evaluated, and matt
will be granted =cd
. Because of the continue
control, SLAPD will then examine the second by
clause, which will also match for the user matt
. Thus, matt
will have =rcd
when the processing of the first ACL completes.
Due to the break
control the second ACL will also be evaluated, and matt
will be granted +w
as well, bringing his final permissions up to =wrcd
.
Using the continue
and break
controls is one way to incrementally handle permissions. In complex configurations, judicious use of continue
and break
can make maintaining ACLs much easier, and can reduce the total number of ACLs.
In the previous sections we have looked at using regular expressions in both the access
to
phrase and the by
phrase. But we can use both in conjunction. We can store information about the matches identified in the access
to
phrase, and use that information later in the by
phrases.
To temporarily store matching information in an access
to
phrase we can surround the regular expression with parentheses. Here's an example:
access to dn.regex="ou=([^,]+),dc=example,dc=com" by dn.children,expand="ou=$1,dc=example,dc=com" read
This ACL grants a client the DN access to read a record DN only if both the client DN and the record DN are in the same part of the directory tree (that is, if both are in the same OU).
In the first line of the given ACL we used parentheses to capture the match from the regular expression [^,]+
, which will be the value of the ou=
component of the DN. Again, [^,]+
says "match all charcters that are not ',
'."
In the second line we used the dn.children
specifier but supplemented it with an extra keyword: expand
. The expand
keyword tells SLAPD to substitute matches from the access
to
clause into this phrase.
Because of the expand
keyword, the variable $1
is substituted with the value of the match in the first line. Everything captured between '(
' and ')
' in the regular expression will be stored in $1
.
Variable names are assigned in order. The first set of parenthesis in the regular access
to
phrase gets stored in $1
. If a second set of parenthesis existed, the matching information inside of those would be stored in $2
and so on for each additional set of parenthesis.
For example, we might want an ACL like this:
access to dn.regex="uid=([^,]+),ou=([^,]+),dc=example,dc=com" by dn.children,expand="uid=$1,ou=$2,dc=example,dc=com" write
This rule would grant a client DN access to read and write any entries subordinate to its own record but deny other uses the ability to even read those entries.
Address books are sometimes implemented in OpenLDAP by storing a user's addresses as subordinate entries to the user's own entry in the directory. There is an example of this in the OpenLDAP FAQ-O-Matic: http://www.openldap.org/faq/data/cache/1005.html
Notice that the first line stores two variables. The UID goes in $1
and the OU goes in $2
. These are expanded in the second line.
It is also possible to use matches from the access
to
phrase in regular expressions in the by
phrase:
access to dn.regex="uid=[^,]+,ou=([^,]+),dc=example,dc=com" by dn.regex="uid=[^,]+,ou=$1,dc=example,dc=com" write
In the first line only the results of the second regular expression are captured and stored in a variable. The second line also contains a regular expression, and it makes use of the $1
variable to retrieve the value of the OU from the first line. Note that dn.children,expand
was replaced with dn.regex
. The expand
keyword need not be added for regular expressions.
The rule grants write access to a client DN for any user record that is in the same OU of that directory tree.
We have looked at some simple, though useful, regular expressions in these ACLs. But much more complex regular expressions can be composed, making ACLs even more powerful. As you compose more advanced regular expressions you may find some other sources of information helpful. Along with the slapd.access
man page, the POSIX extended regular expressions man page (man
regex
) may turn out to be useful as well.
Debugging ACLs can be frustrating. They are complex, security sensitive, and require detailed testing. But there are three tools that make the debugging and testing process easier.
The first is just the ldapsearch
command-line client. It can be used to carefully craft filters designed to test the processing of ACLs. The ldapcompare
tool also comes in handy when you need to test comparison operations.
But it is also useful to make the most of LDAP's logging directives. The trace
and acl
debugging levels each provide detailed information about ACL processing. The acl
level, for example, records each ACL evaluation. This can be very useful in determining what rules are run and when. We find the trace
debugging level to be useful as well, as it provides information about how each evaluation was performed, including how regular expressions were expanded.
Running SLAPD in the Foreground
Sometimes it is easier to test ACLs by running SLAPD in the foreground, instead of as a daemon process, and printing debugging and logging information to standard out. For example, we can print ACL and trace debugging out this way: slapd
-d
"acl,trace"
. Note that you will want to run this command as the appropriate user (such as openldap
). To terminate the process use the Ctrl-C keyboard combination.
Finally, the slapacl
command line utility provides a detail-oriented tool for evaluating ACLs directly. Since it does not connect to the SLAPD server over the LDAP protocol it allows more direct testing of just the ACLs.
For example, we can check whether or not a particular SASL user, matt
, can access the record cn=LDAP
Admins,ou=Groups,dc=example,dc=com
and read the value of the description
attribute:
$ slapacl -U matt -b "cn=LDAP Admins,ou=Groups,dc=example,dc=com" \ "description/read"
The -U
matt
param specifies the SASL user name. The -b
"cn=LDAP
Admins,ou=Groups,
dc=example,dc=com"
param indicates which record we want to test against, and the last field, "description/read"
indicates the attribute and the access level. This will simply return ALLOWED
if the ACLs allow read access, or DENIED
otherwise.
Likewise, we can test other LDAP operations. For example, we can test whether a user has permissions to compare
:
$ slapacl -U matt -b "uid=matt,ou=Users,dc=example,dc=com" "uid/compare" authcDN: "uid=matt,ou=users,dc=example,dc=com" compare access to uid: ALLOWED
In this example we have included the response. The first response line indicates how the SASL DN was resolved, and the second line indicates that compare access on uid
was allowed.
The slapacl
program essentially runs its own SLAPD and as such, it can be set to print complete processing logs to the screen. For example, to turn on trace debugging we can just add the -d
trace
param to the given command:
$ slapacl -U matt -b "uid=matt,ou=Users,dc=example,dc=com" -d trace "uid/compare" slapacl init: initiated tool. slap_sasl_init: initialized! hdb_back_initialize: initialize HDB backend hdb_back_initialize: Sleepycat Software: Berkeley DB 4.3.29: (September 6, 2005) bdb_db_init: Initializing HDB database >>> dnPrettyNormal: <dc=example,dc=com> # LOTS of lines deleted... <<< dnPrettyNormal: <uid=matt,ou=Users,dc=example,dc=com>, <uid=matt,ou=users,dc=example,dc=com> entry_decode: "" <= entry_decode() compare access to uid: ALLOWED slapacl shutdown: initiated ====> bdb_cache_release_all slapacl destroy: freeing system resources.
As you can seeslapacl
provides detailed evaluation information in this case.
Using the LDAP command-line clients, detailed logging, and the slapacl
command, debugging and testing ACLs can be done effectively.
In this part of the chapter, we have taken a low-level look at ACLs in OpenLDAP. We have covered many of the details of the ACL system. Now it is time to implement what we have covered so far to create a generic set of ACLs for our directory information tree.
In Chapter 2 we created a bare-bones set of ACLs in our slapd.conf
file. Here's what we created then:
######## # ACLs # ######## access to attrs=userPassword by anonymous auth by self write by * none access to * by self write by * none
Now, we will create a new, more practical list of ACLs.
The first thing we will do is move the ACLs out of slapd.conf
and into a separate file: acl.conf
. This will keep the lengthy list of ACLs separate from the rest of our configuration. To do this we will replace the ACLs above with an include
directive:
######## # ACLs # ######## include /etc/ldap/acl.conf
When SLAPD is started it will include the contents of /etc/ldap/acl.conf
at the location where the include
statement appears. Recall that ACLs are backend-specific. Each different database can have its own ACLs (and multiple databases can be defined in the same slapd.conf
file). So it is important to put the include
directive after the database directive
in slapd.conf
.
Now we will begin editing the acl.conf
file. The rules that we will write will be simple, and designed for a directory where most of the directory users are allowed to view most of the information in the directory. A higher-security directory may have a far more complex list of ACLs.
Since ACLs are evaluated in order from top to bottom we want to carefully craft our rules so that important restrictions are implemented right away.
If there are network-based access rules they should usually appear at the top of the ACL list so that they are evaluated first. For example, if we want to restrict access to the entire database if the host is not in our LAN, we would use the following rule:
access to * by peername.ip=127.0.0.1 none break by peername.ip=10.40.0.0%255.255.255.0 none break
By this rule only access from the localhost (127.0.0.1) and from inside of our 10.40.0.0 subnet will be allowed to access the directory. Since the break
control is specified, later rules may modify the none
permission, granting clients more permissions. All other connections will be closed immediately.
Next, we want to grant members of the LDAP Admins group write access to everything in the dc=example,dc=com
tree:
access to dn.subtree="dc=example,dc=com" by group/groupOfUniqueNames/uniqueMember= "cn=LDAP Admins,ou=Groups,dc=example,dc=com" write by * none break
This immediately grants write access to the members of the LDAP Admins group. For all other clients though, SLAPD will continue processing.
Next, we want to make sure that the userPassword
field is available to the anonymous user for authentication purposes. We also want to allow users to be able to modify their own passwords, but otherwise we want userPassword
unavailable for reading and writing by others. Note that by the previous rule the LDAP Admins will also be able to modify passwords for users.
access to attrs=userPassword by anonymous auth by self write
In some cases, other users may need auth
access to the password as well, in which case you may need to add by
users
auth
to the given list.
We also need to grant access to the uid
attribute if we are using the ldap://
URL form for SASL binding in the authz-regexp
directive. This is because the filter in the LDAP URL is run as anonymous (see the discussion in the Configuring SLAPD for SASL Support Subsection).
Additionally, we don't want to let users try to modify their own uid
, since uid
is used in the DN:
access to attrs=uid by anonymous read by users read
Now Anonymous and all authenticated users will be able to access the uid
attribute of any record in the directory to which they have access.
There are also a few other attributes we don't want users to be able to modify—even in their own records.
We don't want users to try to modify their OU attributes, since OU attributes are also used in DNs. We also don't want them to be able to modify their employeeNumber
or their employeeType
:
access to attrs=ou,employeeNumber,employeeType by users read
We have a special account, uid=Authenticate,ou=System,dc=example,dc=com
, which will be used on occasion to help with bind requests. This user should not have access to anything else other than what we specified:
access to * by dn.exact="uid=Authenticate,ou=System,dc=example,dc=com" none by users none break
Again, the last line instructs SLAPD to continue processing ACLs for users who aren't having the authentication account. This line will also stop the anonymous user from browsing the rest of the tree since the implicit rule at the end, by
*
none
, will catch the anonymous user.
Let's say that we don't want regular users (DNs in the Users OU) to be able to access records in the System OU of our directory (which is typically used for system accounts). We can implement this with the following rule:
access to dn.subtree="ou=System,dc=example,dc=com" by dn.subtree="ou=Users,dc=example,dc=com" none by users read
This denies access to users in the Users OU, but allows other users (like System accounts) access to these records.
We also want to give every user the ability to read and write records below its own, but restrict others from accessing those records. This makes it possible for users to store their own information (like address books) inside of the directory:
access to dn.regex="^.*,uid=([^,]+),ou=Users,dc=example,dc=com$" by dn.exact,expand="uid=$1,ou=Users,dc=example,dc=com write
Finally, the last rule we want is a default rule. This rule should answer the question, "What do we want to happen when no other rules are matched?" We want users to be able to modify their own records and see the records of others:
Now our list of ACLs is complete. Altogether, this is what they look like:
################################################# # ACLs # These are ACLs for the first database section # of the slapd.conf file found in this directory ################################################# ## ## Restrict by IP address: access to * by peername.ip=127.0.0.1 none break by peername.ip=10.40.0.0%255.255.255.0 none break ## Give Admins immediate write access: access to dn.subtree="dc=example,dc=com" by group/groupOfUniqueNames/uniqueMember="cn=LDAP Admins,ou=Groups,dc=example,dc=com" write by * none break ## Grant access to passwords for auth, but allow users to change ## their own. access to attrs=userPassword by anonymous auth by self write ## This rule is needed by authz-regexp ## (Note: Since uid is used in DN, user cannot change its own uid.) access to attrs=uid by anonymous read by users read ## Don't let anyone modify OUs, employee num or employee type. access to attrs=ou,employeeNumber,employeeType by users read ## Stop authentication account from reading anything else. This also ## stops anonymous. access to * by dn.exact="uid=Authenticate,ou=System,dc=example,dc=com" none by users none break ## Prevent DNs in ou=Users from seeing system accounts access to dn.subtree="ou=System,dc=example,dc=com" by dn.subtree="ou=Users,dc=example,dc=com" none by users read ## Allow user to add subentries beneath its own record. access to dn.regex="^.*,uid=([^,]+),ou=Users,dc=example,dc=com$" by dn.exact,expand="uid=$1,ou=Users,dc=example,dc=com" write ## The default rule: Allow DNs to modify their own records. Give ## read access to everyone else. access to * by self write by users read
While they certainly won't meet all needs, these rules provide a good starting point for balancing security and usability of the directory. Furthermore, they set the stage for some of the things we will be doing later in this book.
In later chapters of this book, the mentioned ACLs will be revisited and fine-tuned to allow additional features, like directory replication.