How to create a svn repository

Subversion is well on its way to becoming the new hot thing in open source version control. While I had CVS mostly figured out, subversion presents some new problems as far as managing access, so this document intends to explore the differences between subversion and CVS from the viewpoint of the administrator and attempt to find solutions to the new problems of access control.

Overview

Subversion, like CVS, can be tunneled over ssh, but, also like CVS, attempts to mete out authentication instead through its own programs. Unlike CVS's pserver, subversion provides a certain level of extensibility by riding on top of the Apache HTTP server. However, I have no interest in putting every service into the web server, and there should be no reason to mandate the presence of a web server on the computer housing the subversion repository. This article will use methods similar to the CVS article, allowing access to the repository through the use of restricted ssh user accounts.

The structure of the repository is an important difference as far as security, since, unlike CVS, the files in the subversion filesystem exist not as files on the system filesystem, but instead contained in the subversion database. This means that read and write access cannot be controlled through UNIX filesystem permissions except at the top-level of the subversion filesystem. Unlike CVS, in which there is usually a single repository with multiple projects, each project in subversion will be its own repository. Related projects in subversion can be combined into a single repository, but they will share access controls as well as configuration.

The chroot environment

The support programs necessary for the restricted environment are similar to those used in the CVS article, and a single environment could be used for both systems, so I will be glossing over the details of how to set this up.

/svn/bin

Since there are two subversion programs useful for the chrooted environment—svnserve and svnlook—and since they both link to so many libraries, I didn't bother with statically-linked programs, instead just copying the dynamically-linked binaries from the system along with their libraries.

The programs I include in /bin are chrootsh (version 0.4 of chrootutils supports multiple commands, so it can be used in a combined svn/cvs environment), svnacl (more on setting that up later), svnserve, svnlook, ldconfig and sh, for the hook scripts.

/svn/etc

/etc/ld.so.conf should exist for ldconfig, but it doesn't need to contain anything.

/etc/nsswitch.conf, to direct the name service functions to the etc and passwd files, should contain the following:

passwd: files
group: files

/etc/passwd and /etc/group should contain entries for any users and groups that may access the subversion filesystems, as well as root.

If you plan to use a global svnacl.conf to write access within the subversion filesystems, it can be added as /etc/svnacl.conf. I use the example.conf file distributed with svnacl.

/svn/lib

The following shell script works for copying the libraries under Linux, assuming the libraries are in /usr/lib:

for file in /svn/bin/* ; do
   for lib in $(ldd $file | sed -n 's/.*=> \([^(]*\) (.*/\1/p') ; do
      if [ ! -z "$lib" ]; then cp -a "$lib" /svn/lib/ ; fi
      link="$lib"
      while [ -L "$link" ]; do
         newlink="$(readlink "$link")"
         if echo $newlink | grep -q '^/' ; then
            cp -a "$newlink" /svn/lib/
         else
            cp -a "$(dirname "$link")/$newlink" /svn/lib/
         fi
         link="$newlink"
      done
   done
   # copy the linker
   linker=$(ldd "$file" | grep '/lib/ld' | sed 's/^[[:space:]]*\([^[:space:]]*\).*/\1/')
   if [ ! -z "$linker" ]; then cp -a "$linker" /svn/lib/ ; fi
   link="$linker"
   while [ -L "$link" ]; do
      newlink="$(readlink "$link")"
      if echo $newlink | grep -q '^/' ; then
         cp -a "$newlink" /svn/lib/
      else
         cp -a "$(dirname "$link")/$newlink" /svn/lib/
      fi
      link="$newlink"
   done
done

Don't forget that for systems like Linux, extra library files are needed for the name service lookup functions. You'll want to copy over at least /lib/libnss_files*

Everything else

/tmp needs to exist with the usual permissions, 1777. /dev/null needs to exist.

Unlike the filesystem described in the CVS article, I did not create user home directories for this project, since I haven't been able to get that to work since I wrote it. Instead, I've been using ssh host aliases to disable X11 forwarding for subversion users. For example, you could create an alias “svnhost” that uses the hostname of your subversion server, your subversion user and sets ForwardX11 to “no”.

The repository

Subversion, since the repository exists as a database of transactions instead of a collection of individual files, has two filesystems for which access must be considered: the system's filesystem on which the repository files reside, and the subversion filesystem within subversion's database. Although it is not possible to control read permissions within the subversion filesystem, the pre-commit hooks provide a mechanism for controlling what operations may be performed on the subversion filesystem and by whom. Also, it is possible through the manipulation of the system's filesystem permissions to control which users have read or write access to the repository as a whole. The fsfs database format is the most flexible for this purpose, since, unlike the bdb backend, it does not require that read-only users be able to write locks.

Creating the repository

First, remember that each repository in subversion is akin to a project in CVS, so you will be creating one repository per project.

The subversion repository is created through the svnadmin command like so:

svnadmin --fs-type fsfs /svn/svnroot/project

In this example, I am using svnroot under the svn chroot environment as the root directory for all subversion repositories. At this point the root of the subversion filesystem can be accessed as “svn+ssh://svnuser@hostname/svnroot/project”.

Permissions

The newly created subversion repository should look contain several files and directories. The README.txt file provides and explanation and warning for the repository, the conf directory contains a configuration file for use with subversion's equivalent of pserver, the dav directory is used by the WebDAV interface, and the format file marks the repository as fsfs. We can ignore all of these; just make sure that the format file is globally readable and writable by no one.

Directories of interest are db, which contains the actual repository files; hooks, which contains scripts to be run before and after commits, and locks, which holds filesystem lock files similar to those used by CVS. The db directory contains several more points of interest: revprops, containing revision properties such as author and commit log information; revs, containing the committed revisions; transactions, which acts as a staging area for commits; write-lock, serving as a global lock file to the repository, and current, which contains information on the last revision. The current file is not written directly; instead, a file “current.tmp” is created and moved into place over current. Thus, though “current” needs only read permissions, the directory itself must be writable by the users with write access to the repository.

In summary, the db, db/transactions, db/revs, db/revprops, db/write-lock and locks directories should be writable by committing users. The contents of db, db/revs, db/revprops and locks need to be readable by the read-only users. The contents of hooks need to be readable by the committing users, since the scripts contained there will be executed by these users, and writable only by the administrator. This structure can be accomplished through access control lists as follows:

chgrp svn-rw db db/transactions db/write-lock db/revs db/revprops hooks locks
chmod 2770 db db/transactions db/revs db/revprops
chmod 660 db/write-lock
chmod 750 hooks
chmod 770 locks
setfacl -m "group:svn-ro:r-x" db db/revs db/revprops locks

Adding initial directories

You probably want to add your initial repository structure before adding hooks to forbid it. Since both branches and tags in subversion are accomplished through copying a directory tree from one location to another, the most common layout is to have a directory for trunk, a directory for branches, a directory for tags, and possibly another for vendor branches. These can be added either by using svn mkdir with URL arguments, or, as I prefer, in one commit by creating empty directories and importing them.

cd svntmp
mkdir branches tags trunk vendor
svn import -m 'Initial directory structure' svn+ssh://svnuser@svnhost/svnroot/project"

Hooks

Specific write permissions can be enforced within the subversion filesystem through the use of hooks. Subversion includes several examples of hooks and their use, but I prefer the one that I wrote: svnacl. The svnacl documentation contains more information on how to use it and includes a config file that enforces a CVS-like semantics: tags are read-only and branches don't change the root of the project. Any hook scripts you decide to install must be executable by any users with permission to commit.

That's it! You should be ready now to use your new subversion repositories in more-or-less the same ways that you used CVS.