Apache and Subversion authentication with Microsoft Active Directory

by Sander Marechal

Last updated on 2007-12-03@23:38. 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:

$ ls -al /etc/apache2/mods-enabled
alias.load -> ../mods-available/alias.load
auth_basic.load -> ../mods-available/auth_basic.load
authnz_ldap.load -> /etc/apache2/mods-available/authnz_ldap.load
authz_default.load -> ../mods-available/authz_default.load
authz_user.load -> ../mods-available/authz_user.load
dav.load -> ../mods-available/dav.load
dav_svn.conf -> ../mods-available/dav_svn.conf
dav_svn.load -> ../mods-available/dav_svn.load
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/.

NameVirtualHost *
<VirtualHost *>
        DocumentRoot /var/lib/svn
        ServerName svn.example.com

        ErrorLog /var/log/apache2/error.log
        LogLevel warn
        CustomLog /var/log/apache2/access.log combined
        ServerSignature On

        <Directory "/var/lib/svn">
                Options Indexes FollowSymLinks MultiViews
                Order allow,deny
                allow from all
        </Directory>
</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:

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" (username@example.com) 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.

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. 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:

<Location "/">
        AuthBasicProvider ldap
        AuthType Basic
        AuthzLDAPAuthoritative off
        AuthName "My Subversion server"
        AuthLDAPURL "ldap://directory.example.com:389/DC=example,DC=com?sAMAccountName?sub?(objectClass=*)" NONE
        AuthLDAPBindDN "CN=apache,CN=Users,DC=example,DC=com"
        AuthLDAPBindPassword hackme

        require valid-user
</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:

"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 example.com) 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. The scope parameter tells LDAP how deep to search beneath the BaseDN. Do yourself a favour and leave it on "sub" (all the way). The filter determines what kind of objects should be returned. In my example I play safe again and say "all objects".

Officially the base, attribute, scope 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 word NONE. 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).

NONE
stablish an unsecure connection on the default LDAP port. This is the same as ldap:// on port 389.
SSL
Establish a secure connection on the default secure LDAP port. This is the same as ldaps://
TLS/STARTTLS
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:

AuthLDAPBindDN "apache@example.com"

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 the AuthLDAPURL. Here's what a bind failure looks like:

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:

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 distinguishedName field 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 <Directory> block and the DocumentRoot 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.

$ cd /var/lib/svn
$ mkdir group1
$ mkdir group2
$ svnadmin create /var/lib/svn/sandbox
$ svnadmin create /var/lib/svn/group1/g1-repository
$ svnadmin create /var/lib/svn/group1/g2-repository

Now you need to create some <Location> 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 <LimitExcept> I only protect writing to a repository. Everyone can read all repositories. Here is what the full configuration looks like in the end:

NameVirtualHost *
<VirtualHost *>
        DocumentRoot /var/lib/svn/htdocs
        ServerName svn.example.com

        ErrorLog /var/log/apache2/error.log
        LogLevel warn
        CustomLog /var/log/apache2/access.log combined
        ServerSignature On

        <Location "/">
                AuthBasicProvider ldap
                AuthType Basic
                AuthzLDAPAuthoritative off
                AuthName "My Subversion server"
                AuthLDAPURL "ldap://directory.example.com:389/DC=example,DC=com?sAMAccountName?sub?(objectClass=*)" NONE
                AuthLDAPBindDN "CN=apache,CN=Users,DC=example,DC=com"
                AuthLDAPBindPassword hackme

                require valid-user
        </Location>

        # The sandbox repository can be written to by anyone
        <Location "/sandbox">
                DAV svn
                SVNPath /var/lib/svn/sandbox
        </Location>

        # repositories for Group 1
        <Location "/group1">
                DAV svn
                SVNParentPath /var/lib/svn/group1
                SVNListParentPath on  # Show an index of all repositories in /var/lib/svn/group1
                <LimitExcept GET PROPFIND OPTIONS REPORT>
                        require ldap-group CN=Group 1,DC=example,DC=com
                </LimitExcept>
        </location>

        # repositories for Group 2
        <Location "/group2">
                DAV svn
                SVNParentPath /var/lib/svn/group2
                SVNListParentPath on  # Show an index of all repositories in /var/lib/svn/group2
                <LimitExcept GET PROPFIND OPTIONS REPORT>
                        require ldap-group CN=Group 2,DC=example,DC=com
                </LimitExcept>
        </location>
