segunda-feira, 8 de outubro de 2012

Apache and Subversion authentication with Microsoft Active Directory

by Sander Marechal
Last updated on 2009-02-16@22:43. The company I work for had finally realized the benefits of a decent source code versioning system so after a short evaluation they settled on Subversion. To make user management easier they also wanted to use Microsoft Active Directory, so I set off on a quest to make Apache talk to our Active Directory 2003 server for authentication. Before I explain to you how I set this all up on Debian Etch I have to get something off my chest first. Sensitive people may want to skip the next paragraph.
Microsoft Active Directory is a bit-rotten crock that should have never seen the light of day. After two full days of waving dead chickens at it, trying to make any sense of it's irrational behavior I would love nothing more than to pick it up and throw it off the roof of our building, BofH style, aiming it at the PHB that bought it in the first place. Or it's programmer. Whomever passes by first. It's API only vaguely resembles LDAP after at least three bottles of whiskey or one pan galactic gargle blaster and squinting really, really hard. Fortunately our management has seen the light of day after this little misadventure and in a few months we're migrating to Open-Xchange.
Right. That's settled. Back to making it work because we need Subversion before we have migrated to Open-Xchange. Let's start off by installing a bunch of software that we need: apache2, subversion and libapache2-svn. Make sure that the correct modules are enabled by symlinking then from /etc/apache2/mod-enabled to /etc/apache2/mods-available. Here are the relevant files:
  1. $ ls -al /etc/apache2/mods-enabled
  2. alias.load -> ../mods-available/alias.load
  3. auth_basic.load -> ../mods-available/auth_basic.load
  4. authnz_ldap.load -> /etc/apache2/mods-available/authnz_ldap.load
  5. authz_default.load -> ../mods-available/authz_default.load
  6. authz_user.load -> ../mods-available/authz_user.load
  7. dav.load -> ../mods-available/dav.load
  8. dav_svn.conf -> ../mods-available/dav_svn.conf
  9. dav_svn.load -> ../mods-available/dav_svn.load
  10. ldap.load -> ../mods-available/ldap.load
Apache2 on Debian Etch ships with mod_authnz_ldap instead of mod_auth_ldap, so if most of the online tutorials for LDAP authentication did not work for you, that's why. mod_authnz_ldap works just a little bit different. I am going to implement things in such a way that makes it easy to test your configuration in between. First we get Active Directory working and then we look at Subversion.

Active Directory authentication

Start off by creating a directory where later on you will host Subversion repositories and create a basic Apache configuration for it. For ease of testing make sure that you can view directory indexes. I chose to put my repositories under /var/lib/svn and I will use a virtual server for it. Create a new configuration file /etc/apache2/sites-available/svn and symlink it from/etc/apache2/sites-enabled/.
Update: You should not point your DocumentRoot to the directory that contains all your repositories. If you do that then Apache will not know if it should handle those himself of if he should hand it over to the Subversion module. That is why in these examples the DocumentRoot to /var/lib/svn/htdocs.
  1. NameVirtualHost *
  2. <VirtualHost *>
  3.         DocumentRoot /var/lib/svn/htdocs
  4.         ServerName
  6.         ErrorLog /var/log/apache2/error.log
  7.         LogLevel warn
  8.         CustomLog /var/log/apache2/access.log combined
  9.         ServerSignature On
  11.         <Directory "/var/lib/svn">
  12.                 Options Indexes FollowSymLinks MultiViews
  13.                 Order allow,deny
  14.                 allow from all
  15.         </Directory>
  16. </VirtualHost>
Now you should modify your local LDAP configuration. There's a problem with references when using Active Directory so you need to turn them off. Edit your /etc/ldap/ldap.conf and add:
  1. REFERRALS       off
