About a year ago, I switched my primary source code control system from the venerable old CVS to the (relatively) new kid on the block, Subversion. On the whole, I’ve been ecstatically happy with the system. It patched many of the ridiculous problems with CVS, and added on things that opensource community has been asking for for ages (like ‘rename’), but never made it into CVS.
Now I have all my projects stored in SVN, and my main client is using it as well for their code (they’ve chosen to go with SVN and are planning to End Of Life their VSS server – to the dismay of no one).
Subclipse
One of the best tools that made this switchover workable (aside from SVN’s similarity CVS in many respects, particularly on the command line) is the Subclipse plugin for Eclipse. Subclipse provides a great easy to use interface into SVN servers, giving all the functionaly one would have on the command line via a very simple, tightly integrated GUI.
One thing that had been bugging me, however, was the access methodology I was using to get to my (remote) SVN server. It involved setting up a tunnel in SecureCRT (though Putty can do it as well), and then telling subclipse to use my ‘svn://localhost/stonekeep’ repository.
While doing some surfing, I found that Subclipse supports the svn+ssh syntax for specifying the repository. “Great!” says I, “I won’t need to set up the tunnel each time!”
A few more fiddles, a pleasant discovery of a configuration screen in Subclipse, and I had an SVN over SSH connection to my repository, even using my ssh key pair.
Danger, Will Robinson!
But wait! All is not well. When I tried to browse the repository from Subclipse, I quickly hit this error:
Could not open file system at /var/lib/svn/stonekeep
(13)Permission Denied: Berkley DB Error while opening environment for file
system /var/lib/svn/stonekeep/db:
This vexed me, because I had been having no problems accessing the repository locally on the server, and over my ssh tunnel. Both used the locally running ‘svnserve’ on the repository host, so why wasn’t the svn+ssh connection using it?
The answer comes in the SVN documentation, and via a little research:
What’s happening here is that the Subversion client is invoking a local ssh process, connecting to host.example.com, authenticating as the user harry, then spawning a private svnserve process on the remote machine, running as the user harry. The svnserve command is being invoked in tunnel mode (-t) and all network protocol is being âtunneledâ? over the encrypted connection by ssh, the tunnel-agent. svnserve is aware that it’s running as the user harry, and if the client performs a commit, the authenticated username will be attributed as the author of the new revision.
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.
The Problem With This
I’m really unhappy with this model. The problem is that now the user must have read/write access to the entire repository tree. When using a local socket connection (or one over ssh via a normal tunnel), the Subclipse client connects directly to the svnserve process running on the repository box, and interactions with the server happen under that processes ownership.
The svn+ssh protocol does not use the svnserver on the target machine. It tunnels the command to a user-invoked svnserve process, and that process must have read-write access to the repository.
“Well gosh, that doesn’t seem too bad. What’s the issue?”
The issue is that to make this methodology work, I have to give the user read/write access to the repository tree. Meaning, they could happily type ‘rm -rf /var/lib/svn’ and destroy the entire repository. Even worse, the configuration files (including the password / access file, which has passwords in plaintext) must be made available to the general users.
Why svn+ssh doesn’t simply make a local socket connection to the svnserve process already running, I don’t know. But I can find no way to make that happen.
The fix?
As far as I can tell, there really is no direct fix for this. There are various workarounds, which the SVN documentation discusses, including setting up an ‘svn user’ for the svn+ssh logins, and the possibility of using unix groups for permissions, but I feel that if you have a listening socket server on your repository host, you should use it, not introduce a second methodology and have to jump through hoops to implement it.
For now, I have to abandon the svn+ssh possibility, and go back to my hand-configured socket tunnels. There’s no real loss here – they work remarkably well, are very secure, and quite stable. The slight annoyance of having to open up a SecureCRT session before doing work in Eclipse is just that – a slight annoyance. I’ve dealt up until now, and I’ll just continue to deal.
How annoying. Is it possible to restrict the user’s access to the SVN server by binding their SSH key to the “svnserve -t” command? That seems like it might solve your immediate issues.
I have not found a way to do it yet, but I’m way open to suggestions. 🙁
Take a look at the svnbook a bit more — you don’t have to create system accounts with svn+ssh://. You can just drop keys into authorized_keys and restrict the command which is run. It allows multiple users to share a single ssh account:
http://svnbook.red-bean.com/nightly/en/svn.serverconfig.svnserve.html#svn.serverconfig.svnserve.sshtricks
This gets around your problem. Make an ‘svn’ user, make the repository wholly owned by that user, and then drop your remote users’ keys into authorized_keys and set the command appropriately.
While I understand that using a shared login is one of the workarounds, I still feel it’s a workaround. It is a fix for a problem that IMHO should not have been there in the first place.
A service should be a service. It accepts socket connections remotely, authenticates them, and serves up the requested data. Why must I create a local account that has local file access to the repository and bypass the server?
This unfortunately follows one oft he major faults in VSS. With the SVN+SSH solution you propose, we trust that the client ‘will behave’ when it is writing directly to the database files and repository directory. Why should this be the case when we have a perfectly good server available?
For what it’s worth, the solution that Ben suggested (more clearly than I did 🙂 doesn’t give user “svn” (or “harry” or whoever) any more access to the filesystem than the svnserve server itself does. Once sshd has authenticated the user, it starts up svnserve -t and immediately attaches that to the user’s stdin and stdout streams. The remote user does not have the opportunity to start a shell or do anything else that might give them access to the filesystem.
This method is more cumbersome than being able to just have svnserve detach and start listening on an external port, but I can see the reasons in favor of it: it means that the SVN code doesn’t need to include authentication, authorization and encryption code that would be easier to get wrong than what ssh already offers.
It is true that this method is dependent on ssh not having flaws that would allow an attacker around them, but I contend that if ssh has such a flaw then svnserve is the least of our problems. 🙂
I can explain why svn+ssh doesn’t simply make a local socket connection to the svnserve process already running. It is designed after the model of CVS’s :ext: connection method; this makes a lot of sense when you want absolute minimum overhead to running a Subversion service – all you need is an existing sshd – but is not so handy for your situation.
I’ve had good luck using the dav_svn_module and authz_svn_module modules with apache to provide http DAV access to subversion archives with individualized logins. The archive is owned by apache and people with credentials can log in via svn and do things.
Here is a mini howto for getting started with it:
http://www.csoft.net/docs/micro/svndav.html.no
–[Lance]
Honest advice: unless you’re married to the idea of using ssh keys for authentication, break down and set up the apache module for https access. Recent versions of svn have done a lot to bring svnserve near to parity with the DAV server in terms of in-repository ACL support, but the interaction with the unix login system is still a damn hack. Get a self-signed cert, set up mod_auth_pam or mod_auth_ldap so you don’t have to maintain a seperate password db, spend one day reminding your developers to change their urls from svn:// to https:// and then blissfully forget that this entire class of problems ever existed. As a bonus, the entire repository is suddenly a browsable web server, and you get to play with cute tricks like XSLT styles for repository display.