My first 389ds experience and lessons learned
Quirks
389ds is weird when it comes to configuration. There isn’t much to be found of a configuration file anywhere but instead the ldap configuration is stored in ldap itself. 389ds will always store a local copy of the configuration from a stance of ‘this is how it looked when it worked last’ so if you break the configuration you can still recover.
It also stores the entire ldap tree in files whenever you stop the server. If you desire you can stop the server, update the config via vim then start the server back up. This is discouraged as the config updates are surprisingly good at stopping you from breaking everything.
Setup
Manual
Follow 389 docs for install https://directory.fedoraproject.org/docs/389ds/legacy/install-guide.html
Run setup-ds.pl and follow all prompts on a development server. Get to understand what is required of setting up a basic ldap server. Once created query with ldapsearch to see what is created by default.
Ansible
To get my head around 389ds deploys I forked an existing ansible playbook and built upon it. Look at the default variables, tasks and templates. https://github.com/colbyprior/389-ldap-server
The setup-ds.pl script can also take in a config file instead of prompting for information. Looking at this example may help get you started. https://github.com/colbyprior/389-ldap-server/blob/master/templates/ldap.inf.j2
Ldapsearch
Ldapsearch basics
At a very basic level. To use ldapsearch you should supply a ldap URI, bind dn, search base and password like the example below. If you memorise these arguments you will be on your way to querying ldap.
ldapsearch -H ldap://localhost -D cn=root -b ‘cn=config’ -W
You can use search filters to find specific entries:
ldapsearch -H ldap://localhost -D cn=root -b ‘ou=People,dc=dontthinkjustroll,dc=com’ -W ‘uid=colby’
Also search for specific attributes to make the output readable on larger searches.
ldapsearch -H ldap://localhost -D cn=root -b ‘ou=People,dc=dontthinkjustroll,dc=com’ -W uid
Ldapmodify
Modify vs add vs delete
Ldapmodify commands use an ldif file to perform updates to an ldap server. The ldif requires a changetype parameter so specify if the attribute / entity is being added, deleted or modified. The changetype parameter is inferred when using ldapadd and ldapdelete and is therefore not needed.
Ldapmodify uses the same connection arguments as ldapsearch. You need to also specify -f argument to specify the ldif file.
ldapmodify -H ldap://localhost -D cn=root -W -f example.ldif
Ldif basics
Below is an example of a ldif file. As you can see the changetype is add and the dn is the entity being created. There are other attributes added which are better explained in the schema and security sections.
dn: uid=colby,ou=Staff,ou=People,dc=dontthinkjustroll,dc=com
changetype: add
uid: colby
objectClass: top
objectClass: account
objectClass: person
sn: Prior
cn: Colby
userPassword: password
An ldif for modifying the surname of the newly created user.
dn: uid=colby,ou=Staff,ou=People,dc=dontthinkjustroll,dc=com
changetype: modify
replace: sn
sn: Cheese
An ldif to delete the user.
dn: uid=colby,ou=Staff,ou=People,dc=dontthinkjustroll,dc=com
changetype: delete
Schema
Draw your schema on a whiteboard
Explore your tree once you get your daemon running and you can perform ldapsearch queries. The tree which is created by default may have some organisational units and access control rules already created. Use this to draw how your tree looks on a whiteboard and how it should look.
Object classes
Each object in ldap can have multiple object classes. They determine what ldap attributes can or must be part of that entity. Want a user account? Add the person objectclass. Want that same user account to have uid and gid mappings for linux? Add the posixAccount objectclass.
RedHat has a good documentation page on default objectclasses.
https://access.redhat.com/documentation/en-US/Red_Hat_Directory_Server/8.2/html/Schema_Reference/Directory_Server_Object_Class_Reference.html
Separate users under people
When you first set up 389 it will come with a People tree (eg. ou=People,dc=dontthinkjustroll,dc=com). You can simply put all of your user accounts directly under People but it will likely come back to give you a headache later.
Separate users into logical groups under the People tree. As an example: put staff under ou=Staff and customers under ou=Customers. Want to set up an internal web application using ldap authentication? Set the auth filter to use ou=Staff,ou=People,dc=dontthinkjustroll,dc=com so that way only staff will be able to log in. You still have the ability to set search bases on the People leven so that everyone is included and you can set MemberOf values for more grouping options.
Logical group subtrees
Base installs of 389 will also come with a ou=Groups tree. Make logical separations here like under People. How would you need to group users? Make a application access tree ‘ou=App,ou=Groups,dc=dontthinkjustroll,dc=com’. Make a teams subtree ‘ou=Teams,ou=Groups,dc=dontthinkjustroll,dc=com’.
You can perform actions like create a user, add them to the Sysadmin group under Teams, add the Sysadmin group to the Website Admin group under the App group. More about groups under MemberOf.
Config tree
It is important to remember that your ldap server will have ‘cn=config’ attached on the side where config for how the server operates can be viewed and modified in real time using ldapmodify commands.
Schema tree
The ldap schema is stored under ‘cn=schema’. If you need to update the schema try to add new object classes instead of updating existing ones to save update headaches.
If you are trying to do something it has probably already been done before but maybe your install doesn’t have the object classes you can’t. Check out the latest code in the 389 project to see if what you want is something that has been done before. https://pagure.io/389-ds-base/blob/master/f/ldap/schema
You can put in new object classes and attributes in your slapd schema directory and it will load the new objects on a service restart. See the example from my ansible deploy for how adding object classes and attributes looks like. Also keep in mind the files are read in alphabetical order of the file name and always define attributes before object classes.
/etc/dirsrv/slapd-<instance-name>/schema/80custom.ldif
Plugins
How plugins work
Plugins are enabled and configured under the config tree. I touch on two plugins I enabled as part of my default ldap deploy.
MemberOf
MemberOf is arguably the most important plugin, I think it should come by default. It is very easy to enable as you can see in the ansible playbook. https://github.com/colbyprior/389-ldap-server/blob/master/templates/plugin_memberof.ldif.j2
It gives referential integrity for group memberships. If you add a person to a group, the person’s memberOf group gets automatically updated. If you add the group in question to another group then both the group and the person’s memberOf attribute gets updated. This is what allows us to put on search filters for applications.
DNA plugin
One of the uses of my ldap server is to set up sssd for linux authentication. The DNA plugin auto generates uid and gid numbers for new users who get the posix user and group types. This ties in to your linux uid and gid numbers to ensure consistency.
High availability
Replication is easy
First off you need a replication user. An ldap user account that has access to bind to each server and read the tree so that it can sync changes. https://github.com/colbyprior/389-ldap-server/blob/master/tasks/configure_replication_user.yaml
Set up at least two servers with ldap multi master. My cloudformation stack only sets up two servers to replicate off each other. First you set up a changelog file which the server uses to see what changes need to be replicated. https://github.com/colbyprior/389-ldap-server/blob/master/templates/master_changelog.ldif.j2
Then you add a replication configuration which tells the server what part of the ldap tree to replicate. Note that this doesn’t include cn=config.
https://github.com/colbyprior/389-ldap-server/blob/master/templates/master_replica.ldif.j2
Then the replication agreement is set up, this is on a server per server. So if you have three multi masters they each need a replication agreement for the other two. https://github.com/colbyprior/389-ldap-server/blob/master/templates/master_replication_agreement.ldif.j2
Finally you start the replication. This is now enabled and will stay enabled after reboots and service restarts. https://github.com/colbyprior/389-ldap-server/blob/master/templates/master_init_replica.ldif.j2
Optionally a server can be set up as a read only replica instead of a multi master. Keep in mind a server is either a master, multi master or replica. https://github.com/colbyprior/389-ldap-server/blob/master/tasks/configure_replication_replica.yaml
Load balancer
Setting up multiple ldap servers behind a load balancer is easy. Only allow port 636 and pass it through to your servers. Consider running read only slaves if you have applications with high read requirements. https://github.com/colbyprior/389ds-multi-master-cloudformation/blob/master/formation-stack.yml#L315
Security
Access control and ACI
Access control in 389 is managed by ACI rules which are broken up into a target, permission and bind rule. They can be configured on a entity to cover the subtree underneath it, eg. setting an aci rule on ou=People can allow actions for the accounts under People.
Target (What thing)
There are multiple ways of setting an ACI target but the easiest way of getting started is to set a targetattr value. The following example will allow the uid and sn values.
(targetattr = "uid || sn")
https://access.redhat.com/documentation/en-US/Red_Hat_Directory_Server/8.2/html/Administration_Guide/Managing_Access_Control-Creating_ACIs_Manually.html#tab.LDIF_Target_Keywords
Permission (What action)
The permission is typically used to allow rather than deny actions. A very simple allow (any) will allow any kind of ldap action. Keep in mind that in order for ldapsearch to work properly you will need both read and search.
https://access.redhat.com/documentation/en-US/Red_Hat_Directory_Server/8.2/html/Administration_Guide/Managing_Access_Control-Creating_ACIs_Manually.html#Defining_Permissions-Assigning_Rights
Bind rule (Who)
A bind rule specifies who can access a resource. The ACI rule can be restricted to things like a specific user or an anonymous user like ldap:///anyone.
https://access.redhat.com/documentation/en-US/Red_Hat_Directory_Server/8.2/html/Administration_Guide/Managing_Access_Control-Bind_Rules.html#Bind_Rules-Bind_Rule_Syntax
Putting these examples together creates the following ACI rule. If applied to the ou=People,dc=dontthinkjustroll,dc=com it would allow anyone to search for everyone under the People tree and the result will contain the uid and sn for each person.
aci: (targetattr = "uid || sn")(version 3.0;acl "my example";allow (read, search)(userdn = "ldap:///anyone");)
Any anonymous bind to ldap will then return the uid and sn values.
ldapsearch -x -H ldaps://ldap.dontthinkjustroll.com -b "ou=People,dc=auscert,dc=org,dc=au"
Certutil is bad but TLS is easy
In order to configure TLS on a LDAP server you need to use Certutil. Certutil is a creative solution for managing certificates in a local database for the application to use. Certutil gave me a great deal of pain while setting up and following the official 389ds docs didn’t work for me. In my ansible playbook I use pk12util to create the Certutil db.
https://github.com/colbyprior/389-ldap-server/blob/5e3a795f1a6d544427e598b4ed1b787719e8cda8/tasks/configure_tls.yaml#L30
As long as you have a valid certificate turning on TLS is very easy. You simply add the required configuration to the config tree and restart the server. https://github.com/colbyprior/389-ldap-server/blob/5e3a795f1a6d544427e598b4ed1b787719e8cda8/templates/rsa-encryption.ldif.j2
LDAPS vs StartTLS
LDAPS typically serves over port 636 and it serves LDAP over TLS only. StartTLS is an LDAP connection over the normal port 389 however the connection is upgraded to a TLS connection. I prefer LDAPS as you can open this port on your load balancer or firewall so that only encrypted connections are going to be made.
Using ldapsearch over LDAPS can have some confusing results if you have certificate problems. https://github.com/colbyprior/389-ldap-server/blob/5e3a795f1a6d544427e598b4ed1b787719e8cda8/tasks/configure_tls.yaml#L17
Password hashing
It’s quite good out of the box. Without touching anything passwords will be hashed and salted using SSHA-512. If you are a crypto nerd you know there is better than this but SSHA-512 isn’t bad.
Backup
Backup to S3
db2bak is the command for dumping an entire ldap tree. It dumps to the local filesystem but disk space shouldn’t be a problem depending on your deployment. I bundled the command into a simple shell script which I run in a daily cron job to dump everything from ldap, archive it to S3 then remove the local copy.
https://github.com/colbyprior/389-ldap-server/blob/master/templates/backup-ldap.sh.j2
Restoring from backups
All backup procedures should be tested with restores. Make sure you stop the server you are restoring a backup for and you also disable replication. bak2db is the command for restoring from a backup. Change to your slapd directory then run the bak2db command specifying the full file path of the backup directory.
Misc
Don’t use the java console
Just don’t. It is terrible and you can avoid pain by never going near it.
Don’t use the web admin interface
Don’t bother, it will only serve to confuse you.
