As the two major enterprise linux distributions (SUSE and Red Hat) have decided to remove OpenLDAP from their platforms, there has been and will continue to be interest from major deployments wanting to move from OpenLDAP to 389 Directory Server on SLE and Red Hat Directory Server on RHEL. We should not only provide supporting tooling to make this possible, but also document the possible migrations paths and strategies to give customers a long term, supported, high quality LDAP install.
Note this excludes FreeIPA/RHIDM which has it’s own LDAP migration tooling, and that not all deployments can move to IPA due to their requirements.
This document will serve not only as a technical discussion of the approach, but also as a reference for downstream documentation teams on possible migration approaches.
389 DS and OpenLDAP while both being LDAP servers, are very different projects, with different features, configurations and approaches. This means there is not a 1:1 parity between the two, so there will always be situations or configurations that can not be adapted fully.
|Feature||OpenLDAP||389 DS||Compatible ?|
|2 way Replication||SyncREPL||389 Specific system||❌|
|MemberOf||Overlay||Plugin||✅ (simple configs only)|
|External Auth||SASL Authd||PTA Plugin||🔨 (Config and Data Changes needed)|
|AD Synchronisation||-||Winsync Plugin||❌|
|Inbuilt Schema||OLDAP Schemas||389 Schemas||🔨 (Upstream work underway)|
|Custom Schema||OLDAP Schemas||389 Schemas||✅ (Migration Required)|
|Database Import||LDIF||LDIF||✅ (Some data manipulation may be needed)|
|Password Hashes||Varies||Varies||🔨 (Some types may work, some may need development or migration)|
|oldap 2 ds repl||-||-||❌ (No mechanism for openldap to replicate to 389 is possible)|
|ds 2 oldap repl||-||SyncREPL||🔨 (Investigation on 389 able to emulate OpenLDAP syncrepl metadata is underway)|
|EntryUUID||Part of OpenLDAP||Plugin||🔨 (Accepted, pathway to rust-by-default underway)|
From this snapshot we can hopefully see that it’s not possible to mix and match openldap with 389 ds, and that there will always be migration steps required. The only feasible mix and match will be if we develop support for 389-ds syncrepl to impersonate openldap’s syncrepl provider (we will likely not consider delta syncrepl). Even in this scenario, as some plugins and configurations will have different attribute values,
This is a set of example topologies we consider we could support migration from
In this scenario, we are able to do a “once off” migration, replacing the OpenLDAP with 389-ds
In this scenario, as we have no method of replication sync, we would consider the migration to be where a parallel 389-ds infrastructure is built, and then the related schema and database is migrated into the new 389 topology.
In this scenario, this is the same as the multiple active RW server scenario. If there are many RO servers, it may not be feasible to buld this fully, so we could have the RO replicas read from 389-ds instead via syncrepl with some live data manipulation. The scope of this is still under investigation.
In this scenario, 389-ds would be built in parallel to use winsync plugin. This would use a different configuration and set of integrations to provide the AD read sync services.
To support the inbuilt openldap schema, 389-ds is should adopt the rfc2307compat schema, which resolves a set of schema conflicts that may arise during a migration. There are no other known conflicts with openldap schema.
A tool called openldap2ds is being developed that can check for and migrate schema to 389-ds from openldap instances. It generates a migration plan which can be reviewed and modified, that then applies changes to the 389-ds instance, as well as suggesting changes for other instances in the topology.
Openldap2ds can also detect some plugins from openldap and configure 389-ds to match. This feature is limited.
Given an ldif export openldap2ds will be able to import an openldap backup into 389ds, performing needed data manipulation to remove openldap specific attributes.
EntryUUID as a plugin for 389-ds - openldap uses this as part of it’s replication model, and many applications are configured to use it as a primary key. If there is an existing parallel 389-ds and openldap instance that will merge, the nsUniqueId and entryUUID will differ, so EntryUUID is developed as a plugin seperate to nsunique id. This adds some duplication in the server, but in return admins do not need to manipulate their entryUUID -> nsuniqueid values, and we don’t need to write another virtual attribute transformer. It also allows admins to post migration change the entryUUID if required.
We do not support saslauthd so any site using this will need to configure pam pass through auth with sssd or other modules.
We do not support TOTP, so this can not be migrated at this time. The versions of openldap on the enterprise distros do not support TOTP anyway, so this is likely not to be an issue.
389-ds does not function as an LDAP proxy, we prefer to have extra read only servers created.
389-ds can use winsync to consume AD information and changes, rather than acting as a proxy.
High Level Workflow
The administrator should inventory their current assets, dataflow and configuration. For example it could be an external IDM that feeds data into silos of OpenLDAP and AD, or it could be OpenLDAP in proxy mode to AD. It could also be pure OpenLDAP.
The replication topology should be accounted for in this step.
It should also be noted which plugins are enabled, and how authentication is performed and where credentials are stored.
The parallel 389-ds instance or topology should be brought up. Test migrations with openldap2ds should be performed. openldap2ds should be run against each server in the topology OR the migration commands should be run manually against each node to ensure they are in the same configuration state.
The openldap2ds tool can be run to import and manipulate the openldap database as needed.
If openldap is acting as a proxy to AD, 389-ds should be configured with winsync. It should be checked that the correct data associations are made to the entries.
It may be required to run both openldap and 389-ds silos in parallel due to the nature of the data and topology. It is critical that writes are able to be directed to BOTH openldap and 389-ds silos OR that they only flow to one or the other and a cutover process is defined.
When the cut over is made in a single data flow scenario, when moving the 389-ds as the incoming write silo, then the remaining openldap stack should be configured to syncrepl from 389-ds OR the entire 389-ds topology should be pivoted to in a single change (this depends on our ability to improve sync repl). This depends on the size and scale of your topology to whether a “single action” cutover is possible.
Rollback plans should always be developed and understood as in any migration. Backups should always be taken as needed (Such as system state backup on windows, ldifs and config on openldap, and db2ldif on 389-ds).
A requested feature has been the ability to have 389-ds be able to provide changes to openldap read-only replicas. It is not feasible to have 389-ds consume from openldap, but to provide is a simpler matter.
Focusing on syncrepl (not delta-sync repl) makes for a simpler, and easier implementation, as well as only focusing on the “refreshOnly” mode. This has largely been implemented already, but some deviations from the current implementation are observed. (See also https://tools.ietf.org/html/rfc4533)
The initial replication is initiated by a search request with the syncRequestOID, SyncRequestValue with a cookie containing the consumers rid and no CSN.
The provider then responds with all entries, with a syncStateOID control that has the state add (1) and the EntryUUID of the entry.
The searchResDone message then provides a SyncDoneOID control with a cookie the rid of the consumer, and the CSN that was replicated up to. The cookie format
The consumer will parse this CSN to order the events that have just been provided, however the consumer then assigns it’s own internal CSN to the events. This CSN is based on the systems localtime and is not a lamport clock, which means that there may be occasions the generated CSN could be out of order relative to others.
The subsequent requests are made with the cookie with the last rid and csn provided in the SyncRequestValue. This allows the provider to know where to start to “send updates” from. These updates are documented below based on the causing operation type.
Openldap requests the following when in dsee changelog mode.
attrs="\* + aci"
We can already provide sync repl with the following setup.
dsconf localhost plugin retro-changelog enable dsconf localhost plugin retro-changelog set --attribute nsuniqueid:targetUniqueId dsconf localhost plugin contentsync enable dsconf localhost backend create --suffix dc=example,dc=com --be-name userRoot dsidm localhost initialise
create a sync capable user.
dn: cn=syncrepl,ou=services,dc=example,dc=com objectClass: top objectClass: organizationalRole objectClass: nsAccount cn: syncrepl userPassword: password nsSizeLimit: -1 nsLookThroughLimit: -1 nsTimeLimit: -1 nsidletimeout: -1 # ldapadd -f 389_sync_acct.ldif -D 'cn=Directory Manager' -w password -x -H ldap://127.0.0.1:3389
We want to limit what can be synced. We achieve this with access controls on the account.
dn: dc=example,dc=com changetype: modify add: aci aci: (targetattr != "aci || creatorsName || modifiersName || createTimestamp || modifyTimestamp || parentId || entryId || nsAccountLock || numSubordinates || tombstoneNumSubordinates || nsSizeLimit || nsLookThroughLimit || nsTimeLimit || nsidletimeout")(targetfilter="(objectClass=*)")(version 3.0; acl "Enable sync repl full read"; allow (read, search)(userdn="ldap:///cn=syncrepl,ou=services,dc=example,dc=com");) # ldapmodify -f 389_sync_aci.ldif -D 'cn=Directory Manager' -w password -x -H ldap://127.0.0.1:3389
Add the dsee.ldif
Configure the database
database mdb suffix "dc=example,dc=com" rootdn "cn=Manager,dc=example,dc=com" rootpw password directory /var/lib/ldap index objectClass eq index entryUUID eq syncrepl rid=123 provider=ldap://172.17.0.2:3389 binddn="cn=syncrepl,ou=services,dc=example,dc=com" bindmethod=simple credentials=password searchbase="dc=example,dc=com" # It is likely wise to limit this to objectClasses you are interested in (IE ou, group, account only) filter="(objectClass=*)" schemachecking=off scope=sub type=refreshOnly retry="3 +" interval=00:00:00:03 updateref ldap://172.17.0.2:3389
check the changelog:
ldapsearch -H ldap://172.17.0.2:3389 -b cn=changelog -D 'cn=Directory Manager' -x -w password
Show the current openldap cookie:ldapsearch -H ldap://127.0.0.1 -b ‘dc=example,dc=com’ -s base -x contextCSN
# example.com dn: dc=example,dc=com contextCSN: 21000101110148.000000Z#000000#000#000000
Due to OpenLDAP’s replication being “whole entry” based, and the details in the syncrepl being very simple, it would be easy to emulate with a small number of changes.
If we detect the initial request with a rid= format, we know that we are in openldap provider mode (compared to other sync repl clients that will provide an empty sync cookie).
We can generate an OpenLDAP compatible CSN by converting the changenumber into a timestamp via unix epoch. By adding an offset we can guarantee that the CSN’s we generate will “always be higher” than anything OpenLDAP can generate, preventing conflicts.
This would allow us to construct an openLDAP compatible synccookie, that has the correct timestamps and that when they come to query us next, we are able to provide “what changed since” and guarantee correct ordering of events.
No other changes are needed.
The currently syncrepl plugin uses nsUniqueId to provide the ID’s for operations. If the EntryUUID plugin is enabled, then we need to use that instead, else OpenLDAP can become confused about the difference between the sent entryuuid in the entry, and the different syncrepl uuid derived from nsuniqueid.
Due to the time based nature, and not using a true lamport clock, it may be possible that entries become out of sync with the consumer in a chaining scenario. In this case, since we only support read-only consumers, we would advise that a full re-init of the consumer should occur. This can be triggered by simply deleting the OpenLDAP database mdb files which will trigger a refresh.
That we may need more schema or other changes to make this possible, and that we may need schema added into openLDAP to make this work. Openldap has a schema check=off mode, but it appears to do nothing and schema continues to be enforced.
OpenLDAP 2.5 has a DSEE compatible sync mode that combines syncrepl searches with a cn=changelog search and interpretation. However, the major enterprise distros are using version 2.4 so this is unlikely to be an option.
William Brown wbrown at suse.de