Friday, April 18, 2008

Securing Subversion via LDAP

This started out as an email reply to the Subversion users mailing list but I decided to post it here instead since it got a little wordy. I can't believe it's been a year since I posted anything, time flies when you're falling off of a mountain bike...


So, in our environment, we're using the LDAP interface to Active Directory. All of our users are known there and we have defined groups to represent various bits of the repository. I'm using mod_auth_ldap in front of SVN to do the integration.


$ grep svn.conf httpd.conf
include conf/svn.conf

$ cat svn.conf

<IfModule mod_dav_svn.c>
<IfModule util_ldap.c>
<IfModule mod_auth_ldap.c>

LDAPSharedCacheSize 200000
LDAPCacheEntries 1024
LDAPCacheTTL 600
LDAPOpCacheEntries 1024
LDAPOpCacheTTL 600

<Location /ldap-status>
SetHandler ldap-status
Order deny,allow
Deny from all
Allow from .myco.com 172.18.
</Location>

<Location /svn>
DAV svn
SVNParentPath /usr/local/svn/repositories
SVNListParentPath on
SVNAutoVersioning on
AuthName "Enterprise Shared SCM Repository"
AuthType Basic
Require valid-user
AuthLDAPUrl ldap://myLdapHost/DC=foo,DC=myco, \
DC=com?SAMAccountName?sub?(objectCategory=person)
AuthLDAPBindDN "CN=foobar,DC=foo,DC=myco,DC=com"
AuthLDAPBindPassword ...

</Location>

include conf/svn/Authorization.conf

</IfModule>
</IfModule>
</IfModule>

So that sets up the basic wiring and in Authorization.conf I break it down into Subversion specifics:

$ cat svn/Authorization.conf

include conf/svn/root.conf
include conf/svn/sandbox.conf
include conf/svn/admin.conf
include conf/svn/BusinessUnit1.conf
include conf/svn/BusinessUnit2.conf
...
include conf/svn/BusinessUnit3.conf

We've taken the route of one large repository shared by our multiple business units. There are pros and cons of this approach (as there are with the repository-per-project approach). For various reasons we felt that a single repository would be most appropriate.

Defining the per-whatever rules below was a bit of trial and error. I put together a set of typical use cases for various paths and who should and should not access them. By running through those cases and watching the logs I was able to sort out the Limit tags necessary to secure things the way I needed.

Since the repository is shared across business units, the first thing is to make sure that only the admin can create top-level directories. this is done in the root.conf file included above:

$ cat svn/root.conf

# Any attempt to modify the myRepo repository requires
# membership in the admin group.
<Location /svn/myRepo>
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
</Limit>
</Location>

# On many operations, Subversion does some behind the
# scenes work at .../!svn
<Location /svn/myRepo/!svn>
Require valid-user
</Location>

# Any paths below the root that are not otherwise secured
# only require a valid user for read/write access
<LocationMatch /svn/myRepo/.*>
Require valid-user
</LocationMatch>

In order to encourage people to use Subversion, I have setup a special "sandbox" are of the repository that is more or less a free-for-all. Given the above configuration, this config isn't entirely necessary but if we change the default policy (via the LocationMatch tag) from all-access to no-access our sandbox would break.

$ cat svn/sandbox.conf

<Location /svn/myRepo/sandbox>
Require valid-user
</Location>

Now I define business unit specific access. For each path we want to secure, the business unit tells me if they want the path to be read/write for all members of the group or if they want to have some people with read-only and others with read-write.

For instance, I keep the actual Subversion configuration in the repository. Because some of that may be "sensitive" (e.g. -- the LDAP bind password) I need to protect it.

$ cat svn/admin.conf

<Location /svn/myRepo/Admin/SVN>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
</Location>

Thus, any access (read/write/merge/...) to /svn/myRepo/Admin/SVN requires membership in the administration group.

In some cases, we may have a project where it is acceptable for anybody with a valid username/password to view the files but only members of a particular group can edit them:

$ cat svn/BusinessUnit1/someProject1.conf

<Location /svn/myRepo/BusinessUnit1/SharedSource>
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit1-SharedSource-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</Limit>
</Location>

The group that protects this branch is "CN=ACL-SVN-BusinessUnit1-SharedSource-RW,...". I've included the administration group also, however, so that the repository admins can help out if people get in a bind. This may or may not be something you want to do or you may have a different group (e.g. -- a help desk or support org) that would do this kind of thing:

<Location /svn/myRepo/BusinessUnit1/SharedSource>
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-Support,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit1-SharedSource-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</Limit>
</Location>

In any case, you can see how you can list multiple 'Require group' directives for the path. These are OR'd together BTW.

There may be other paths that I do not want to be world-readable. In the simple case this is identical to my /svn/myRepo/Admin/SVN example. In the more complex case, however, you may have one group of people who need read-only and another who need read-write access.

$ cat svn/BusinessUnit2/someProject1.conf

<Location /svn/myRepo/BusinessUnit2/someProject1>
<LimitExcept MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit2-someProject1-RO, \
OU=ACLs,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit2-someProject1-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</LimitExcept >
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit2-someProject1-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</Limit >
</Location>

By pairing the Limit and LimitExcept tags I can control who gets to make changes separately from who gets to view files.

