3. svnserve - 定制的服务器

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:// scheme. 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.

3.1. 调用服务器

有许多不同方法运行svnserve

  • 作为一个独立守护进程启动svnserve,监听请求。

  • 当特定端口收到一个请求,就会使UNIX的inetd守护进程临时调用svnserve处理。

  • 使用SSH在加密通道发起临时svnserve服务。

  • 以Windows service服务方式运行svnserve

  • Run svnserve as a launchd job.

The following sections will cover in detail these various deployment options for svnserve.

3.1.1. svnserve 与守护进程

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 we successfully start svnserve as explained previously, 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 /var/svn/project1, a client would reach it via svn://host.example.com/var/svn/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 /var/svn
…

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
…

3.1.2. 通过 inetd 调用 svnserve

If you want inetd to launch the process, you need to pass the -i (--inetd) option. In the following example, we've shown the output from running svnserve -i at the command line, but note that this isn't how you actually start the daemon; see the paragraphs following the example for how to configure inetd to start svnserve.

$ svnserve -i
( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops d\
epth log-revprops partial-replay ) ) )

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 such as these (if they don't already exist):

svn           3690/tcp   # Subversion
svn           3690/udp   # Subversion

如果系统是使用经典的类Unix的inetd守护进程,你可以在/etc/inetd.conf添加这几行:

svn stream tcp nowait svnowner /usr/bin/svnserve svnserve -i

Make sure svnowner is a user that 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.

3.1.3. 通过隧道调用 svnserve

Another way to invoke svnserve is in tunnel mode, using 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. (Note that you, the user, will rarely, if ever, have reason to invoke svnserve with the -t at the command line; instead, the SSH daemon does so for you.) 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.

这个选项将在第 3.4 节 “穿越 SSH 隧道”详细讨论。

3.1.4. svnserve as a Windows service

If your Windows system is a descendant of Windows NT (Windows 2000 or newer), 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 Windows 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 startup time:

C:\> sc create svn
        binpath= "C:\svn\bin\svnserve.exe --service -r C:\repos"
        displayname= "Subversion Server"
        depend= Tcpip
        start= auto

This defines a new Windows service named svn which executes a particular svnserve.exe command when started (in this case, rooted at C:\repos). There are a number of caveats in the prior example, however.

First, notice that the svnserve.exe program must always be invoked with the --service option. Any other options to svnserve must then be specified on the same line, but you cannot add conflicting options such as --daemon (-d), --tunnel, or --inetd (-i). Options such as -r or --listen-port are fine, though. Second, be careful about spaces when invoking the SC.EXE command: the key= value patterns must have no spaces between key= and must have 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 -r C:\repos"
        displayname= "Subversion Server"
        depend= Tcpip
        start= auto

Also note that the word binpath is misleading—its value is a command line, not the path to an executable. That's why you need to surround it with quotes if it contains embedded spaces.

一旦定义了服务,就可以使用标准GUI工具(服务管理控制面板)进行停止, 启动和查询,或者是通过命令行:

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.

3.1.5. svnserve as a launchd job

Mac OS X (10.4 and higher) uses launchd to manage processes (including daemons) both system-wide and per-user. A launchd job is specified by parameters in an XML property list file, and the launchctl command is used to manage the lifecycle of those jobs.

When configured to run as a launchd job, svnserve is automatically launched on demand whenever incoming Subversion svn:// network traffic needs to be handled. This is far more convenient than a configuration which requires you to manually invoke svnserve as a long-running background process.

To configure svnserve as a launchd job, first create a job definition file named /Library/LaunchDaemons/org.apache.subversion.svnserve.plist. 例 6.1 “A sample svnserve launchd job definition” provides an example of such a file.

例 6.1. A sample svnserve launchd job definition

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>org.apache.subversion.svnserve</string>
        <key>ServiceDescription</key>
        <string>Host Subversion repositories using svn:// scheme</string>
        <key>ProgramArguments</key>
        <array>
            <string>/usr/bin/svnserve</string>
            <string>--inetd</string>
            <string>--root=/var/svn</string>
        </array>
        <key>UserName</key>
        <string>svn</string>
        <key>GroupName</key>
        <string>svn</string>
        <key>inetdCompatibility</key>
        <dict>
            <key>Wait</key>
            <false/>
        </dict>
        <key>Sockets</key>
        <dict>
            <key>Listeners</key>
            <array>
                <dict>
                    <key>SockServiceName</key>
                    <string>svn</string>
                    <key>Bonjour</key>
                    <true/>
                </dict>
            </array>
        </dict>
    </dict>
</plist>

[警告] 警告

The launchd system can be somewhat challenging to learn. Fortunately, documentation exists for the commands described in this section. For example, run man launchd from the command line to see the manual page for launchd itself, man launchd.plist to read about the job definition format, etc.

Once your job definition file is created, you can activate the job using launchctl load:

$ sudo launchctl load \
       -w /Library/LaunchDaemons/org.apache.subversion.svnserve.plist

To be clear, this action doesn't actually launch svnserve yet. It simply tells launchd how to fire up svnserve when incoming networking traffic arrives on the svn network port; it will be terminated it after the traffic has been handled.

[注意] 注意

Because we want svnserve to be a system-wide daemon process, we need to use sudo to manage this job as an administrator. Note also that the UserName and GroupName keys in the definition file are optional—if omitted, the job will run as the user who loaded the job.

Deactivating the job is just as easy to do—use launchctl unload:

$ sudo launchctl unload \
       -w /Library/LaunchDaemons/org.apache.subversion.svnserve.plist

launchctl also provides a way for you to query the status of jobs. If the job is loaded, there will be line which matches the Label specified in the job definition file:

$ sudo launchctl list | grep org.apache.subversion.svnserve
-       0       org.apache.subversion.svnserve
$

3.2. 内置的认证和授权

如果一个客户端连接到svnserve进程,如下事情会发生:

  • 客户端选择特定的版本库。

  • 服务器处理版本库的conf/svnserve.conf文件,并且执行里面定义的所有认证和授权政策。

  • 取决于已定义的策略,可能发生下列事情:

    • 如果没有收到认证请求,客户端可能被允许匿名访问。

    • 客户端可以在任何认证时被要求。

    • 如果操作在通道模式,客户端会宣布自己已经在外部得到认证(通常通过SSH)。

The svnserve server, by default, knows only how to send a CRAM-MD5[44] 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, and 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.

If your svnserve server was built with SASL support, it not only knows how to send CRAM-MD5 challenges, but also likely knows a whole host of other authentication mechanisms. See 第 3.3 节 “让 svnserve 使用 SASL” later in this chapter to learn how to configure SASL authentication and encryption.

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 this name as the authenticated username. For more on this, see the later section, 第 3.4 节 “穿越 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 第 1 节 “运行配置区”): 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 these files and learn how to use them.

