The svnserve program is a lightweight
server, capable of speaking to clients over TCP/IP using a
custom, stateful protocol. Clients contact an
svnserve server by using URLs that begin with
the svn://
or svn+ssh://
schema. This section will explain the different ways of running
svnserve, how clients authenticate themselves
to the server, and how to configure appropriate access control
to your repositories.
There are a few different ways to run the svnserve program:
Run svnserve as a standalone daemon, listening for requests.
Have the Unix inetd daemon temporarily spawn svnserve whenever a request comes in on a certain port.
Have SSH invoke a temporary svnserve over an encrypted tunnel.
Run svnserve as a Windows service.
The easiest option is to run svnserve
as a standalone “daemon” process. Use the
-d
option for this:
$ svnserve -d $ # svnserve is now running, listening on port 3690
When running svnserve in daemon mode,
you can use the --listen-port=
and
--listen-host=
options to customize the
exact port and hostname to “bind” to.
Once the svnserve program is running,
it makes every repository on your system available to the
network. A client needs to specify an
absolute path in the repository URL. For
example, if a repository is located at
/usr/local/repositories/project1
, then a
client would reach it via
svn://host.example.com/usr/local/repositories/project1
.
To increase security, you can pass the -r
option to svnserve, which restricts it to
exporting only repositories below that path. For example:
$ svnserve -d -r /usr/local/repositories …
Using the -r
option effectively
modifies the location that the program treats as the root of
the remote filesystem space. Clients then use URLs that
have that path portion removed from them, leaving much
shorter (and much less revealing) URLs:
$ svn checkout svn://host.example.com/project1 …
If you want inetd launch the process,
then you can pass the -i
(--inetd
) option:
$ svnserve -i ( success ( 1 2 ( ANONYMOUS ) ( edit-pipeline ) ) )
When invoked with the --inetd
option,
svnserve attempts to speak with a
Subversion client via stdin and
stdout using a custom protocol. This is
the standard behavior for a program being run via
inetd. The IANA has reserved port 3690
for the Subversion protocol, so on a Unix-like system you can
add lines to /etc/services
like these (if
they don't already exist):
svn 3690/tcp # Subversion svn 3690/udp # Subversion
And if your system is using a classic Unix-like
inetd daemon, you can add this line to
/etc/inetd.conf
:
svn stream tcp nowait svnowner /usr/bin/svnserve svnserve -i
Make sure “svnowner” is a user which has
appropriate permissions to access your repositories. Now, when
a client connection comes into your server on port 3690,
inetd will spawn an
svnserve process to service it. Of course,
you may also want to add -r
to the
configuration line as well, to restrict which repositories are
exported.
A third way to invoke svnserve is in
“tunnel mode”, with the -t
option. This mode assumes that a remote-service program
such as RSH or SSH has
successfully authenticated a user and is now invoking a
private svnserve process as
that user. The svnserve
program behaves normally (communicating via
stdin and stdout),
and assumes that the traffic is being automatically
redirected over some sort of tunnel back to the client.
When svnserve is invoked by a tunnel
agent like this, be sure that the authenticated user has
full read and write access to the repository database files.
It's essentially the same as a local user accessing the
repository via file://
URLs.
This option is described in much more detail in the section called “Tunneling over SSH”.
If your Windows system is a descendant of Windows NT
(2000, 2003, XP, Vista), then you can
run svnserve as a standard Windows
service. This is typically a much nicer experience than
running it as a standalone daemon with the --daemon
(-d)
option. Using daemon-mode requires launching
a console, typing a command, and then leaving the console
window running indefinitely. A Windows service, however,
runs in the background, can start at boot time
automatically, and can be started and stopped using the same
consistent administration interface as other
services.
You'll need to define the new service using the command-line tool SC.EXE. Much like the inetd configuration line, you must specify an exact invocation of svnserve for Windows to run at start-up time:
C:\> sc create svn binpath= "C:\svn\bin\svnserve.exe --service [args]" displayname= "Subversion Server" depend= Tcpip start= auto
This defines a new Windows service named “svn”, and which executes a particular svnserve.exe command when started. There are a number of caveats in the prior example, however.
First, notice that the svnserve.exe
program is always invoked with
the --service
option. You must always
specify this option, and you may not
specify other conflicting options such
as --daemon (-d)
, --tunnel
,
or --inetd (-i)
. Options such
as -r
or --listen-port
are
fine. Second, be careful about spaces when invoking
the SC.EXE command: the
key= value
patterns must have no
spaces between key=
and exactly one
space before the value
. Lastly, be
careful about spaces in your command-line to be invoked. If
a directory name contains spaces (or other characters that
need escaping), place the entire inner value
of binpath
in double-quotes, by escaping
them:
C:\> sc create svn binpath= "\"C:\program files\svn\bin\svnserve.exe\" --service [args]" displayname= "Subversion Server" depend= Tcpip start= auto
Once the service is defined, it can stopped, started, or queried using standard GUI tools (The Services administrative control panel), or at the command line as well:
C:\> net stop svn C:\> net start svn
The service can also be uninstalled (i.e. undefined) by
deleting its definition: sc delete svn
.
Just be sure to stop the service first!
The SC.EXE program has many other
subcommands and options, run sc /?
to
learn more about it.
When a client connects to an svnserve process, the following things happen:
The client selects a specific repository.
The server processes the repository's
conf/svnserve.conf
file, and begins to
enforce any authentication and authorization policies defined
therein.
Depending on the situation and authorization policies,
the client may be allowed to make requests anonymously, without ever receiving an authentication challenge, OR
the client may be challenged for authentication at any time, OR
if operating in “tunnel mode”, the client will declare itself to be already externally authenticated.
At the time of writing, the server only knows how to send a CRAM-MD5 [42] authentication challenge. In essence, the server sends a small amount of data to the client. The client uses the MD5 hash algorithm to create a fingerprint of the data and password combined, then sends the fingerprint as a response. The server performs the same computation with the stored password to verify that the result is identical. At no point does the actual password travel over the network.
It's also possible, of course, for the client to be externally authenticated via a tunnel agent, such as SSH. In that case, the server simply examines the user it's running as, and uses it as the authenticated username. For more on this, see the section called “Tunneling over SSH”.
As you've already guessed, a repository's
svnserve.conf
file is the central
mechanism for controlling authentication and authorization
policies. The file has the same format as other configuration
files (see the section called “Runtime Configuration Area”): section names
are marked by square brackets ([
and
]
), comments begin with hashes
(#
), and each section contains
specific variables that can be set (variable =
value
). Let's walk through this file and learn how
to use them.
For now, the [general]
section of the
svnserve.conf
has all the variables you
need. Begin by defining a file which contains usernames and
passwords, and an authentication realm:
[general] password-db = userfile realm = example realm
The realm
is a name that you define.
It tells clients which sort of “authentication
namespace” they're connecting to; the Subversion
client displays it in the authentication prompt, and uses it
as a key (along with the server's hostname and port) for
caching credentials on disk (see the section called “Client Credentials Caching”). The
password-db
variable points to a separate
file that contains a list of usernames and passwords, using
the same familiar format. For example:
[users] harry = foopassword sally = barpassword
The value of password-db
can be an
absolute or relative path to the users file. For many
admins, it's easy to keep the file right in the
conf/
area of the repository, alongside
svnserve.conf
. On the other hand, it's
possible you may want to have two or more repositories share
the same users file; in that case, the file should probably
live in a more public place. The repositories sharing the
users file should also be configured to have the same realm,
since the list of users essentially defines an
authentication realm. Wherever the file lives, be sure to
set the file's read and write permissions appropriately. If
you know which user(s) svnserve will run
as, restrict read access to the user file as necessary.
There are two more variables to set in the
svnserve.conf
file: they determine what
unauthenticated (anonymous) and authenticated users are
allowed to do. The variables anon-access
and auth-access
can be set to the values
none
, read
, or
write
. Setting the value to
none
restricts all access of any kind;
read
allows read-only access to the
repository, and write
allows complete
read/write access to the repository. For example:
[general] password-db = userfile realm = example realm # anonymous users can only read the repository anon-access = read # authenticated users can both read and write auth-access = write
The example settings are, in fact, the default values of the variables, should you forget to define them. If you want to be even more conservative, you can block anonymous access completely:
[general] password-db = userfile realm = example realm # anonymous users aren't allowed anon-access = none # authenticated users can both read and write auth-access = write
The server process not only understands
these “blanket” access controls to the
repository, but also finer-grained access restrictions placed
on specific files and directories within the repository. To
make use of this feature, you need to define a file containing
more detailed rules, and then set
the authz-db
variable to point to it:
[general] password-db = userfile realm = example realm # Specific access rules for specific locations authz-db = authzfile
The syntax of the authzfile
file is
discussed in detail in
the section called “Path-Based Authorization”. Note
that the authz-db
variable isn't mutually
exclusive with the anon-access
and auth-access
variables; if all the
variables are defined at once, then all
of the rules must be satisfied before access is allowed.
svnserve's built-in authentication can be very handy, because it avoids the need to create real system accounts. On the other hand, some administrators already have well-established SSH authentication frameworks in place. In these situations, all of the project's users already have system accounts and the ability to “SSH into” the server machine.
It's easy to use SSH in conjunction with
svnserve. The client simply uses the
svn+ssh://
URL schema to connect:
$ whoami harry $ svn list svn+ssh://host.example.com/repos/project harry@host.example.com's password: ***** foo bar baz …
In this example, 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 its 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.
The important thing to understand here is that the Subversion client is not connecting to a running svnserve daemon. This method of access doesn't require a daemon, nor does it notice one if present. It relies wholly on the ability of ssh to spawn a temporary svnserve process, which then terminates when the network connection is closed.
When using svn+ssh://
URLs to access a
repository, remember that it's the ssh
program prompting for authentication, and
not the svn client
program. That means there's no automatic password caching
going on (see the section called “Client Credentials Caching”). The
Subversion client often makes multiple connections to the
repository, though users don't normally notice this due to the
password caching feature. When using
svn+ssh://
URLs, however, users may be
annoyed by ssh repeatedly asking for a
password for every outbound connection. The solution is to
use a separate SSH password-caching tool like
ssh-agent on a Unix-like system, or
pageant on Windows.
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”.) But even in the case of
tunneling, the svnserve.conf
file can
still be used to block access, by simply setting
auth-access = read
or auth-access
= none
.
[43]
You'd think that the story of SSH tunneling would end
here, but it doesn't. Subversion allows you to create custom
tunnel behaviors in your run-time config
file (see the section called “Runtime Configuration Area”). For example,
suppose you want to use RSH instead of SSH. In the
[tunnels]
section of your
config
file, simply define it like
this:
[tunnels] rsh = rsh
And now, you can use this new tunnel definition by using a
URL schema that matches the name of your new variable:
svn+rsh://host/path
. When using the new
URL schema, the Subversion client will actually be running the
command rsh host svnserve -t behind the
scenes. If you include a username in the URL (for example,
svn+rsh://username@host/path
) the client
will also include that in its command (rsh
username@host svnserve -t). But you can define new
tunneling schemes to be much more clever than that:
[tunnels] joessh = $JOESSH /opt/alternate/ssh -p 29934
This example demonstrates a couple of things. First, it
shows how to make the Subversion client launch a very specific
tunneling binary (the one located at
/opt/alternate/ssh
) with specific
options. In this case, accessing a
svn+joessh://
URL would invoke the
particular SSH binary with -p 29934
as
arguments—useful if you want the tunnel program to
connect to a non-standard port.
Second, it shows how to define a custom environment
variable that can override the name of the tunneling program.
Setting the SVN_SSH
environment variable is
a convenient way to override the default SSH tunnel agent.
But if you need to have several different overrides for
different servers, each perhaps contacting a different port or
passing a different set of options to SSH, you can use the
mechanism demonstrated in this example. Now if we were to set
the JOESSH
environment variable, its value
would override the entire value of the tunnel
variable—$JOESSH would be executed
instead of /opt/alternate/ssh -p
29934.
It's not only possible to control the way in which the client invokes ssh, but also to control the behavior of sshd on your server machine. In this section, we'll show how to control the exact svnserve command executed by sshd, as well as how to have multiple users share a single system account.
To begin, locate the home directory of the account
you'll be using to launch svnserve. Make
sure the account has an SSH public/private keypair
installed, and that the user can log in via public-key
authentication. Password authentication will not work,
since all of the following SSH tricks revolve around using
the SSH authorized_keys
file.
If it doesn't already exist, create the
authorized_keys
file (on Unix,
typically ~/.ssh/authorized_keys
).
Each line in this file describes a public key that is
allowed to connect. The lines are typically of the
form:
ssh-dsa AAAABtce9euch… user@example.com
The first field describes the type of key, the second
field is the uuencoded key itself, and the third field is a
comment. However, it's a lesser known fact that the entire
line can be preceded by a command
field:
command="program" ssh-dsa AAAABtce9euch… user@example.com
When the command
field is set, the
SSH daemon will run the named program instead of the
typical svnserve -t invocation that the
Subversion client asks for. This opens the door to a number
of server-side tricks. In the following examples, we
abbreviate the lines of the file as:
command="program" TYPE KEY COMMENT
Because we can specify the executed server-side command, it's easy to name a specific svnserve binary to run and to pass it extra arguments:
command="/path/to/svnserve -t -r /virtual/root" TYPE KEY COMMENT
In this example, /path/to/svnserve
might be a custom wrapper script
around svnserve which sets the umask (see
the section called “Supporting Multiple Repository Access Methods”). It also shows how to
anchor svnserve in a virtual root
directory, just as one often does when
running svnserve as a daemon process.
This might be done either to restrict access to parts of the
system, or simply to relieve the user of having to type an
absolute path in the svn+ssh://
URL.
It's also possible to have multiple users share a single
account. Instead of creating a separate system account for
each user, generate a public/private keypair for each
person. Then place each public key into
the authorized_users
file, one per
line, and use the --tunnel-user
option:
command="svnserve -t --tunnel-user=harry" TYPE1 KEY1 harry@example.com command="svnserve -t --tunnel-user=sally" TYPE2 KEY2 sally@example.com
This example allows both Harry and Sally to connect to
the same account via public-key authentication. Each of
them has a custom command that will be executed;
the --tunnel-user
option
tells svnserve -t to assume that the named
argument is the authenticated user. Without
--tunnel-user
, it would appear as though
all commits were coming from the one shared system
account.
A final word of caution: giving a user access to the
server via public-key in a shared account might still allow
other forms of SSH access, even if you've set
the command
value
in authorized_keys
. For example, the
user may still get shell access through SSH, or be able to
perform X11 or general port-forwarding through your server.
To give the user as little permission as possible, you may
want to specify a number of restrictive options immediately
after the command
:
command="svnserve -t --tunnel-user=harry",no-port-forwarding,\ no-agent-forwarding,no-X11-forwarding,no-pty \ TYPE1 KEY1 harry@example.com