Wednesday, April 2, 2014

Setting up a Secure Subversion Server

Setting up a Secure Subversion Server

Backing up your scripts.

# tar czvf - /usr/local/etc/www/data | ssh dru@ "cat > www.tar.gz"

Preparing the System

In my scenario, it was important that only the members of the development team have access to the repository. We also chose to have the repository on a system separate from the actual web server and left it up to the web administrator to copy over files from the repository to the web server as he saw fit.

To accomplish this, start by creating a backup of the existing directory structure you wish to put under revision control, and send it securely to the repository server. In my case, I backed up the www data on the web server to an internal server at

Add a user called svn.
# pw useradd -n svn -s /bin/tcsh -w yes -d /home/svn -c "svn user" -m
# passwd svn

Next, on the repository system, create a new group called svn and add to it any existing user accounts that need access to the repository. For example, I added my existing web administrator as I created the group by running following command:

# pw groupmod svn -m webadmin

Then, create a new user called svn and, if necessary, any missing user accounts that need access to the repository. Make sure each account is a member of the svn group and has a password and a valid shell. I used sysinstall to create user accounts for the new web developers. When I finished, I double-checked the membership of the svn group. It looked something like this:

# grep svn /etc/group

Dealing with umask

Before installing Subversion, take a close look at the existing umask for the svn user. On my FreeBSD system it was:

# su - svn
% umask

In Unix, the umask value determines the default permissions of a newly created directory or file. It does this by defining which permissions to disable. If you remember:

r = 4
w = 2
x = 1

you'll see that this umask doesn't turn off any (0) permissions for the user (svn); it turns off write (2) for the group (svn); and it turns off write (2) for world.

Because the members of the svn group should be able to write to the repository, change that group 2 to a 0. If you don't want nongroup members even to be aware of the existence of the repository, also change the world 2 to a 7.

The easy part is changing the umask for the svn user's shell. If it uses csh:

# su - svn
svn # vi ~svn/.cshrc
# A righteous umask
umask 027

Note: the meaning of each umask:
umask 002 // File permission 644. Owner can read/write. Group and Others can only read.
umask 007 // File permission 660. Owner and Group can read/write. Others can not read or write.
umask 027 // File permission 640. Owner can read/write. Group can read. Others can not read or write.

Note: I personally prefer to set umask 027. There is a security reason behind the thought. In order to prevent bad scripts trying to create new scripts or modify existing scripts on your server, you can have "svn update" running automatically in crontab. That will take care of source code update part. Then, you would want to make "svn" user to be the only user that has the write permission. www group users will only have read permission.

then find the existing umask line and change it to either 002, 007 or 027.

If your svn user has a shell other than csh, make your edit in your chosen shell's configuration file.

Once you've saved your changes to ~svn/.cshrc (or wherever), don't forget to tell the shell:

svn # source ~svn/.cshrc

Repeat the umask command to verify that your changes have taken place.
Installing Subversion with the correct umask

If you chose a umask of 002, you can compile a wrapper into Subversion when you build it from the ports collection. If you chose a umask of 007 or 027, or prefer to install the precompiled version of Subversion, create a wrapper script to ensure that the Subversion binaries use your umask value.

To compile in a wrapper that sets a umask of 007 or 027:

# cd /usr/ports/devel/subversion
# make config-recursive
# make install clean

To compile in a wrapper that sets a umask of 002:
# cd /usr/ports/devel/subversion
# make -DWITH_SVNSERVE_WRAPPER install clean

Note: you do NOT need -DWITH_SVNSERVE_WRAPPER option if you decided to use umask of 007 or 027.

Make sure you DO uncheck this option:
[] BDB=off "db4 repository backend"

Because some poeple said:

- I never used a BDB repos myself, exactly because the Subversion manual warns strongly against it. So second this. – jfs Sep 23 at 8:30

- I also second this - we used to have Subversion BDB repository problems. Switching to the FSFS repository type helped. – Phill Sacre Sep 23 at 8:38