3.2.1. 创建一个用户文件和认证域

For now, the [general] section of svnserve.conf has all the variables you need. Begin by changing the values of those variables: choose a name for a file that will contain your usernames and passwords and choose 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 第 11.2.1 节 “缓存凭证”). 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 users file as necessary.

3.2.2. 设置访问控制

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 value none, read, or write. Setting the value to none prohibits both reading and writing; 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 understands not only 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

We discuss the syntax of the authzfile file in detail later in this chapter, in 第 5 节 “基于路径的授权”. 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, all of the rules must be satisfied before access is allowed.

3.3. 让 svnserve 使用 SASL

For many teams, the built-in CRAM-MD5 authentication is all they need from svnserve. However, if your server (and your Subversion clients) were built with the Cyrus Simple Authentication and Security Layer (SASL) library, you have a number of authentication and encryption options available to you.

Normally, when a subversion client connects to svnserve, the server sends a greeting that advertises a list of the capabilities it supports, and the client responds with a similar list of capabilities. If the server is configured to require authentication, it then sends a challenge that lists the authentication mechanisms available; the client responds by choosing one of the mechanisms, and then authentication is carried out in some number of round-trip messages. Even when SASL capabilities aren't present, the client and server inherently know how to use the CRAM-MD5 and ANONYMOUS mechanisms (see 第 3.2 节 “内置的认证和授权”). If server and client were linked against SASL, a number of other authentication mechanisms may also be available. However, you'll need to explicitly configure SASL on the server side to advertise them.

3.3.1. 使用 SASL 认证

To activate specific SASL mechanisms on the server, you'll need to do two things. First, create a [sasl] section in your repository's svnserve.conf file with an initial key-value pair:

          [sasl]
          use-sasl = true

Second, create a main SASL configuration file called svn.conf in a place where the SASL library can find it—typically in the directory where SASL plug-ins are located. You'll have to locate the plug-in directory on your particular system, such as /usr/lib/sasl2/ or /etc/sasl2/. (Note that this is not the svnserve.conf file that lives within a repository!)

On a Windows server, you'll also have to edit the system registry (using a tool such as regedit) to tell SASL where to find things. Create a registry key named [HKEY_LOCAL_MACHINE\SOFTWARE\Carnegie Mellon\Project Cyrus\SASL Library], and place two keys inside it: a key called SearchPath (whose value is a path to the directory containing the SASL sasl*.dll plug-in libraries), and a key called ConfFile (whose value is a path to the parent directory containing the svn.conf file you created).

