14 May 2011

A secure, consistent SVN mirror

Using svnsync, ssh+svn, and post-commit hooks

Many svn mirror implementations take the hammer approach by running a cron job every minute to poll for changes. Bang, bang, bang and any change is pushed to a mirror via svnsync. But do you really want cron pounding away at subversion all the time and pulling changes out like a rusty nail?

Hammers and mirrors do not work well together.

The more elegant approach is to set up post-commit hooks. These capture every change as it happens. Unfortunately getting this to work is frustrating. Many administrators can’t get the post-commit to do anything at all; the post-commit “never runs” with no hint of failure and no logged errors.

Post-commit configuration usually fails due to permission problems. Each ssh+svn user comes in as themselves when a commit is being made, but that user often does not have permissions to a) run the post-commit hook and/or b) run commands in the post-commit script.

So here is how to both set up the mirror and set up the permissions so that post-commit can run.
This installation assumes CentOS/Redhat/AWS platforms and an already working svn repository where users access via ssh+svn.

Set up the svn mirror (aka target)

On the target system, create a user that will have exclusive write access to the mirror, traditionally this username is svnsync.

useradd svnsync

As the svnsync user, replicate the file system location of the original subversion repository. Then create the blank repository.

svnadmin create --fs-type=fsfs /repos/svn/myrepos

Normally when creating an SSH subversion repository, it is necessary to adjust the umask to 002 and create a group that will have write access. But in this case, only this user will have write privileges, so there is no need to create a subversion group, nor to adjust umask.

Then, if not already done, generate the ssh keys needed for the svnsync user to access the blank repository via SSH. For added security, add the following string to lock down the public key:

On the source system, add the svnsync user and the private SSH key. This user will read from the source and push changes to the target.

If a bit paranoid, a secondary check can be put on the target repository which ensures only the svnsync user can make a change in the form of a pre-revision change hook. This will prevent a non-SSH user from accidentally making commits.

cp -p pre-revprop-change.tmpl pre-revprop-change

Adjust the script to ensure only the svnsync user can effect a revision** change:

Populate the mirror

Now the mirror is ready to be initialized and synchronized (a two step process). On the source system first initialize the mirror, which locks the target to the source repository. The init flag uses the <destination> <source> format:

If this fails with the error “svnsync: Session is rooted”, double-check the SSH key. It likely has the repository path in the key: -r /repos/svn/myrepos. Remove this to generate a successful initialization.

The target is now locked to the source. Time to push the repository data. On the source svn server, synchronize the data:

Automate the mirror

Here is where the magic comes in. When a user makes a commit to the source repository via SSH, that process has the permissions of that user, not as root or some svn service. Unfortunately, the mirror only accepts writes from the svnsync user, not from everyone. This is where sudo comes in to solve the problem.

1. Set up SUDO privileges

On the source server, for SSH+SVN access, every user is already a member of a OS group that has read-write access to the repository. These group members must be given the ability to a) become the synsync user and b) run a single command. Fire up visudo and add this line:

%svngroup ALL=(svnsync) NOPASSWD: /usr/bin/svnsync

This allows all members of the svngroup (%svngroup) the ability to switch to the svnsync user (ALL=(svnsync)) without a password prompt (NOPASSWD) and run a single command (/usr/bin/svnsync).

Of course, users won’t be doing this manually. They won’t even know they have this “privilege”. If the SSH keys are locked down properly, users can’t log onto the subversion server, much less execute sudo commands remotely.

Instead, what this sudo change does is give the post-commit hook scripts the ability to push any changes to the mirror as the svnsync user.

2. Set up the post-commit scripts

Post-commit scripting is well documented, but without the sudo setup and sudo commands in the script, the post-commit scripts will not execute in a SSH+SVN environment.

On the source server, in the hooks folder, copy post-commit.tmpl and post-revprop-change.tmpl scripts: