07th September 2012, updated:24th March 2018

Setting up SVN on SSH

Normally, a subversion url looks as

https://server/etc

This means that subversion is being served using HTTP(s) protocol; an alternative is to use SVN + SSH, that is, to tunnel subversion on ssh. The main advantage of supporting HTTP is to avoid problems with firewalls, but it is slower and implies additional setup on the server machine. This setup is anyway easy, but only if Apache is already installed; if the server favors Nginx, this setup becomes suddenly a complication.

Having SSH + SVN does not imply, in fact, any additional work. Let's imagine we have a server account called coderazzi, and assume a SVN repository called projectx, located as /home/coderazzi/svn/projectx. To checkout this repository on any remote machine is enough to do:

svn co svn+ssh://coderazzi@coderazzi.com/home/coderazzi/svn/projectx . 

This checkout will succeed,a s far as the user enters correctly the password of the account. Matter of fact -and of inconvenience-, the user will have to enter twice the password.

What is happening on the server side? Not much: the svn client action triggers the svnserver process on the server -which is created just for this transaction-, using the passed credentials; the required files are then transferred back over ssh.

So, this setup works, but has two main problems:

  1. Being requested the password. Twice. This can be solved using ssh keys.
  2. Requiring the account's password. The user will likely have more services on the server, potentially sensible, and using its password for subversion purposes seems an ill idea

So, the solution passes by creating a separate account and setting up ssh keys. Any account is okay, but using svn seems rather appropriated. So, on the server side do first:

#install subversion, if required:

sudo apt-get install subversion

#setup specific user

sudo useradd svn
sudo mkdir /home/svn
sudo mkdir /home/svn/.ssh
sudo chown -R svn:svn /home/svn
 
#And request  a passwd:

sudo passwd svn

Now, create the directory /srv/svn and dump there the current repositories (or create them using svnadmin create):

sudo chown -R svn:svn /srv/svn
 
#Change permissions and umask to 2 to ensure correct permissions:

sudo chmod 2770 -R /srv/svn/ 

Now, instead of using the coderazzi user, we can use the more restricted svn user

svn co svn+ssh://svn@coderazzi.com/srv/svn/projectx . 

To avoid the password request madness, it is obviously possible to use ssh keys on the svn account. This short tutorial describes the whole proccess of setting up ssh keys: in short, it implies having a ~/.ssh directory in the svn user home folder (chmodded to 700), and inside a file called authorized_keys (chmodded to 600). To add a specific user, we obtain his public key, and add it to the authorized_keys file of the svn user:

cat coderazzi.key.pub > ~/.ssh/authorized_keys

On the client side, it implies having a ~/.ssh directory in the user home folder (chmodded to 700), and inside a file called id_rsa (chmodded to 600), containing the private key -note that this is the default expected configuration, but other configurations are indeed possible-. Once this is done, the user owning the coderazzi.key certificate (and its passphrase), will be able to access the subversion repository. In addition, this user does not need to know the svn's user password; however, this user can still do any normal ssh session on the svn account -which in most cases seems a bad idea-.

To avoid this, the autorized_keys file must include information to restrict the ssh operations: it can include an entry that defines the command to be executed when this key is used to start a ssh session. This file contain one entry for each public key, such as:

ssh-rsa AAAAB3....

To ensure that only SVN is provided, and, in addition, that only specific folders (/srv/svn) are supported, we update this entry to look like:

command="/usr/bin/svnserve -t -r /srv/svn/",no-port-forwarding,no-pty,
	           no-agent-forwarding,no-X11-forwarding ssh-rsa AAA...

(so we are setting additionally sound restrictions). Because of the entry -r /srv/svn, the user does not need any longer to enter the full path to the svn folder as in:

svn co svn+ssh://svn@coderazzi.com/srv/svn/projectx .

Instead, the user can enter now directly:

svn co svn+ssh://svn@coderazzi.com/projectx . 