Because SASL provides so many different kinds of authentication mechanisms, it would be foolish (and far beyond the scope of this book) to try to describe every possible server-side configuration. Instead, we recommend that you read the documentation supplied in the doc/ subdirectory of the SASL source code. It goes into great detail about every mechanism and how to configure the server appropriately for each. For the purposes of this discussion, we'll just demonstrate a simple example of configuring the DIGEST-MD5 mechanism. For example, if your subversion.conf (or svn.conf) file contains the following:

pwcheck_method: auxprop
auxprop_plugin: sasldb
sasldb_path: /etc/my_sasldb
mech_list: DIGEST-MD5

you've told SASL to advertise the DIGEST-MD5 mechanism to clients and to check user passwords against a private password database located at /etc/my_sasldb. A system administrator can then use the saslpasswd2 program to add or modify usernames and passwords in the database:

$ saslpasswd2 -c -f /etc/my_sasldb -u realm username

A few words of warning: first, make sure the realm argument to saslpasswd2 matches the same realm you've defined in your repository's svnserve.conf file; if they don't match, authentication will fail. Also, due to a shortcoming in SASL, the common realm must be a string with no space characters. Finally, if you decide to go with the standard SASL password database, make sure the svnserve program has read access to the file (and possibly write access as well, if you're using a mechanism such as OTP).

This is just one simple way of configuring SASL. Many other authentication mechanisms are available, and passwords can be stored in other places such as in LDAP or a SQL database. Consult the full SASL documentation for details.

Remember that if you configure your server to only allow certain SASL authentication mechanisms, this forces all connecting clients to have SASL support as well. Any Subversion client built without SASL support (which includes all pre-1.5 clients) will be unable to authenticate. On the one hand, this sort of restriction may be exactly what you want (My clients must all use Kerberos!). However, if you still want non-SASL clients to be able to authenticate, be sure to advertise the CRAM-MD5 mechanism as an option. All clients are able to use CRAM-MD5, whether they have SASL capabilities or not.

3.3.2. SASL 加密

SASL is also able to perform data encryption if a particular mechanism supports it. The built-in CRAM-MD5 mechanism doesn't support encryption, but DIGEST-MD5 does, and mechanisms such as SRP actually require use of the OpenSSL library. To enable or disable different levels of encryption, you can set two values in your repository's svnserve.conf file:

[sasl]
use-sasl = true
min-encryption = 128
max-encryption = 256

The min-encryption and max-encryption variables control the level of encryption demanded by the server. To disable encryption completely, set both values to 0. To enable simple checksumming of data (i.e., prevent tampering and guarantee data integrity without encryption), set both values to 1. If you wish to allow—but not require—encryption, set the minimum value to 0, and the maximum value to some bit length. To require encryption unconditionally, set both values to numbers greater than 1. In our previous example, we require clients to do at least 128-bit encryption, but no more than 256-bit encryption.

3.4. 穿越 SSH 隧道

svnserve's built-in authentication (and SASL support) 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 scheme to connect:

$ whoami
harry

$ svn list svn+ssh://host.example.com/repos/project
harryssh@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 harryssh (according to SSH user configuration), then spawning a private svnserve process on the remote machine running as the user harryssh. 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. If the client performs a commit, the authenticated username harryssh will be used 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 第 11.2.1 节 “缓存凭证”). 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 such as 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 第 7 节 “支持多种版本库访问方法” 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.[45]

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 runtime config file (see 第 1 节 “运行配置区”). For example, suppose you want to use RSH instead of SSH.[46] 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 scheme that matches the name of your new variable: svn+rsh://host/path. When using the new URL scheme, the Subversion client will actually be running the command rsh host svnserve -t behind the scenes. If you include a username in the URL (e.g., 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 an 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 nonstandard 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.

3.5. SSH Configuration Tricks

It's possible to control not only 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.

3.5.1. 初始设置

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 base64-encoded 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 tunnel-mode svnserve 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

3.5.2. 控制调用的命令

因为我们可以指定服务器端执行的命令,我们很容易来选择运行一个特定的svnserve程序来并且传递给它额外的参数:

  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 第 7 节 “支持多种版本库访问方法”). 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 key pair for each person. Then place each public key into the authorized_keys 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 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-forw
arding,no-X11-forwarding,no-pty TYPE1 KEY1 harry@example.com

Note that this all must be on one line—truly on one line—since SSH authorized_keys files do not even allow the conventional backslash character (\) for line continuation. The only reason we've shown it with a line break is to fit it on the physical page of a book.



[44] 见RFC 2195。

[45] 请注意,使用svnserve的访问控制进行权限控制将会失去意义,因为用户已经直接访问到了版本库数据。

[46] 我们实际上不支持这个,因为RSH在安全性上显著不如SSH。