Access Control Lists (ACLs)
Learn about Access Control Lists (ACLs), a method of controlling and managing network access, including how they work, their types, and their importance in network security.
Table of Contents
Access Control Lists (ACLs)
This article covers the basic and most commonly used features of ACLs.
- ACLs are settings stored in the slapd configuration file (slapd.conf) or configuration database (cn=config)
- ACLs can be defined globally when configured in the frontend database, or database-specific in the database definition in the configuration database
- ACLs are evaluated in the order in which they appear in the configuration
- Database-specific ACLs take precedence ACLs defined in the frontend database
-
The basic format of an ACL is:
to <what> by <who> <privilege>
ACLs - The "to" Clause
The "to
" clause in an ACL defines what entries or attributes the rule will apply to.
Entries
Rules defining access to entries is done by selecting entries based on their naming context. The scope of the selection can, and should also be defined. The basic format of a DN specification is:
to dn.<specifier>="<pattern>"
DN Styles
DN Style | Description |
exact | The exact entry |
base(object) | The exact entry |
regex | A DN matching the specified regular expression (POSIX 'extended') |
one(level) | The DN or any entry one level below the entry |
sub(tree) | Includes all entries in the subtree |
children | Includes all entries below (subordinate to) the DN |
DN Style Examples
Get only the “cn=replicator,ou=admin,dc=example,dc=com” group:
to dn.exact="cn=replicator,ou=admin,dc=example,dc=com" by <who> <permission>
Get all entries in the “ou=accounting,dc=example,dc=com” subtree:
to dn.subtree="ou=accounting,dc=example,dc=com by <who> <permission>
Get all entries one level below the “ou=accounting,dc=example,dc=com” entry:
to dn.one="ou=accounting,dc=example,dc=com by <who> <permission>
Get all child entries of entries in the “ou=accounting,dc=example,dc=com” subtree:
to dn.children="ou=accounting,dc=example,dc=com" by <who> <permission>
Get entries whose CN starts with “application” in the “ou=administrators,dc=example,dc=com” subtree:
to dn.regex="cn=application\w+,ou=administrators,dc=example,dc=com" by <who> <permission>
Attributes
Rules granting access to specified attributes is done by defining a list of attributes, objectClasses or both. The format is:
to attrlist=<attribute1>,<attribute2> by <who> <permission>
When an objectClass is specified, the rule will be applied to all required and optional attributes defined in that objectClass.
to attrlist=<objectClass>,<attribute> by <who> <permission>
Attribute Specifier Examples
To specify control to the "description" and "initials" attribute
to attrlist=description,initials by <who> <permission>
To specify control access to all the attributes defined in the person objectClass
to attrlist=person by <who> <permission>
ACLs - The "by" Clause
The "by
" clause specifies which users the rule will be applied to.
"by
" Specifiers
Specifier | Description |
* | Everyone & everything |
anonymous | Connections before or without authentication |
users | Users that have successfully authenticated |
self.<selfstyle> | An authenticated user's own entry |
dn.<dnstyle> | The DN of a single, specific entry |
group.<groupstyle> | A group containing member entries |
ssf | The overall Security Strength Factor |
tls_ssf | Security Strength Factor for TLS connections |
sasl_ssf | Security Strength Factor for SASL connections |
transport_ssf | Security Strength Factor for all transport levels |
peername | The socket address of the client |
sockname | The socket address of the server listener |
sockurl | The URL of the server listener |
domain | The DNS name of the client |
set | ACL sets |
While "*
", "anonymous
", "users
" and "self
" are simple, the "dn.<style>
", "group.<style>
"and "ssf
" specifiers deserve further explanation:
dn.[dnstyle]
DN Style | Description |
exact/base | A single entry |
regex | A POSIX 'extended' regular expression to match entries |
one(level) | Entries one level below the specified DN |
sub(tree) | Entries at any level below the specified DN |
children | Entries that are subordinate to the specified DN |
DN Style Examples
By an exact DN:
to <what> by dn.exact="uid=replicator,ou=services,dc=example,dc=com" <permission>
By DNs that match a regular expression:
to <what> by dn.regex="uid=apache_\w+,ou=services,dc=example,dc=com" <permission>
By all DNs one level below dc=example,dc=com:
to <what> by dn.one="dc=example,dc=com" <permission>
By all DNs in the “dc=example,dc=com” subtree.
to <what> by dn.sub="dc=example,dc=com" <permission>
By all DNs that are children of “ou=people,dc=example,dc=com”.
to <what> by dn.children="ou=people,dc=example,dc=com" <permission>
group.[groupstyle]
The most common groupstyle is “exact”. There is also an “expand” style which uses variable substitution using regular expressions (see: 8.4.5 Managing Access with Groups in the OpenLDAP handbook).
For maximum flexibility, the syntax group/groupOfNames/member.exact=""
can be used to cover types of groups or group membership. If using groups that require unique members, use group/groupOfUniqueNames/uniqueMember.exact=""
.
Group Style Example:
Allow access to members of the “cn=student-read,ou=groups,dc=example,dc=com” group to read specified resources.
to <what> by group/groupOfNames/member.exact="cn=student-read,ou=groups,dc=example,dc=com" <permission>
SSF (Security Strength Factor)
The server uses Security Strength Factors (SSF) to indicate the relative strength of protection. A SSF of zero (0) indicates no protections are in place. A SSF of one (1) indicates integrity protection are in place. A SSF greater than one (>1) roughly correlates to the effective encryption key length.
SSF Examples
Require encryption of any strength for read permission:
to <what> by ssf=1 read
Require 256 bit encryption for write operations performed via TLS connections:
to <what> by tls_ssf=256 write
ACLs - The "permission" Clause
Permissions can be granted by either assigning a level or privilege
Permissions (in order from least to most):
Level | Privilege | Description |
none | 0 | No permission granted |
disclose | d | Allow disclosure of information on error |
auth | x | Permission to access attribute for authorization |
compare | c | Permission to compare attributes |
search | s | Permission to search for entries |
read | r | Permission to read entries/attributes |
write (add/modify/delete) | w/a/z | Permission to write, modify, add or delete resources |
manage | m | All permissions + administrative access |
When using levels, each level inherits all the permission levels above.
For example, the "read
" level includes "disclose
", "auth
", "compare
" and "search
". The "write
" level includes "read
" and above levels.
Privileges are more granular, and allow assignment, escalation and deescalation using the "=", "+", and "-" operators.
Under most circumstances, using levels is sufficient.
Permission Examples
Allow access to the userPassword attribute by anonymous users for authorization and no access for anyone else. (Note: auth
access allows an attribute to be used for authorization internally, but will not be readable externally):
to attrs=userPassword by anonymous auth by * none
Allow read access to "ou=janitorial,dc=example,dc=com" by the “cn=janitorial-read,ou=groups,dc=example,dc=com” and write access by the “cn=janitorial-write,ou=groups,dc=example,dc=com” group:
to dn.subtree="ou=janitorial,dc=example,dc=com"
by group/groupOfNames/member="cn=janitorial-read,ou=groups,dc=example,dc=com" read by group/groupOfNames/member="cn=janitorial-write,ou=groups,dc=example,dc=com" write
ACLs - ACL Controls
There are three types of controls:
stop
= If a condition within the rule is matched, stop evaluating further access controls and return the access level.
continue
= If a rule is matched, continue evaluating the conditions within that rule.
break
= If a rule is matched, break out of that rule and start evaluating the next one.
The stop
condition is the default, so if the "continue" and "break" controls are not specified, the "stop" condition is applied.
Control Usage
Controls come after a <permission>:
to <what> by <who> <permission> <control>
ACL Control Examples
Usual starting ACL, giving full access to "special" users:
access to * by dn.exact="cn=replicator,dc=example,dc=com" read by dn.exact="cn=appadmin,ou=services,dc=example,dc=com" write
by * break
Controlling access to an attribute, but entry type not specified, continues to next ACL:
access to attrs=userPassword
by anonymous auth
by self write
by dn.exact="uid=appadmin,ou=services,dc=example,dc=com" manage
by group/groupofmembers/member.exact="cn=Admins,ou=Groups,dc=example,dc=com" write
by dn.exact="cn=replicator,o=nyu" read
Continue control to "layer" access:
access to dn.subtree="ou=services,dc=example,dc=com" by by group/groupofmembers/member.exact="cn=Audit,ou=Groups,dc=example,dc=com" read continue
by group/groupofmembers/member.exact="cn=Admins,ou=Groups,dc=example,dc=com" write by * break
ACL Best Practices
- Start off with limited access to everything by everyone
- Very specific and commonly encountered rules should be written first (replication and service accounts). This increases efficiency.
Always use dn.regex=<pattern> when you intend to use regular expression matching. dn=<pattern> alone defaults to dn.exact<pattern> (in some historic versions, it used to default to dn.regex=<pattern>). As a consequence, an explicit style clears any doubt about what you're doing.
Use (.+) instead of (.*) when you want at least one char to be matched. (.*) matches the empty string as well.
Don't use regular expressions for matches that can be done otherwise in a safer and cheaper manner. Examples:
dn.regex=".*dc=example,dc=com"
is unsafe and expensive:
- Unsafe because any string containing dc=example,dc=com will match, not only those that end with the desired pattern; use ".*dc=example,dc=com$" instead.
- Unsafe also because it would allow any attributeType ending with dc as naming attribute for the first RDN in the string, e.g. a custom attributeType mydc would match as well. If you really need a regular expression that allows just dc=example,dc=com or any of its subtrees, use "^(.+,)?dc=example,dc=com$", which means: anything to the left of dc=..., if any (the question mark after the pattern within brackets), must end with a comma;
- Expensive because if you don't need submatches, you could use scoping styles, e.g.
To include "dc=example,dc=com" in the matching patterns:
dn.subtree="dc=example,dc=com"
To exclude "dc=example,dc=com" from the matching patterns:
dn.children="dc=example,dc=com"
To allow exactly one sublevel matches only:
dn.onelevel="dc=example,dc=com"
Always use ^ and $ in regexes, whenever appropriate, because:
ou=(.+),ou=(.+),ou=adressbooks,o=basedn
will match:
something=bla,ou=xxx,ou=yyy,ou=adressbooks,o=basedn,ou=addressbooks,o=basedn,dc=some,dc=org
Always use ([^,]+) to indicate exactly one RDN, because (.+) can include any number of RDNs; e.g. ou=(.+),dc=example,dc=com will match ou=My,o=Org,dc=example,dc=com, which might not be what you want.
Don't use the dn.regex form for <by> clauses if all you need is scoping and/or substring replacement; use scoping styles (e.g. exact, onelevel, children or subtree) and the style modifier expand to cause substring expansion.
For instance:
access to dn.regex=".+,dc=([^,]+),dc=([^,]+)$" by dn.regex="^[^,],ou=Admin,dc=$1,dc=$2$$" write
although correct, can be safely and efficiently replaced by
access to dn.regex="^.+,(dc=[^,]+,dc=[^,]+)$" by dn.onelevel,expand="ou=Admin,$1" write
where the regex in the <what> clause is more compact, and the one in the <by> clause is replaced by a much more efficient scoping style of onelevel with substring expansion.