Alternatively, to install the precompiled binary:

# pkg_add -r subversion

Note: before installing by either method, finish reading the article. You may find some additional compile options that interest you.

If you didn't compile in your wrapper (that means you use a umask of 007 or 027), move your existing binary and create your own wrapper script:

# mv /usr/local/bin/svn /usr/local/bin/svn.orig

# vi /usr/local/bin/svn

### initialize

### use encoding utf-8 as default if run "svn ci" or "svn commit".
if [ "$1" != "help" ]; then
  for myarg in "$@"; do
    if [ "${myarg}" = "commit" ] || [ "${myarg}" = "ci" ]; then
      svnarg="--encoding utf-8"

### wrapper script to set umask to 027 on subversion binaries
### Note: the meaning of each umask:
### umask 002 // File permission 644. Owner can read/write. Group and Others can only read.
### umask 007 // File permission 660. Owner and Group can read/write. Others can not read or write.
### umask 027 // File permission 640. Owner can read/write. Group can read. Others can not read or write.
umask 027

### svn command
/usr/local/bin/svn.orig ${svnarg} "$@"

Set your umask to either 002, 007 or 027 so that it is the same as the umask for your svn user.

Don't forget to make your wrapper script executable:

# chmod +x /usr/local/bin/svn

Repeat the same steps for proj2 files.

Creating the Repository
Create a central place to store all repositories.
# mkdir /usr/local/repositories

# chown svn:svn /usr/local/repositories

Now that your environment is set up properly, you're ready to create the repository itself.

Log in as the user svn to ensure that both the svn user and the svn group own the files you create in the repository.
# su - svn

svn # cd /usr/local/repositories
svn # svnadmin create proj1
svn # svnadmin create proj2

In this example, I've called my repository "proj1" and "proj2" two separate repositories. You can choose any name that is useful to you.

svnadmin create simply creates the directory infrastructure required by the Subversion tools:

svn # ls -F proj1 proj2
README.txt conf/ db/ format hooks/ locks/

README.txt conf/ db/ format hooks/ locks/ db/ hooks/

Notice that db directory? By default, Subversion uses databases to track changes to the files that you place under revision control. This means that you must import your data into those databases.

Edit svnserve.conf & passwd file:
svn # vi /usr/local/repositories/proj1/conf/svnserve.conf
anon-access = none
auth-access = write
password-db = passwd

svn # vi /usr/local/repositories/proj1/conf/passwd
danny = mypassword

Start SVN server as a stand-alone daemon
# /usr/local/bin/svnserve -d --listen-port=3690 --listen-host= -r /usr/local/repositories

Preparing Files to be imported
At that point, I untarred my backup so that I had some data to import. If you do this, don't restore directly into the /usr/local/repositories/proj1 directory. (It's a database, remember?) Instead, I first made a new directory structure:
# mkdir /usr/local/www/apache22/data/proj1
# cd /usr/local/www/apache22/data/proj1
# mkdir branches tags trunk
# cd trunk
# tar xzvf /full/path/to/www.tar.gz .

Importing the Data
Next, it's time to import the information from /usr/local/www/apache22/data/proj1 into the Subversion databases. To do so, use the svn import command:
# su - svn
svn # cd /usr/local/www/apache22/data
svn # svn import proj1 file:///usr/local/repositories/proj1 -m "initial import"
svn # svn import proj2 file:///usr/local/repositories/proj2 -m "initial import"

svn import is one of many svn commands available to users. Type svn help to see the names of all the available commands. If you insert one of those commands between svn and help, as in svn import help, you'll receive help on the syntax for that specified command.

After svn import, specify the name of the directory containing the data to import (proj1 or proj2). Your data doesn't have to be in the same directory; simply specify the full path to the data, but ensure that your svn user has permission to access the data you wish to import. Note: once you've successfully imported your data, you don't have to keep an original copy on disk. In my case, I issued the command rm -Rf www.

