Setting up multiple repository access methods with SubversionEdit
Motivation
I currently (September 2006) offer Subversion-based access to the source code for a number of projects. This read-only access occurs over unencrypted channels. These are notes I’ve made while setting up encrypted access via SSH for the purposes of write-access (when committing). Even though Subversion never actually passes passwords over the network as plaintext, and even though the per-transaction design of the protocol is such that vulnerability to snooping-based attacks is minimal, I still think it’s good practice to perform write-access over an encrypted channel.
Background
As noted in the "Supporting Multiple Repository Access Methods" section of the Subversion book, accessing the repository simultaneously via various means is complicated.
Anonymous access is mediated by the svnserve
daemon running as the svn
user which has exclusive access to the repository. When accessing via svn+ssh
then the access is performed as the user who authenticated via SSH, not necessarily the svn
user and this can cause ownership and permissions conflicts. The solution must carefully take this potential conflict into account.
Set-up
- This article offers an excellent guide to setting up access.
- Subversion itself comes with an "SSH tricks" document.
Key pair generation
Each unique user that needs write access to the repository will need a key pair.
ssh-keygen -t dsa -f ~/.ssh/id_dsa_subversion
chmod 400 ~/.ssh/id_dsa_subversion
Client-side set-up
Set the SVN_SSH
environment variable:
export SVN_SSH="/usr/bin/ssh -i /Users/wincent/.ssh/id_dsa_subversion"
To make this setting available to GUI-level apps such as SmartSVN and Xcode you should also edit your ~/.MacOSX/environment.plist
file accordingly. A minimal environment.plist
file might resemble this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SVN_SSH</key>
<string>/Users/wincent/.ssh/id_dsa_subversion</string>
</dict>
</plist>
You would set such a variable with the following command:
defaults write ~/.MacOSX/environment SVN_SSH -string "/Users/wincent/.ssh/id_dsa_subversion"
I also had to add the following to my .ssh/config
to prevent SSH from trying to authenticate me as user wincent
instead of user svn
:
Host svn.example.com
HostName svn.example.com
User svn
Server-side set-up
sudo -s
cd /var/lib/svn
sudo -u svn mkdir .ssh
chmod 700 .ssh
cd .ssh
sudo -u svn touch authorized_keys
chmod 400 authorized_keys
sudo -s
cd /var/lib/svn
sudo -u svn mkdir .ssh
chmod 700 .ssh
cd .ssh
sudo -u svn touch authorized_keys
chmod 400 authorized_keys
Edit the authorized_keys
file to include the public key (~/.ssh/id_dsa_subversion
) created above; the following is split across multiple lines for readability but in the actual file it must appear all on the same line. Additional users would each have their own line in the file.
command="/usr/local/bin/svnserve -t
--tunnel-user=wincent
-r /var/lib/svn/repositories",
no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty
ssh-dss AA...WA== wincent@cuzco.local
Note that there are no spaces between the quoted command
, the trailing commas, and the no-port-forwarding
specification and the other restrictions.
The public key itself begins with a key-type marker, ssh-dss
, followed by the key itself (abbreviated here to AA...WA==
, and is followed by a comment (in this case, my username and the name of the host on which I generated the key).
Testing
Perform a test checkout via svn+ssh
to confirm that everything is working as expected:
svn co svn+ssh://svn.example.com/project-name/trunk
In order for this to work I found that I had to set the shell of the svn
user to /bin/sh
(previously it was /sbin/nologin
). Note that even though a login shell is defined for that user it is not possible to actually gain a login shell because logins are explicitly disallowed for the svn
user in the /etc/shadow
file (that is, the password field contains only "*", indicating that the logins are not permitted for that user).
Using ssh-agent
If your private key is protected by a passphrase (and it probably should be) then in typical usage Subversion will prompt you many, many times for the passphrase as it makes multiple connection attempts.
There are two ways to avoid these repeated prompts:
- Do not protect your private key with a passphrase: with appropriate filesystem permissions, this is probably about as secure as the standard Subversion method of storing your passwords as plaintext in your
~/.subversion/
directory (although as of Subversion 1.4.0 passwords are stored securely in the keychain; see "Keychain-based storage of Subversion passwords"). - Use
ssh-agent
andssh-add
: this is the more secure option, detailed below.
You can launch ssh-agent
from a Terminal window and it will set the required environment variables, SSH_AUTH_SOCK
and SSH_AGENT_PID
. The problem with this approach is that such environment variables won’t be available to window-based programs like Xcode. Modifying the ~/.MacOSX/environment.plist
file doesn’t necessarily help either; for one thing, the value of SSH_AUTH_SOCK
will randomly vary (incorporating both a random string component as well as the process ID number of the ssh-agent
process; for example, /tmp/ssh-A4yghslTsh/agent.10578
) and the file is read only once at login (that is, if you make changes after login they will only be read upon the next login by which time the information will be out of date).
A possible workaround is to set only the SSH_AUTH_SOCK
variable and use the -a
switch to force the ssh-agent
to use that (known ahead of time) value, something like ~/.ssh/agent.sock
, and forget about the SSH_AGENT_PID
variable seeing as it can’t be known ahead of time (although this may break certain commands such as ssh-agent -k
). If the appropriate permission are set on the ~/.ssh/
directory (700, for example) then the use of a fixed socket file should not pose any security risk. The SSH_AUTH_SOCK
variable should also be set in your ~/.bash_profile
.
You could put such settings in place with the following commands:
defaults write ~/.MacOSX/environment SSH_AUTH_SOCK -string "/Users/wincent/.ssh/agent.sock"
echo 'export SSH_AUTH_SOCK="/Users/wincent/.ssh/agent.sock"' >> ~/.bash_profile
Once that set-up is in place all that’s required is to actually run the ssh-agent
process at login and kill it off at logout. There are three possible ways of doing this:
- Launch it manually from the terminal once and once only per session.
- Launch it using launchd (see example; this example shows how to patch
ssh-agent
for use withlaunchd
but it is not suitable for use here because theSSH_AUTH_SOCK
environment variable can’t be known prior to login time). - Write a wrapper script or program to launch it, and add the wrapper to your login items.
I have not tested whether this ssh-agent
set-up works with Xcode’s integrated Subversion support (I do all my interaction with Subversion from the command line or from within SmartSVN).
Keychain integration
The above methods can be made truly passwordless (that is, even the initial passphrase prompts can be eliminated) by storing the passphrases in the login keychain. When the user logs in he/she effectively unlocks the login keychain and the passphrases stored therein can be accessed without intervention. In this way we get the convenience of passwordless operation with none of the drawbacks of storing passwords insecurely on the disk.
I am currently working on an application that provides the following functionality:
- Launches
ssh-agent
at login - Automatically adds identities with
ssh-add
- Provides a GUI for entering passphrases (for use from within GUI apps)
- Optionally stores passphrases in the login keychain
- Provides a GUI for adding/removing and enabling/disabling identities
- Checks environment variables at launch and offers to correct any problems detected
The above functionality is already implemented and working. If people express and interest I may release the software publicly and potentially add more features.
Working copy switching
svn switch --relocate svn://svn.example.com/project-name/trunk \
svn+ssh://svn.example.com/project-name/trunk
See also
svn switch --relocate svn://svn.example.com/project-name/trunk \
svn+ssh://svn.example.com/project-name/trunk