Now you can add the LDAP configuration directives to your Apache configuration. I find it very useful to test Active Directory using the LDAP protocol first. You can use this Java LDAP browser to test Active Directory an lookup some of the information that you need to add to Apache. LDAP authentication is a two-step process. First you need to bind LDAP to apache, then you can query information. So, you need an LDAP account to bind to. I recommend setting up a separate user for this and grating him rights to read everything but write nothing. You can test this account using the applet. Note that when connecting to the applet you need to specify the account to bind to as the "user principal name" ( instead of the "distinguished name" (CN=username,DC=example,DC=com). In Apache you can use either. Here's a screenshot of the applet showing the settings that should work.
Update: The LDAP browser applet has disappeared from it's original website. Thankfully Aaron Z. Ward has found other copies of the applet at filewatcher. There is no license included so I cannot host it as an applet myself, but you should be able to download and run it locally if you have Java.
If port 389 does not work for you for some reason, try port 3268. That port speaks a different LDAP dialect apparently (yes, that confuses me too). After you have filled out the hostname, port and protocol version you can click the "Fetch DNs" button to fill the "Base DN" field. When you click "connect" you should be able to browse your Active Directory.
Update: Peter Harvey-Rice let me know by e-mail about the difference between ports 389 and 3289.
Port 389 talks to the local AD server, and can see the local AD tree. Port 3289 talks to the 'Global Directory' on the AD server - if the option is enabled, and can see the whole forest if you have more than one tree in the directory - of course the other trees would be on other servers - but the info is consolidated into one forest.
When this works it's time to add the Apache LDAP configuration directives. I will explain them one by one afterwards. Add this to your VirtualHost configuration:
  1. <Location "/">
  2.         AuthBasicProvider ldap
  3.         AuthType Basic
  4.         AuthzLDAPAuthoritative off
  5.         AuthName "My Subversion server"
  6.         AuthLDAPURL "ldap://,DC=com?sAMAccountName?sub?(objectClass=*)" NONE
  7.         AuthLDAPBindDN "CN=apache,CN=Users,DC=example,DC=com"
  8.         AuthLDAPBindPassword hackme
  10.         require valid-user
  11. </Location>
AuthBasicProvider ldap and AuthType Basic tell Apache to use LDAP for authentication. AuthzLDAPAuthorative off tells Apache that LDAP does not have the final word over who gets access and who doesn't. This is one of the differences between mod_auth_ldap and mod_authnz_ldap. In our case, LDAP just passes some information back to Apache and mod_authz_user has the final decision over who gets access and who does not. The AuthName directive sets the title that the users will see on their login popup. Next up is the AuthLDAPUR:. It's built up as such:
  1. "protocol://hostname:port/base?attribute?scope?filter" NONE
base is the BaseDN you want to search under. Pick whatever worked in the Java Applet. Usually just your domain name (above it's will do. The LDAP attribute is what you try to match to the username that the user typed in. Browse through LDAP to see what possibilities are available. The sAMAccountName is the name that Windows users use to login to their system. Thescope parameter tells LDAP how deep to search beneath the BaseDN. Do yourself a favour and leave it on "sub" (all the way). Thefilter determines what kind of objects should be returned. In my example I play safe again and say "all objects".
Officially the baseattributescope and filter are all optional variables but Active Directory refused to play ball if I did not specify everything. Also, I have no idea why the URL needs to be in double quotes and why it needs to be followed by the wordNONE. All I know is that it doesn't work if I omit it. If someone knows, please leave a comment so I can amend this article.
Updated on 2007-12-03@23:38. Alex Belbey contributes that NONE specified the kind of connection to use. In this case an unsecured connection (as opposed to e.g. an SSL or TLS encrypted connection).
stablish an unsecure connection on the default LDAP port. This is the same as ldap:// on port 389.
Establish a secure connection on the default secure LDAP port. This is the same as ldaps://
Establish an upgraded secure connection on the default LDAP port. This connection will be initiated on port 389 by default and then upgraded to a secure connection on the same port.
After the AuthLDAPURL is the user information for the user you wish to bind LDAP to. You can use the distinguished name as I have done in the example, but you can also use the user principal name:
  1. AuthLDAPBindDN ""
Finally we tell Apache with the require directive that all users should be given access. If you now restart your Apache server with/etc/init.d/apache2 restart you should be able to successfully login. Congratulations, the hardest part is done. If it does not work then you need to look at the apache error log to see what goes wrong. It's a bit cryptic so I will explain that as well. As I explained before, LDAP authentication is a two-step process of binding and querying. Either step can fail and the error log will tell you why. If the bind step fails then there is something wrong with the AuthLDAPBindDN, the AuthLDAPBindPassword or theAuthLDAPURL. Here's what a bind failure looks like:
  1. auth_ldap authenticate: user apache authentication failed; URI / [LDAP: ldap_simple_bind_s() failed][Invalid credentials]
If the bind works but something goes wrong with the query, the error is probably caused by a fault AuthLDAPURL and will look something like:
  1. auth_ldap authenticate: user John Doe authentication failed; URI / [ldap_search_ext_s() for user failed][Operations error]
It's also possible that you do not see any error at all in the logfile. In that case, LDAP works but something goes wrong when Apache's mod_authz_user tries to determine if it should grant access or not.
Update: Mark van Sintfiet adds that in order for require ldap-group to work, you should use the full distinguishedNamefield in the ldap-group directive. If you do not, Active Directory will fail to authenticate. You can use the Java LDAP browser mentioned above to lookup the distinguishedName.

Subversion integration

Adding subversion to the LDAP/Apache mix is actually quite easy. Start off by removing the  block and theDocumentRoot directive because you cannot access the same URL though regular Apache and Subversion at the same time. You can also simply point the DocumentRoot somewhere else so you can create an information page when users hit the root. I will be setting up two groups of repositories that are writable by two groups of LDAP users, plus a sandbox repository for everyone so they can play with Subversion. Start by creating two directories in /var/lib/svn that will hold the repositories. Then create some Subversion repositories.
  1. $ cd /var/lib/svn
  2. $ mkdir group1
  3. $ mkdir group2
  4. $ svnadmin create /var/lib/svn/sandbox
  5. $ svnadmin create /var/lib/svn/group1/g1-repository
  6. $ svnadmin create /var/lib/svn/group1/g2-repository
Now you need to create some  directives in Apache for these repositories. The require ldap-group directives tell Apache to only allow in a certain group. Note that the ldap-group value must not be in quotes. By using a  I only protect writing to a repository. Everyone can read all repositories. Here is what the full configuration looks like in the end:
  1. NameVirtualHost *
  2. <VirtualHost *>
  3.         DocumentRoot /var/lib/svn/htdocs
  4.         ServerName
  6.         ErrorLog /var/log/apache2/error.log
  7.         LogLevel warn
  8.         CustomLog /var/log/apache2/access.log combined
  9.         ServerSignature On
  11.         <Location "/">
  12.                 AuthBasicProvider ldap
  13.                 AuthType Basic
  14.                 AuthzLDAPAuthoritative off
  15.                 AuthName "My Subversion server"
  16.                 AuthLDAPURL "ldap://,DC=com?sAMAccountName?sub?(objectClass=*)" NONE
  17.                 AuthLDAPBindDN "CN=apache,CN=Users,DC=example,DC=com"
  18.                 AuthLDAPBindPassword hackme
  20.                 require valid-user
  21.         </Location>
  23.         # The sandbox repository can be written to by anyone
  24.         <Location "/sandbox">
  25.                 DAV svn
  26.                 SVNPath /var/lib/svn/sandbox
  27.         </Location>
  29.         # repositories for Group 1
  30.         <Location "/group1">
  31.                 DAV svn
  32.                 SVNParentPath /var/lib/svn/group1
  33.                 SVNListParentPath on  # Show an index of all repositories in /var/lib/svn/group1
  34.                 <LimitExcept GET PROPFIND OPTIONS REPORT>
  35.                         require ldap-group CN=Group 1,DC=example,DC=com
  36.                 </LimitExcept>
  37.         </location>
  39.         # repositories for Group 2
  40.         <Location "/group2">
  41.                 DAV svn
  42.                 SVNParentPath /var/lib/svn/group2
  43.                 SVNListParentPath on  # Show an index of all repositories in /var/lib/svn/group2
  44.                 <LimitExcept GET PROPFIND OPTIONS REPORT>
  45.                         require ldap-group CN=Group 2,DC=example,DC=com
  46.                 </LimitExcept>
  47.         </location>
  48. </VirtualHost>
The DAV svn directive tells Apache that Subversion will handle these requests. The SVNPath directive allows access to a single repository and SVNParentPath allows access to a directory full of repositories. By setting SVNListParentPath it will show all the repositories in the directory. Compared to getting Active Directory to work, this is all very easy.
I hope this article saves someone from the Active Directory nightmare I had. Happy (sub)versioning!


0 comentários: