LDAP server with 389ds: Part 3 – ACLs

      1 Comment on LDAP server with 389ds: Part 3 – ACLs

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.

1 thought on “LDAP server with 389ds: Part 3 – ACLs

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.