svnserve, a Custom Server

svnserve是一个轻型的服务器,可以同客户端通过在TCP/IP基础上的自定义有状态协议通讯,客户端通过使用开头为svn://或者svn+ssh://svnserve的URL来访问一个svnserve服务器。这一小节将会解释运行svnserve的不同方式,客户端怎样实现服务器的认证,怎样配置版本库恰当的访问控制。

调用服务器

有许多不同方法运行svnserve

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

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

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

  • Run svnserve as a Microsoft Windows service.

svnserve as daemon

使用svnserve最简单的方式是作为独立“守护”进程运行,使用-d选项:

$ svnserve -d
$               # svnserve is now running, listening on port 3690

当以守护模式运行svnserve时,你可以使用--listen-port=--listen-host=选项来自定义“绑定”的端口和主机名。

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, then 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
…

使用-r可以有效地改变文件系统的根位置,客户端可以使用去掉前半部分的路径,留下的要短一些的(更加有提示性)URL:

$ svn checkout svn://host.example.com/project1
…

使用svnserve通过inetd

If you want inetd to launch the process, then 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 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 ( 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 such as these (if they don't already exist):

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

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 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.

svnserve over a tunnel

A third 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.

This option is described in much more detail later in this chapter in “SSH 隧道”一节.

svnserve as Windows service

If your Windows system is a descendant of Windows NT (2000, 2003, XP, or 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 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 commandline 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

也需要注意单词binpath会造成误解—它的值是一个命令行,而不是可执行的路径,所以我们为了防止有嵌入的空格而使用了引号围绕。

Once the service is defined, it can be stopped, started, or queried using standard GUI tools (the Services administrative control panel), or at the command line:

C:\> net stop svn
C:\> net start svn

也可以通过删除其定义删除服务:sc delete svn,只需要确定首先停止服务,SC.EXE有许多子命令和选项,更多信息可以运行sc /?查看。

Built-in Authentication and Authorization

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

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

  • The server processes the repository's conf/svnserve.conf file and begins to enforce any authentication and authorization policies it describes.

  • Depending on the defined policies, one of the following may accur:

    • The client may be allowed to make requests anonymously, without ever receiving an authentication challenge.

    • The client may be challenged for authentication at any time.

    • If operating in tunnel mode, the client will declare itself to be already externally authenticated (typically by SSH).

The svnserve server, by default, knows only how to send a CRAM-MD5 [39] 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, then it not only knows how to send CRAM-MD5 challenges, but likely knows a whole host of other authentication mechanisms. See “Using svnserve with 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 “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 “运行配置区”一节 in chapter 7): 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.

Create a users file and realm

For now, the [general] section of the 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 “客户端凭证缓存”一节). 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

password-db的值可以是用户文件的绝对或相对路径,对许多管理员来说,把文件保存在版本库conf/下的svnserve.conf旁边是一个简单的方法。另一方面,可能你的多个版本库使用同一个用户文件,此时,这个文件应该在更公开的地方,版本库分享用户文件时必须配置为相同的域,因为用户列表本质上定义了一个认证域,无论这个文件在哪里,必须设置好文件的读写权限,如果你知道运行svnserve的用户,限定这个用户对这个文件有读权限是必须的。

设置访问控制

svnserve.conf有两个或多个参数需要设置:它们确定未认证(匿名)和认证用户可以做的事情,参数anon-accessauth-access可以设置为noneread或者write,设置为none会限制所有方式的访问,read允许只读访问,而write允许对版本库完全的读/写权限,例如:

[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

实例中的设置实际上是参数的缺省值,你一定不要忘了设置它们,如果你希望更保守一点,你可以完全封锁匿名访问:

[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

服务进程不仅仅理解对版本库的整体访问控制,也可以细粒度的控制版本库某个文件或目录的访问,为了使用这个特性,你需要定一个包含详细规则的文件,并将变量authz-db指向到这个文件。

[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 later in this chapter in “基于路径的授权”一节. 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.

Using svnserve with 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, then 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 “Built-in Authentication and Authorization”一节). If server and client were linked against SASL, then 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.

Authenticating with 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 and 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

then 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 that 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 that 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.

SASL encryption

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.

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.

SSH与svnserve结合很简单,客户端只需要使用svn+ssh://的URL模式来连接:

$ whoami
harry

$ svn list svn+ssh://host.example.com/repos/project
[email protected]'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 used as the author of the new revision.

这里要理解的最重要的事情是Subversion客户端是连接到运行中的svnserve守护进程,这种访问方法不需要一个运行的守护进程,也不需要在必要时唤醒一个,它依赖于ssh来发起一个svnserve进程,然后网络断开后终止进程。

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 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 “支持多种版本库访问方法”一节 later in this chapter). 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. [40]

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 “运行配置区”一节.) For example, suppose you want to use RSH instead of SSH. [41] 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 (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 nonstandard port.

第二点,它展示了怎样定义一个自定义的环境变量来覆盖管道程序中的名字,设置SVN_SSH环境变量是覆盖缺省的SSH管道的一种简便方法,但是如果你需要为多个服务器做出多个不同的覆盖,或许每一个都联系不同的端口或传递不同的SSH选项,你可以使用本例论述的机制。现在如果我们设置JOESSH环境变量,它的值会覆盖管道中的变量值—会执行$JOESSH而不是/opt/alternate/ssh -p 29934

SSH 配置技巧

不仅仅是可以控制客户端调用ssh方式,也可以控制服务器中的sshd的行为方式,在本小节,我们会展示怎样控制sshd执行svnserve,包括如何让多个用户分享同一个系统帐户。

初始设置

作为开始,定位到你启动svnserve的帐号的主目录,确定这个账户已经安装了一套SSH公开/私有密钥对,用户可以通过公开密钥认证,因为所有如下的技巧围绕着使用SSHauthorized_keys文件,密码认证在这里不会工作。

如果这个文件还不存在,创建一个authorized_keys文件(在UNIX下通常是~/.ssh/authorized_keys),这个文件的每一行描述了一个允许连接的公钥,这些行通常是下面的形式:

  ssh-dsa AAAABtce9euch.... [email protected]

第一个字段描述了密钥的类型,第二个字段是未加密的密钥本身,第三个字段是注释。然而,这是一个很少人知道的事实,可以使用一个command来处理整行:

  command="program" ssh-dsa AAAABtce9euch.... [email protected]

command字段设置后,SSH守护进程运行命名的程序而不是通常Subversion客户端询问的svnserve -t。这为实施许多服务器端技巧开启了大门,在下面的例子里,我们简写了文件的这些行:

  command="program" TYPE KEY COMMENT

控制调用的命令

因为我们可以指定服务器端执行的命令,我们很容易来选择运行一个特定的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 “支持多种版本库访问方法”一节.) 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_users file, one per line, and use the --tunnel-user option:

  command="svnserve -t --tunnel-user=harry" TYPE1 KEY1 [email protected]
  command="svnserve -t --tunnel-user=sally" TYPE2 KEY2 [email protected]

这个例子允许Harry和Sally通过公钥认证连接同一个的账户,每个人自定义的命令将会执行。--tunnel-user选项告诉svnserve -t命令采用命名的参数作为经过认证的用户,如果没有--tunnel-user,所有的提交会作为共享的系统帐户提交。

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 [email protected]

(Note that this all must be on one line—truly on one line, since SSH authorized_keys files do not even allow the conventional “\” for line continuation. Thus, there should be no line break and no space between “no-port-forwarding,” and “no-agent-forwarding,” in the example above; the only reason we've formatted it with a line break is to fit it on the physical page of a book.)



[39] 见RFC 2195。

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

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