</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!

References

Creative Commons Attribution-ShareAlike

Comments

#1 H B

Excellent article. I found a number of articles on Active directory Apache configuration but none was so crisp. The best part is that configuration seems to have been taken from a working prototype.

#2 Sander Marechal (http://www.jejik.com)

From Steffen Sobieg via e-mail

Hi Sander!

I just read your great article about Apache authentication
against Active Directory.

When using your instructions, I have a slight problem to which
I found no solution so far:

When entering the username in the form "DOMAIN\User", the
authentication doesn't work. It works, however, when only
"User" is entered.
In the company where I work, the people tend to use the former
format (maybe because IE pre-fills the authentication dialog
like that).

Did you encounter this problem, too? Do you have a solution to
this problem?

Kind Regards

Steffen Sobiech


I don't have an immediate solution. I suggest that you use the LDAP
browser Java applet that I pointed to in the article, connect to your
Active Directory server and lookup a user. In the left column there
should be field names like sAMAccountName, userPrincipalName, etcetera.
Whatever fieldname you put in the AuthLDAPURL directive in Apache is
what will be matched against the username entered. Just scroll through
the list of fields inside a user and see if there is a field with the
value of DOMAIN\User. If there is, put that fieldname in the AuthLDAPURL
(instead of sAMAccountName).

#3 Mark van Sintfiet (http://www.markvansintfiet.nl)

After reading this perfect article I was be able to setup a subversion server with Apache/AD authentication.

The only thing that doesn't work is the "require ldap-group" for setting up authentication based on AD groups.

I get this error in my apache2\error.log:
[error] [client 192.168.0.73] access to /P0001 failed, reason: require directives present and no Authoritative handler.

If I replace the "require ldap-group" with "require valid-user" in the same <location>...</location> everything works fine.

I'm using Debian Etch and Windows 2003 SBS!

#4 Sander Marechal (http://www.jejik.com)

Hi Mark van Sintfiet.

I have had someone ask me the same question via e-mail. I don't have an exact solution. It's a bit of fiddling to get it *just* right. You could try setting AuthzLDAPAuthoritative to "on" but when you do that, your "require valid-user" will fail, although ldap-group will work. I solved it for my case after hours and hours of trying hundreds of different configurations. That's why my article started with a rant :-)

I have asked the person that e-mailed me if I can post our full conversation here and if he will send me the changes he made to get it working for both ldap-group and valid-user. Hopefully that will help you too. I suggest you check back here in a few days.

PS: When I try to load your site in FireFox I get an XML parsing error.

#5 Mark van Sintfiet (http://www.markvansintfiet.nl/)

After hours of trying different things the solution for my problem was quite simple. I didn't specify the location of my group in the AD.

Changing:
require ldap-group CN=subversion,DC=example,DC=com

to:
require ldap-group CN=subversion,CN=Users,DC=example,DC=com

solved most of my problems.

So I had to specify exactly where my group was in the AD. After this I made an OU for Subversion in my Active Directory to put all the groups related to Apache/Subverion. To use a group in an OU it looks like this:
require ldap-group CN=P0001,OU=Subversion,DC=rltsft1,DC=local

To know what to use for sure you can use the JAVA ldap browser in this article to lookup your group and look for the property: "distinguishedName", the value of this property is what you have to use as the "require ldap-group" value.

Last but not least, there is a very strange thing. I have to restart Apache after adding users to my group in the AD( on the Windows 2003 SBS server). I find it strange, maybe it will work over time if you don't restart Apache, I'm not patient enough for that. I know for sure, restarting Apache does the job.

Everything works great for me now, I also use Trac with the same
authentication.

Does someone have experience with adding SSL(https) to this configuration? With an self-created not-trusted certificate, I don't wanna pay for it, I just want my information to be send encrypted.

#6 Sander Marechal (http://www.jejik.com)

Hi Mark,

