During a staged Active Directory migration from Domain A to Domain B, we hit a common but frustrating problem: legacy applications that only support a single LDAP endpoint couldn’t authenticate users split across two domains.
This post covers our journey from an overly complex OpenLDAP virtual directory attempt to a simple, effective Python-based solution.
The Problem
When migrating users between AD domains in stages, you end up with users in both domains simultaneously. Modern applications handle this gracefully with multiple LDAP endpoints or federated identity. Legacy applications? Not so much.
Our legacy apps expected one LDAP server. Users were in two domains. Something had to bridge the gap.
First Attempt: OpenLDAP Virtual Directory
The “enterprise” solution was to deploy OpenLDAP as a virtual directory—a proxy that would present a unified namespace:
dc=proxy,dc=example,dc=org
├── ou=REGION1 → AD Forest #1
└── ou=REGION2 → AD Forest #2
All inbound connections would use LDAPS (636), proxying to Active Directory Global Catalogs on port 3269.
The Challenges
This approach quickly became a troubleshooting nightmare:
Overlapping namingContexts: OpenLDAP refused to start when both parent and child suffixes were defined without the glue module. Database definition order became critical.
Unsupported directives: Our Linux distribution didn’t support several slapd.conf directives including tls_cacertdir, tls_reqcert, and multi-line idassert-bind blocks.
rewriteMap URI warnings: The strict slaptest parser complained about “illegal URI” formats, even when the URIs were functional.
AD anonymous bind blocking: Active Directory Global Catalogs rejected anonymous searches, requiring careful credential assertion configuration.
We eventually got it working with:
- Careful database ordering (child databases before parent)
- Global TLS configuration instead of per-database settings
- Single-line
idassert-bindstatements - Frontend rwm overlays for convenience binds (DOMAIN\user, UPN formats)
But the configuration was fragile, difficult to maintain, and hard to hand off to other team members.
The Better Solution: A Simple Python Proxy
After wrestling with OpenLDAP, we stepped back and asked: what do we actually need?
The answer was simple: route LDAP authentication requests to the correct domain controller based on the user’s UPN suffix.
Instead of a complex virtual directory, we built a lightweight Python proxy:
Legacy Application
|
| (Single LDAP endpoint: proxy:3389)
v
LDAP Proxy Server
|
|-- [email protected] → DC-A
|-- [email protected] → DC-B
The proxy:
- Accepts LDAP bind requests on a single port
- Parses the UPN to extract the domain
- Routes to the appropriate domain controller
- Returns the authentication result
No complex rewrite rules. No subordinate databases. No glue overlays. Just straightforward request routing.
Results
The Python proxy deployed in under an hour and handled our migration perfectly:
- Single endpoint for all legacy applications
- Automatic routing based on UPN domain suffix
- Easy configuration via JSON file
- Comprehensive logging for troubleshooting
- Zero licensing costs compared to enterprise IAM solutions
Recommendation
If you’re facing a similar challenge during an AD migration, skip the OpenLDAP virtual directory complexity. A purpose-built proxy that does exactly what you need is faster to deploy, easier to maintain, and simpler to troubleshoot.
I’ve published the LDAP proxy solution on GitHub: github.com/nasomers/ldap-proxy
It’s designed specifically for staged AD migrations where legacy applications need to authenticate users across multiple domains through a single LDAP endpoint. The README includes deployment instructions, configuration examples, and troubleshooting guides.
Lessons Learned
- Complexity isn’t always the answer. The “enterprise” OpenLDAP solution was technically impressive but operationally painful.
- Understand the actual requirement. We needed authentication routing, not a full virtual directory.
- Build for maintainability. The next person to touch this system will thank you for choosing simplicity.