Although this is now enough for the setup, we have assumed that the user owns a single ssh key. This can be a bad idea for security reasons, so let's assume that the user has a second key, to be used explicitly on SVN access. In this case is this second public key what is inserted into the server' authorized_keys file for the SVN user. The key itself can be saved on the .ssh folder of the user, and he must now explicitly require this user for SVN access. To do so:

export SVN_SSH="ssh -i /Users/coderazzi/.ssh/coderazzi.svn.key
One better alternative to this approach is to define a new entry in the ssh configuration file ~/.ssh/config
Host coderazzi.net
        IdentityFile ~/.ssh/svn_id_rsa
        User svn
        Hostname 51.254.207.228
        Port 22

Note that there can be more than one entry for host coderazzi.net, if different users are specified. BUT the first entry in the configuration file should be the default one

Repositories

The process to handle new repositories is done directly on the server, and it is therefore not impacted by how svn is served; for example, for a repository called website, available on the created /srv/svn location:

sudo svnadmin create /srv/svn/website
sudo chmod -R 770 /srv/svn/website

To import now content into the newly created repository, using ssh keys, on the client that currently holds that content:

svn import cdf svn+ssh://svn@coderazzi.net/website -m "initial one"

Which should then be immediately checked out to start working on the versioned folder:

svn co svn+ssh://svn@coderazzi.net/website [folder-name]

Alternatively, if using the conventional structure supporting branches, tags, the initial import would look like:

svn import cdf svn+ssh://svn@coderazzi.net/website/trunk -m "initial one"

And creation of tags, branches would be as easy as:

svn copy svn+ssh://svn@coderazzi.net/website/trunk \
	svn+ssh://svn@coderazzi.net/website/tags/v0.20 -m "v 0.20"

These usages of the repository from the client side are only valid if the ssh key is used. If, on the contrary, the user needs at any moment to access it without this key, just with credentials, the complete repository path would be required. That is, instead of:

svn co svn+ssh://svn@coderazzi.net/website

The user would do:

svn co svn+ssh://svn@coderazzi.net/srv/svn/website

Web browsing support

A problem with this approach is losing the ability to browse the subversion repositories in a web browser. The solution is setting up ViewVC

Installation is as simple as downloading and expanding it to a suitable location, such as /opt/viewvc. The only file absolutely requiring some configuration is viewvc.conf, to provide the SVN repository locations. In my case, I had to include:

root_parents = /srv/svn: svn

Although it is possible to run its standalone server, the obvious advantage is integrating it into the available nginx engine. However, providing the correct nginx configuration time took me longer than expected. This is my final ViewVC / nginx configuration:

server {
        listen 80;
        server_name svn.coderazzi.net;

        access_log /var/log/nginx/svn.access.log;
        error_log  /var/log/nginx/svn.error.log;

        root /opt/viewvc;

        location ~ ^/viewvc.cgi$ {
                auth_basic "SVN access login";
                auth_basic_user_file /home/coderazzi/www/coderazzi.net/cgi/.htpasswd;
                gzip off;
                fastcgi_pass  unix:/var/run/fcgiwrap.socket;
                fastcgi_param   DOCUMENT_ROOT           $document_root;
                fastcgi_param   SCRIPT_FILENAME         /opt/viewvc/viewvc.cgi;
        }
        location ~ ^/$ {
                rewrite .* http://svn.coderazzi.net/viewvc.cgi;
                break;
        }
        location ^~ /templates/ {
        }
        location ~ /(.+)$ {
                auth_basic "SVN access login";
                auth_basic_user_file /home/coderazzi/www/coderazzi.net/cgi/.htpasswd;
                gzip off;
                fastcgi_pass  unix:/var/run/fcgiwrap.socket;
                fastcgi_param   DOCUMENT_ROOT           $document_root;
                fastcgi_param   SCRIPT_FILENAME         /opt/viewvc/viewvc.cgi;
                fastcgi_param   PATH_INFO               $1;
                fastcgi_param   QUERY_STRING            $query_string;

        }
}

To customize it, it is needed to override the values for server_name, root and SCRIPT_FILENAME, plus, of course, your very own auth_basic_user_file

.