Next, notice the syntax I used when specifying the full path to the repository. Subversion supports multiple URL schemas or "repository access" RA modules. Verify which schemas your svn supports with:

# svn --version
svn, version 1.1.3 (r12730)
compiled Mar 20 2005, 11:04:16

Copyright (C) 2000-2004 CollabNet.
Subversion is open source software, see
This product includes software developed by CollabNet (http://www.Collab.Net/).

The following repository access (RA) modules are available:

* ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
- handles 'http' schema
- handles 'https' schema
* ra_local : Module for accessing a repository on local disk.
- handles 'file' schema
* ra_svn : Module for accessing a repository using the svn network protocol.
- handles svn schema

Because I wished to access the repository on the local disk, I used the file:/// schema. I also appended www at the very end of the URL, as I wish that particular part of the repository to be available by that name. Yes, you can import multiple directory structures into the same Subversion repository, so give each one a name that is easy for you and your users to remember.

Finally, I used the -m message switch to append the comment "initial import" to the repository log. If I hadn't included this switch, svn would have opened the log for me in the user's default editor (vi) and asked me to add a comment before continuing.

This is a very important point. The whole reason to install a revision control system is to allow multiple users to modify files, possibly even simultaneously. It's up to each user to log clearly which changes they made to which files. It's your job to make your users aware of the importance of adding useful comments whenever an svn command prompts them to do so.

Edit /etc/rc.conf:
Just went through this (thank you) however I came across the issue where my FreeBSD box was just listening on tcp6. I'm using this internally on my network but without a tcp6 router this of course doesn't help. To make it work, I just modified my rc.conf to listen on host (telling it to use tcp4). Also for anyone who wants it to start easily on boot, add this to your /etc/rc.conf (replacing the data dir, user, and group as necessary)

svnserve_flags="-d --listen-port=3690 --listen-host="

To make sure svn is running for utf-8 content. Make sure you do have following setting:
# vi ~svn/.cshrc
setenv LC_ALL en_US.UTF-8
setenv LANG en_US.UTF-8

Method 1:

Since the svn server stores everything in utf-8, and crontab is using /bin/sh shell. So, we need to add:

# vi /etc/crontab
*/5 * * * * svn export LC_ALL=en_US.UTF-8 && /usr/local/bin/svn update --username MYUSERNAME --password MYPASSWORD --non-interactive /www/web_hosting > /dev/null 2>&1

Method 2:
Run the command with tcsh:

# vi /etc/crontab
*/5 * * * * svn tcsh -c "/usr/local/bin/svn update --username MYUSERNAME --password MYPASSWORD --non-interactive /www/web_hosting" > /dev/null 2>&1

Edit Firewall Rules:
# vi /usr/local/etc/ipfw.rules
### SVN
$IPF 250 allow tcp from 192.168.500.0/24 to any 3690 in

Refresh Firewall Rules:
# sh /usr/local/etc/ipfw.rules

Backup Repositories
There are at least four ways to backup repositories:
- SVN hotcopy
- SVN dump
- tar entire directory.
- svnsync (also use svnsync + hook script is great. Sync at each commit)

SVN Hotcopy backup
# svnadmin hotcopy /usr/local/repositories/proj1 /home/bot/repositories/proj1
Note: the target (destination) directory must be a empty directory.

SVN Restore from hotcopy
Hotcopy should produce a usable file-level repository. You should be
able to use it as-is if the ownership and permissions are suitable. If
you are running a server, you may have to copy back to the location the
server expects or adjust the configuration to use the new location.

You must read carefully the following sections:

And you will be golden and all set.

As you read those links, then you must realize by now that restoring a
hotcopy which is basically a copy of all your repository its easy just
copy to your Subversion scope, you may need to change owner
permissions and change your hook-scripts (only if you were using
hook-scripts) after this you will be ready to go.

SVN Client - TortoiseSVN
On Windows, create folders:

Right click on each folder (proj1 & proj2) > check out > enter respectively:

or try command line:
# svn checkout svn:// /usr/local/www/apache22/data/proj1
# svn update /usr/local/www/apache22/data/proj1
# svn update
# svn status /usr/local/www/apache22/data/proj1
# svn status -u
# svn add /usr/local/www/apache22/data/proj1/test111.php
# svn commit -m "LogMessage"

List all properties on files, dirs, or revisions:
# svn proplist /www/drupal6/sites

Print the value of a property on files, dirs, or revisions:
# svn propget svn:ignore /www/drupal6/sites

Edit a property with an external editor:
# svn propedit svn:ignore /www/drupal6/sites

Set the value of a property on files, dirs, or revisions:
# cd /www/drupal6/sites
# svn propset svn:ignore *.local .

Note: you should consider use "Edit a property with an external editor" instead.

Deciding Upon a URL Schema

Congratulations! You now have a working repository. Now's the best time to take a closer look at the various URL schemas and choose the access method that best suits your needs.

Chapter 6 of the freely available e-book Version Control with Subversion gives details about the possible configurations. You can choose to install the book when you compile the FreeBSD port by adding -DWITH_BOOK to your make command.

If all of your users log in to the system either locally or through ssh, use the file:/// schema. Because users are "local" to the repository, this scenario doesn't open a TCP/IP port to listen for Subversion connections. However, it does require an active shell account for each user and assumes that your users are comfortable logging in to a Unix server. As with any shell account, your security depends upon your users choosing good passwords and you setting up repository permissions and group memberships correctly. Having users ssh to the system does ensure that they have encrypted sessions.

Another possibility is to integrate Subversion into an existing Apache server. By default, the FreeBSD port of Subversion compiles in SSL support, meaning your users can have the ability to access your repository securely from their browsers using the https:// schema. However, if you're running Apache 2.x instead of Apache 1.x, remember to pass the -DWITH_MOD_DAV_SVN option to make when you compile your FreeBSD port.

If you're considering giving browser access to your users, read carefully through the Apache httpd configuration section of the Subversion book first. You'll have to go through a fair bit of configuration; fortunately, the documentation is complete.

A third approach is to use svnserve to listen for network connections. The book suggests running this process either through inetd or as a stand-alone daemon. Both of these approaches allow either anonymous access or access once the system has authorized a user using CRAM-MD5. Clients connect to svnserve using the svn:// schema.

Anonymous access wasn't appropriate in my scenario, so I followed the configuration options for CRAM-MD5. However, I quickly discovered that CRAM-MD5 wasn't on my FreeBSD system. When a Google search failed to find a technique for integrating CRAM-MD5 with my Subversion binary, I decided to try the last option.

This was to invoke svnserve in tunnel mode, which allows user authentication through the normal SSH mechanism as well as any restrictions you have placed in your /etc/ssh/sshd_config file. For example, I could use the AllowUsers keyword to control which users can authenticate to the system. Note that this schema uses svn+ssh://.

The appeal of this method is that I could use an existing authentication scheme without forcing the user to actually be "on" the repository system. However, this network connection is unencrypted; the use of SSH is only to authenticate. If your data is sensitive, either have your users use file:// after sshing in or use https:// after you've properly configured Apache.

If you decide to use the svnserve server and you compiled in the wrapper, it created a binary called svnserve.bin. Users won't be able to access the repository until:

# cp /usr/local/bin/svnserve.bin /usr/local/bin/svnserve

That's it for this installment. In the next column, I'll show how to start accessing the repository as a client.

Dru Lavigne is a network and systems administrator, IT instructor, author and international speaker. She has over a decade of experience administering and teaching Netware, Microsoft, Cisco, Checkpoint, SCO, Solaris, Linux, and BSD systems. A prolific author, she pens the popular FreeBSD Basics column for O'Reilly and is author of BSD Hacks and The Best of FreeBSD Basics.


No comments: