Now that we’ve set up an instance of the 389 Directory Server in part 1 and configured essential plugins in part 2, it’s time to take a closer look at access-control list (ACLs). After all, regular users of the directory shouldn’t be able to change data that they’re not supposed to or have universal read access in most use cases.
4. ACLs
The directory server grants or denies access to objects in the DIT based on what’s called Access Control Instructions (ACI). These are multi-valued (operational) attributes that are added to the directory entry in question. There are eight operations that the server can allow or deny access for: read, write, add, delete, search, compare, selfwrite and proxy.
The syntax of an ACI attributes looks like this:
(target_rule) (version 3.0; acl "ACL_name"; permission_rule bind_rules;)
4.1. 389 Directory Server sample ACIs
Or course, this can get rather complex really quickly and is heavily dependent on the environment and use case. But to get started, there’s a set of sample ACIs that were installed during the setup of our 389ds instance in part 1. Since we’ve set the sample_entries
option to yes
we can take advantage of those:
[general] full_machine_name = ldap.example.com start = True strict_host_checking = False [slapd] instance_name = localhost root_password = mysecret port = 389 secure_port = 636 self_sign_cert = False [backend-userroot] sample_entries = yes suffix = dc=example,dc=com
This does a couple of things: It sets up the organizational units (OU) groups and people where future users and groups can be stored and creates a demo user and group within these OUs. But it also creates the OU permissions that contains a bunch of groups:
- group_admin
- group_modify
- user_admin
- user_modify
- user_passwd_reset
- user_private_read
These groups are then used in various ACI attributes. We can list those by running:
ldapsearch -LLL -D "cn=Directory Manager" -W -x -b "dc=example,dc=com" -s sub '(aci=*)' aci
The output should look like this:
dn: dc=example,dc=com aci: (targetattr="dc || description || objectClass") (targetfilter="(objectClass=domain)") (version 3.0; acl "Enable anyone domain read"; allow (read, search, compare) (userdn="ldap:///anyone"); ) aci: (targetattr="ou || objectClass") (targetfilter="(objectClass=organizationalUnit)") (version 3.0; acl "Enable anyone ou read"; allow (read, search, compare) (userdn="ldap:///anyone"); ) dn: ou=groups,dc=example,dc=com aci: (targetattr="cn || member || gidNumber || nsUniqueId || description || objectClass") (targetfilter="(objectClass=groupOfNames)") (version 3.0; acl "Enable anyone group read"; allow (read, search, compare) (userdn="ldap:///anyone"); ) aci: (targetattr="member") (targetfilter="(objectClass=groupOfNames)") (version 3.0; acl "Enable group_modify to alter members"; allow (write) (groupdn="ldap:///cn=group_modify,ou=permissions,dc=example,dc=com"); ) aci: (targetattr="cn || member || gidNumber || description || objectClass") (targetfilter="(objectClass=groupOfNames)") (version 3.0; acl "Enable group_adminto manage groups"; allow (write, add, delete) (groupdn="ldap:///cn=group_admin,ou=permissions,dc=example,dc=com"); ) dn: ou=people,dc=example,dc=com aci: (targetattr="objectClass || description || nsUniqueId || uid || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate") (targetfilter="(objectClass=posixaccount)") (version 3.0; acl "Enable anyone user read"; allow (read, search, compare) (userdn="ldap:///anyone"); ) aci: (targetattr="displayName || legalName || userPassword || nsSshPublicKey") (version 3.0; acl "Enable self partial modify"; allow (write) (userdn="ldap:///self"); ) aci: (targetattr="legalName || telephoneNumber || mobile || sn") (targetfilter="(|(objectClass=nsPerson)(objectClass=inetOrgPerson))") (version 3.0; acl "Enable self legalname read"; allow (read, search, compare) (userdn="ldap:///self"); ) aci: (targetattr="legalName || telephoneNumber") (targetfilter="(objectClass=nsPerson)") (version 3.0; acl "Enable user legalname read"; allow (read, search, compare) (groupdn="ldap:///cn=user_private_read,ou=permissions,dc=example,dc=com"); ) aci: (targetattr="uid || description || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalName || telephoneNumber || mobile") (targetfilter="(&(objectClass=nsPerson)(objectClass=nsAccount))") (version 3.0; acl "Enable user admin create"; allow (write, add, delete, read) (groupdn="ldap:///cn=user_admin,ou=permissions,dc=example,dc=com"); ) aci: (targetattr="uid || description || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalName || telephoneNumber || mobile") (targetfilter="(&(objectClass=nsPerson)(objectClass=nsAccount))") (version 3.0; acl "Enable user modify to change users"; allow (write, read)(groupdn="ldap:///cn=user_modify,ou=permissions,dc=example,dc=com"); ) aci: (targetattr="userPassword || nsAccountLock || userCertificate || nsSshPublicKey") (targetfilter="(objectClass=nsAccount)") (version 3.0; acl "Enable user password reset"; allow (write, read) (groupdn="ldap:///cn=user_passwd_reset,ou=permissions,dc=example,dc=com"); ) dn: ou=services,dc=example,dc=com aci: (targetattr="objectClass || description || nsUniqueId || cn || memberOf || nsAccountLock ") (targetfilter="(objectClass=netscapeServer)") (version 3.0; acl "Enable anyone service account read"; allow (read, search, compare) (userdn="ldap:///anyone"); )
Wow, that’s a lot to unpack. But it looks worse than it actually is. Let’s go through one ACI as an example:
dn: ou=people,dc=example,dc=com ... aci: (targetattr="userPassword || nsAccountLock || userCertificate || nsSshPublicKey") (targetfilter="(objectClass=nsAccount)") (version 3.0; acl "Enable user password reset"; allow (write, read) (groupdn="ldap:///cn=user_passwd_reset,ou=permissions,dc=example,dc=com"); )
Line 1 tells us that this access control instruction acts on the entry ou=people,dc=example,dc=com
and all entries below (if it has children), i.e. all users in our directory.
It only concerns the attributes userPassword
, nsAccountLock
, userCertificate
or nsSshPublicKey
(line 4) on nsAccount
class objects (line 5).
And it grants write and read rights (line 8) to every user who’s a member of our permission OU cn=user_passwd_reset,ou=permissions,dc=example,dc=com
(line 9).
Line 7 describes what the ACI does in a human readable way.
4.2. Granting privileges
By adding a user to one or more groups of the permissions OU we can grant them the associated privileges. For example, to give the user Eve (for details on how that user was created check part 2) privileges to modify certain attributes of other users we simply add it to the user_modify
group:
# ldapmodify -D "cn=Directory Manager" -w mysecret -x << EOL dn: cn=user_modify,ou=permissions,dc=example,dc=com changetype: modify add: member member: uid=eve,ou=people,dc=example,dc=com EOL modifying entry "cn=user_modify,ou=permissions,dc=example,dc=com"
Since the groups in the permissions OU are fairly self-explanatory and the names of the default ACIs also quite descriptive it should be somewhat straightforward how to use them.
4.3. Changing ACIs
Let’s assume we would like to change these sample ACIs to make access to the directory a bit more restrictive. For example it could be necessary (e.g. for GDPR reasons) to deny regular users the permission to search the directory and read the personal details of other users (i.e. attributes such as their displayName
or even uid
, since those are often comprised of first and last name).
In this case the ACI to change would be:
dn: ou=people,dc=example,dc=com aci: (targetattr="objectClass || description || nsUniqueId || uid || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate") (targetfilter="(objectClass=posixaccount)") (version 3.0; acl "Enable anyone user read"; allow (read, search, compare) (userdn="ldap:///anyone"); )
since this ACI allows anyone to read attributes such as displayName
or uid
(see the targetattr
list in line 3).
To change that, we delete the ACI and recreate it with the userdn
altered from anyone to self:
# ldapmodify -D "cn=Directory Manager" -w mysecret -x << EOL dn: ou=people,dc=example,dc=com changetype: modify delete: aci aci: (targetattr="objectClass || description || nsUniqueId || uid || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate")(targetfilter="(objectClass=posixaccount)")(version 3.0; acl "Enable anyone user read"; allow (read, search, compare)(userdn="ldap:///anyone");) dn: ou=people,dc=example,dc=com changetype: modify add: aci aci: (targetattr="objectClass || description || nsUniqueId || uid || displayName || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn || memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate")(targetfilter="(objectClass=posixaccount)")(version 3.0; acl "Enable self user read"; allow (read, search, compare)(userdn="ldap:///self");) EOL modifying entry "ou=people,dc=example,dc=com" modifying entry "ou=people,dc=example,dc=com"
Now a user is only able to read their own attributes but not those of other users below the OU people.
Since ACLs are so heavily dependent on use case and environment this will conclude part 3. You should check the RHDS 11 documentation for more details on ACLs to suit your concrete needs but the sample ACIs should get you off and running.
In part 4 we’re going to take a look at password storage schemes, securing our 389ds instance with TLS and how to disable anonymous access.
Great article. Thanks a lot.