One last thing I'll cover is protecting sub-paths within a parent path. Assuming that below /svn/myRepo/BusinessUnit2/someProject1 we have the traditional Subversion directories of "trunk", "tags" and "branches", it is very likely that your enterprise will want to secure the branches area to a specific group. In our case, we want to be sure that our branched code is only modifiable by a few experienced people who are allowed to make changes to the production candidate.

So, building on the /svn/myRepo/BusinessUnit2/someProject1 example, we further restrict the branches to this smaller group of people:

<Location ~ /svn/myRepo/BusinessUnit2/someProject1/[^/]+/branches>
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group CN=ACL-SVN-ADMIN,OU=SVN,DC=foo,DC=myco,DC=com
Require group CN=ACL-SVN-BusinessUnit2-someProject1-branches-RW, \
OU=ACLs,DC=foo,DC=myco,DC=com
</Limit >
</Location>

That's about all of the highlights I can think of to share. The most critical thing is to setup a test LDAP (I used OpenLDAP) and repository that is similar to your production environment. Then drill through as many usecases as you can think of to ensure that you're getting the behavior you expect. Once you're done, leave the test environment in place so that when somebody thinks something is broken you can go there and prove that it is user error :-)

My future goal is to define a simple XML format such that business unit (or project) owners can define their authorization requirements and submit them to an agreed-upon repository path. I would then have code to transform that XML into the Apache rules and drop them into place. In other words, I really don't intend to create and maintain all of the Apache configs by hand as we roll this out to dozens of projects.

11 comments:

Anonymous said...

Excellent. This is exactly the access control information I need to get our subversion repository configured correctly and dump the file authentication method completely.

Anonymous said...

What abt svnserve to auth againts LDAP?

James CE Johnson said...

The way I did it above leverages Apache's mod_ldap without subversion being aware of the details. From http://svnbook.red-bean.com/en/1.0/ch06.html I get the impression that Apache integration is the only way to do LDAP.

Anonymous said...

Thanks for the great help.

I struggled for a long time only because using the collab.net version of the packaged SVN server the directive for the authldap module was Require ldap-group and not Require group (witch seem to be used for classic user/group Auth only).
I do not know if it is an option of the module configuration or a recent change in the module directives syntax.
I thought it was worth mentioning just in case.

We settled on a multiple repository structure so I had to remove most of the "root.conf" meat and wondered if the !svn url was something I need to thus replicate on each individual repositories under the root repository path.
For now I did not see the use for it in the logs ...

James CE Johnson said...

I think you will probably need the /!svn rules at some point because of the way Subversion handles many things internally. It's a bit of a bear to google for but /!svn/ver turns up a reference to webdav-protocol which explains it a bit. I also dug up ./subversion/mod_dav_svn/dav_svn.h on my gentoo box and the comments there (search for SPECIAL URI in dav_svn.h). The gist of it is that the /!svn paths are used to get to pseudo-resources such as working resources, activities, version resources and version history resources. It goes on to say that the !svn bit is configurable on a per-server basis and simply defaults to the !svn value that I've seen. If you're using a pre-built binary it could be that it isn't using the default or that the logging has been muted in some way so that you don't see those internal URLs.

Anonymous said...

Does this mean you cant do LDAP without Apache??? I want a situation where I can just configure ldap auth on my svn server without apache.

Unknown said...

@Benny: You can, its just not as easy. For svnserve you have to use SASL to get LDAP. Google "svnserve sasl ldap" to get an idea. First get sasl itself working with ldap. Edit /etc/saslauthd.conf, put in all the ldap_ parameters (many examples out there). Launch the sasl deamon in debug: saslauthd -c -m /var/run/saslauthd -d -a ldap
then test against it with testsaslauthd. Once you can get Ok from that, setup svn.conf (subversion.conf?) in the sasl lib dir (usually /usr/lib/sasl2), should have one line:
pwcheck_method saslauthd
From there, it treats the user as any other for the purposes of the authz file. (at least, thats how its supposed to work. I havent fully finished out my setup quite yet, so YMMV).

Anonymous said...

I am at a company that has the same setup. I am able to authenicate with no problems; however, when I try to restrict based on the Limit tag and using SVNParentPath, I can't get access to the SVN server at all.

How do you have your repos file structure setup to use SVNParentPath? I have to use SVNPath for anything to work. I also need to lock down top level paths for example.

/svn/ATTIC
/svn/Development
/svn/Docs
/svn/Project 1
/svn/Project 2

each one of these directories have their own trunk, etc, if looking through the repo-browser. Any help would greatly be appreciated.

James CE Johnson said...

I don't reference SVNParentPath in any of my authorization configuration. What I have in the <Location> tags are paths within the repository.

For instance, <Location /svn/MyRepository>. And "/svn" is the location that is mapped onto "DAV svn".

To secure the root such that not just anyone can create top-level directories, I do this:

<Location /svn/MyRepository>
<Limit MERGE MKCOL POST PUT DELETE PATCH PROPPATCH>
Require group someGroup
</Limit>
</Location>

<Location /svn/MyRepository/!svn>
Require valid-user
</Location>

Unknown said...

Hi All,

Just a quick question : Does SVN Manager supports LDAP based user/group authentication? Thanks for your quick response.

Regards
Kumar

James CE Johnson said...

I'm sorry, I don't know anything about SVN Manager.