One project we’ve been working on for a while is single sign-on across all our servers and other services (e.g. SVN repository, a few other things). One thing I wanted to avoid, I guess for mostly religious reasons, was reliance on a Windows instance for any of our production environment. The logical part of my brain knows that people build huge websites with Windows farms and AD, but my gut still doesn’t trust it. So what I wanted to do was setup OpenLDAP as a “slave” to an Active Directory “master” and have all the LDAP info propagate over the slave whenever any changes were made in the master. I’ve done this with DNS – setup Bind as a slave to an AD server and everything basically works as I expect in a Bind-Bind master/slave scenario. Well, it turns out that it doesn’t work like that when it comes to LDAP. Apparently AD doesn’t follow the RFC for LDAP (surprise!) so many things that would be expected to work with OpenLDAP won’t.
I thought to myself, well, I can just go ahead and write something that will sync AD -> OpenLDAP every 5 minutes. This would mostly work, except there’s apparently no way to retrieve the user’s password via LDAP – the field is write-only. This makes it so I could essentially clone the whole LDAP tree… except for the piece of info that would let people login, which is the only thing I want it for. This was frustrating.
I decided to explore a caching proxy. I was thinking if I could setup OpenLDAP as a caching proxy to Active Directory, and set a cache time of ~15 minutes, it would at least let me withstand a Windows reboot (it’s 2009 and I still feel like any Windows fix begins and ends with a reboot). I figured I’d start with a regular proxy first. This turned out to be relatively simple: in slapd.conf, at the bottom, add:
database ldap suffix "dc=example,dc=com" uri "ldap://activedirectory.example.com" acl-bind bindmethod=simple binddn="some user's DN" credentials=password
Restart slapd and do an ldapsearch against the new ldap server and it will relay the request to the AD server and relay the response to you.
Caching is another story. It seemed like it should be straightforward – yum install openldap-servers-overlays – but on my FC11 box, the package didn’t exist, and on my CentOS 5.4 box, openldap-servers-overlays didn’t appear to contain the pcache overlay used for caching. So I gave up on that.
I ended up shelving the whole idea for a while and just created 2 Windows AD VMs that will serve as Production AD auth boxes. I got everything configured – here’s a sample .htaccess file that queries our AD server for a user (by sAMAccountName, e.g. the “user” part of a username in the user@domain login name) and checks that the user is in the Sysadmins group:
AuthBasicProvider ldap AuthType basic AuthName "AD LDAP Test" AuthLDAPURL "ldap://activedirectory.example.com/OU=Users,DC=example,DC=com?sAMAccountName?sub" AuthzLDAPAuthoritative On AuthLDAPGroupAttribute member AuthLDAPBindDN ldapuser@example.com AuthLDAPBindPassword password Require ldap-group CN=Sysadmins,OU=Internal Groups,OU=Groups,DC=example,DC=com
This works (I’ve modified it so it doesn’t include our real info, though). It will let the user in if their credentials are OK and if they are in the required group. What I discovered, however, is that if you go into AD and disable the user’s account, they are still allowed to log in (assuming the user is still in the account). This seemed stupid. I did some more research and discovered userAccountControl. Apparently you need to use bit masking against this attribute to determine if an account is disabled. This is as far as I’d gotten before vacation, but let me say this:
BRILLIANT, MICROSOFT!
Microsoft provides a neat trick to work with bitmasks.
They have defined a matchingrules. We documented this at:
http://ldapwiki.willeke.com/wiki/LDAP%20Query%20using%20a%20bitwise%20filter
So putting this to use, you can determine the disabled users with an LDAP filter:
http://ldapwiki.willeke.com/wiki/LDAP%20Queries%20AD%20Integration%20Specific#section-LDAP+Queries+AD+Integration+Specific-AllUsersWhoseAccountIsDisabled
Now, figure put how to determine if the password it expired is a bit more involved.
-jim
Thanks a lot, Jim! I had seen some of that stuff when I was looking into this, but haven’t had time to try it out yet.
In openldap proxy configuration secion you can replace
acl-bind bindmethod=simple binddn=”some user’s DN” credentials=password
with
rebind-as-user
which will effectively use the user that tries to authenticate to AD for actual bind. Kind of removes necessity of storing AD username password in one more place.