Table of Contents
- SVN+SSH path-based authorisation
- 1.1. Rationale
- 1.2. Existing methods
- 1.3. Requirements
- 1.4. Choice
- 2.1. Initialise a SVN repository :
- 2.2. Set up basic authorisation
- 2.3. SVN+SSH configuration :
- 3.1. svnserve daemon
- 3.2. svnserve tunnel
- 4.1. Give all permissions to svn users :
- 4.2. Add public SVN user keys to its authorized_keys following the below format :
- 4.3. Configure a generic svnserve.conf
- 4.4. Per repository permissions :
- 4.5. User checkouts
- 4.6. Deploy website on web server
- 5.1. SVN migration
- 5.2. Clean up a SVN repository from bogus commits
- 5.3. Reference for Debugging
- See Also
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
SVN+SSH path-based authorisation
This is an article about using SVN as a secure replacement for FTP to facilitate partners collaborations. If you are not interested in the rationale/testing part you ca go straight to the SVN+SSH path-based authorisation How-to.
- Problem
1.1. Rationale
As we are hosting plenty of websites, we have to work with different partners (web designer, integrators, website developers etc.). All using different operating systems and browsers and all demanding a plain old FTP access to upload their work. We tried to get them to use chrooted SFTP access but it seems they couldn’t get their head around it.
Internally we were using SVN for all our projects. It’s running on a webdav enabled Apache with SSL linked with a password based LDAP authentication. This server was only accessible through our VPN (yes we are a bit paranoid).
The idea was to allow our partners/customers to have read/write access to our web servers without the need for an insecure password based FTP access (yes even if it is running through SSL). A solution we are contemplating is to give a secure access to our SVN server. Our web servers would then have working copies of the part of the repository they are concerned. Auto-update can be done through a cron jobs that calls a simple svn update. Partners would then be able to work on same files instead of having lengthy discussion of whose turns it is to upload to the FTP.
In short the goal was :
- non-password authentication
- encrypted
- path-based authorisation (to avoid to have to create a SSH repository per-project)
- if possible try to avoid another Apache instance (all our servers are openVZ containers and I don’t want to add yet another Apache server)
1.2. Existing methods
There is 3 way of running a SVN server :
- On a Webdav share. It is only available for Apache with mod_dav_svn. Lighttpd’s webdav does not support SVN’s specifics. Nginx and lighttpd advise to only use them as caching/proxy mechanism for an Apache webdav server.
- svnserve as a daemon. There is no encryption available for svnserve which means that all data are transiting in clear except the authentication that can be through SASL. SASL provides lots of authentication methods and notably LDAP.
- svnserve ran standalone from a SSH connection.
According to the SVN book it is only possible to do path based authorisation with Apache and svnserve daemon (See Table 6.1)
svnserve over SSH : Read/write access only grant-able over the whole repository
1.3. Requirements
Path base authorisation is necessary if we want to provide access to partners on website devs. The best solution would be to keep the current repository structure :
- Websites
|-trunk
|-html
|-customer1website1.com
|-customer2website2.fr
|-drupal6
|-customer1website3.net
|-customer1website4.com
|...
and give only access to the customer’s website folder. Else we would have to completely re-structure the SVN and do some script magic to create/delete/link lots of separate SVN to redmine.
1.4. Choice
- We cannot use webdav as only Apache has a webdav SVN module and we do not want to waste resources on it.
- svnserve is not an option as data won’t be crypted (even if login can be)
- SSH is a great option except that when connecting to the SVN server through SSH, it spawns a svnserve standalone process which doesn’t support path based authorisation, according to the documentation.
- Testing
2.1. Initialise a SVN repository :
mkdir /var/lib/svn
cd /var/lib/svn/
svnadmin create test
2.2. Set up basic authorisation
vi /var/lib/svn/test/conf/svnserve.conf
-> [general]
anon-access = none
auth-access = write
password-db = passwd
authz-db = authz
vi /var/lib/svn/test/conf/authz
-> [groups]
testwrite = user1
testread = svn
[test:/]
@testwrite = rw
@testread = r
[test:/public]
@testread = rw
# passwd is only required for the svnserve daemon
vi /var/lib/svn/test/conf/passwd
-> [users]
user1 = test
svn = test
svnserve -d -r /var/lib/svn
svn co --no-auth-cache svn://127.0.0.1/test
Committing things with user svn only works in the ‘public’ folder. whereas user1 can commit everywhere.
2.3. SVN+SSH configuration :
adduser svn --disabled-password
cp -ax /root/.ssh/ /home/svn/
chown -R svn:svn /home/svn/
chown -R svn:svn /var/lib/svn/
vi /home/svn/.ssh/authorized_keys
-> command="svnserve -t -r /var/lib/svn --config-file=/var/lib/svn/haven/conf/svnserve.conf --log-file=/tmp/svnserver.log --tunnel-user=USERNAME",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty USERKEY
# On Client
svn co svn+ssh://svn@svnserver/test
According to the SVN book’s SSH tricks. This configuration is very secure but does not respect above configuration regarding path based authorisation.
As said in the SSHauth section of the SVN book :
When running over a tunnel, authorization is primarily controlled by operating system permissions to the repository’s database files; it’s very much the same as if Harry were accessing the repository directly via a file:// URL. If multiple system users are going to be accessing the repository directly, you may want to place them into a common group, and you’ll need to be careful about umasks (be sure to read the section called “Supporting Multiple Repository Access Methods” later in this chapter). But even in the case of tunneling, you can still use the svnserve.conf file to block access, by simply setting auth-access = read or auth-access = none.
- Solution
After some testing/debugging I manage to come up with 2 solutions that should work with our set goals :
3.1. svnserve daemon
We can simply override SVN+SSH behaviour by using netcat in the commands= section of the authorized_keys file. So for example we can do the following :
vi /home/svn/.ssh/authorized_keys
-> command="netcat localhost 3690"
svnserve -d -r /var/lib/svn/
# On the client
svn co svn+ssh://svn@svnserver/test
This will use 2 authentications process. First it will use the SSH keys, then it will ask for a regular username/password. That’s where you could use SASL to link this with your authentication of choice (LDAP or others). This isn’t ideal but according to the SVN documentation this is the only way of having path based authorisation with SSH, which as you will see below is NOT TRUE.
3.2. svnserve tunnel
-
From the start of my debugging I found out that svnserve.conf, authz and passwd ARE indeed loaded by svnserve (even in tunnel mode -t). So I was pretty sure that path based authorisation should work with little tinkering.
-
After debugging the whole application I found that path based authorisation SHOULD work by modifying the configuration. Simply revoking all authorisation in the authz file before allowing them works like a charm ! vi /var/lib/svn/test/conf/authz -> [groups] testwrite = user1 testread = svn
#Revoke all rights to the SVN folder [/] * = [test:/] @testwrite = rw [test:/public] @testread = rw
In that case svn can’t check out /test but has full permissions in /test/public !
- Beware of the default permissions in the svnserve.conf that cannot
be overridden. Setting auth-access to read will prevent EVERY user
from writing despite anything that can be written in the authz file,
as stated above :
But even in the case of tunneling, you can still use the svnserve.conf file to block access, by simply setting auth-access = read or auth-access = none._
- Implementation
Now that we found out that the SVN documentation is misleading (probably from lack of update), here is a simple procedure to setup path based authorisation with SVN+SSH.
4.1. Give all permissions to svn users :
chown -R svn:svn /var/lib/svn/
4.2. Add public SVN user keys to its authorized_keys following the below format :
vi /home/svn/.ssh/authorized_keys
-> command="svnserve -t -r /var/lib/svn --log-file=/tmp/svnserver.log --tunnel-user=USERNAME",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-dss USERKEY
4.3. Configure a generic svnserve.conf
/var/lib/svn/svnserve.conf
-> [general]
anon-access = none
auth-access = write
# password-db = passwd
authz-db = authz
rm */conf/svnserve.conf .
# Our example repository for all websites :
ln -s /var/lib/svn/svnserve.conf websites/conf/
# All you other projects
ln -s /var/lib/svn/svnserve.conf project2/conf/
...
ln -s /var/lib/svn/svnserve.conf projectn/conf/
4.4. Per repository permissions :
vi /var/lib/svn/websites/conf/authz
-> [aliases]
# Could be used with LDAP i.e. :
# joe = /C=XZ/ST=Dessert/L=Snake City/O=Snake Oil, Ltd./OU=Research Institute/CN=Joe Average
[groups]
websitesread = webserver1, webserver2
websiteswrite = adminuser
customer1site1read = customer1
customer1site1write = designer1, webdeveloper1
# VERY IMPORTANT
[/]
* =
[websites:/]
@websitesread = r
@websiteswrite = rw
[websites:/trunk/html/customer1/site1.com]
@customer1site1read = r
@customer1site1write = rw
4.5. User checkouts
SVN+SSH makes it very easy to test permissions of your repository. You just have to change your USERNAME in SVN’s authorized_keys.
As customer1 :
svn co svn+ssh://svn@svnserver/websites
svn: Authorization failed
svn co svn+ssh://svn@svnserver/websites/trunk/html/customer1/site1.com
At revision 5.
As adminuser :
svn co svn+ssh://svn@svnserver/websites
At revision 5.
4.6. Deploy website on web server
- Generate ssh key for webserver1 su - www-data ssh-keygen -t rsa -b 2048
- On the SVN server add webserver1’s public key to svn’s authorized_keys and give it the username webserver1
- Back on webserver1 cd /var/www/customer1/site1/web/ svn co svn+ssh://svn@svnserver/websites/trunk/html/customer1/site1.com crontab -e */5 * * * * /usr/bin/svn --non-interactive update /var/www/customer1/site1.com/web 2>&1 > /dev/null
- Related topics
5.1. SVN migration
See Migration-trac-redmine#6-SVN-migration.
5.2. Clean up a SVN repository from bogus commits
While migrating our SVN to a new server we jump on the occasion to clean up some bogus commits. Here is an example on how to modify the author property, that can be used for any revision properties.
-
List all committer svn log --quiet file:///var/lib/svn/project1/ | awk '/^r/ {print $3}' | sort -u
-
Change bogus committer (requires the pre-revprop-change script) for rev in
svn log --quiet file:///var/lib/svn/project1/ | awk '/bogususer/ {print substr($1,2)}'
; do svn propset --revprop -r $rev svn:author correctuser file:///var/lib/svn/project1/ ; done # If done after the import in redmine we need to modify this in Redmine's DB as well : sqlite3 /var/lib/dbconfig-common/sqlite3/redmine/instances/default/redmine_default -> update changesets set user_id = X where committer="bogususer" and user_id is null; update changesets set committer="correctuser" where committer = "bogususer";
5.3. Reference for Debugging
Here are some links to tools and documentation that were useful for my debugging :
-
The SVN daemon protocol.
-
If you require to sniff the full SVN protocol you can use the following : aptitude install simpleproxy simpleproxy -L 3333 -R 127.0.0.1:3690 -t trace # Locally do svn co svn://127.0.0.1:3333/test
-
For svnserve step by step debugging, I compiled from source and used gdb, nemiver and netbeans.
See Also
- The migration from trac to Redmine that was performed in conjunction with the above.