Thanks for figuring out how to solve the ldap-group problem. I don't use SSL at work so I haven't tried it myself, but from looking at a couple of online resources such as this one (for Debian) or this one (for Ubuntu) it doesn't look hard. Just

  • Load the SSL module (a2enmod ssl)
  • Add "Listen 443" to /etc/apache2/ports.conf
  • Create a self-signed SSL certificate (apache2-ssl-certificate)
  • Change the configration from the article from:
    NameVirtualHost *
    <VirtualHost *>

    To:
    NameVirtualHost *:443
    <VirtualHost *:443>
            SSLEngine on
            SSLCertificateFile /etc/apache2/ssl/apache.pem
            SSLProtocol all
            SSLCipherSuite HIGH:MEDIUM
  • Restart Apache

#7 Anonymous Coward (http://opensourcedevelopment.net)

Hi All,
You can download the complete package from http://opensourcedevelopment.net/text-tutorials/apache-subversion-active-directory.html
this is working copy of complete package.
Regards

#8 Federico Castagnini (http://castagnini.com.ar)

Hi Mark van Sintfiet

the problem is that you don't include the following lines in every "<Location" sections:


AuthBasicProvider ldap
AuthType Basic
AuthzLDAPAuthoritative off
AuthName "My Subversion server"
AuthLDAPURL "ldap://directory.example.com:389/DC=example,DC=com?sAMAccountName?sub?(objectClass=*)" NONE
AuthLDAPBindDN "CN=apache,CN=Users,DC=example,DC=com"
AuthLDAPBindPassword hackme

require valid-user


i recommend to write that section on a separate file an include in every <Location section.

Best regards!
Fede

#9 Sander Marechal (http://www.jejik.com)

Matthew Perry (via e-mail) in response to the ldap-group issue:
Hey Sander,

Sorry it has taken me so long to respond, I have been very busy at work. The issue that I was having was in our test environment we were setting the users up to have group access by their primary group. For example lets say to access group1 repo, I had setup user1 to have group1 as its primary group. Since the user primary group doesn't show up in AD the search would say user1 doesn't exist in group1. Once I switched users back to the primary group of Domain Users everything worked as expected. Hope that helps and makes sense.

- Matthew

#10 VeZouL (http://vezoul.blogspot.com)

I'm not sure, but that worked for me during the conf of trac/ldap authentication :
Mark van Sintfiet : try change ldap-group to group

#11 sBox

I have LDAPS going at the moment, although I have yet to fix the ldap-groups portion. I've played around with the LDAPS cert verification too long--I gave up and added the following to my apache2.conf:

LDAPVerifyServerCert off
LDAPTrustedMode SSL

This forces SSL and prevents verification of the certificate. Change your LDAP URL to use LDAPS:

ldaps://dc01.mydomain.local:636/ou=User Accounts,dc=mydomain,dc=local?sAMAccountName?sub?(objectClass=*)

I can go into the various things I've tried with getting the verification to work if anyone cares to know.

#12 HarlequinSmurf

How do you handle segregating permission into different sections of the subversion repositories when you are using ldap for auth. eg: when you have a largish team of developers and only the team leaders are permitted to merge into the release branch.

Typically when using the old password file setup you can configure different access restrictions inside the repository using the AuthzSVNAccessFile parameter in your apache config or your .htaccess file. The format of that file is


[repository:/path/into/repository]
user = permission
@group = permission
* = permission


groups are defined at the top of the file in the format

groupname = user1, user2, user3


permissions area r = read and w = write and the * in the list means anyone not yet explicitly given permissions.

I ask as so far i've not come across any decent suggestions on how to do this and wonder if you have set something up for this type of control.

#13 Sander Marechal (http://www.jejik.com)

HarlequinSmurf: You can put the team leaders in a separate group and then only give write/commit permissions to the members of that group, like you would do with snvserve. It doesn't look like AuthzSVN can deal with ldap-groups, so you need to create those groups inside the AuthzSVNAccessFile from the ldap-user names the old fashioned way. If there is a better way, IU don;t know about it and the SVN book over at red-bean doesn't mention it.

Comments have been retired for this article.