本手册用于描述 Subversion 1.4。如果您运行的是较新版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并参考与您的 Subversion 版本相匹配的版本。

svnserve,自定义服务器

svnserve 程序是一个轻量级服务器,能够通过 TCP/IP 使用自定义的有状态协议与客户端通信。客户端通过以 svn://svn+ssh:// 开头的 URL 与 svnserve 服务器进行连接。本节将解释运行 svnserve 的不同方法,客户端如何向服务器进行身份验证,以及如何为您的仓库配置适当的访问控制。

调用服务器

运行 svnserve 程序有几种不同的方法

  • 运行 svnserve 作为独立守护进程,监听请求。

  • 让 Unix inetd 守护进程在接收到特定端口上的请求时临时生成 svnserve

  • 让 SSH 通过加密隧道调用临时 svnserve

  • svnserve 作为 Windows 服务运行。

svnserve 作为守护进程

最简单的选项是将 svnserve 作为独立的“守护进程”进程运行。为此,请使用 -d 选项

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

在守护进程模式下运行 svnserve 时,可以使用 --listen-port=--listen-host= 选项自定义要“绑定”的特定端口和主机名。

成功启动 svnserve 后,系统上的每个仓库都将对网络可用。客户端需要在仓库 URL 中指定绝对路径。例如,如果一个仓库位于 /usr/local/repositories/project1,那么客户端可以通过 svn://host.example.com/usr/local/repositories/project1 访问它。为了提高安全性,可以将 -r 选项传递给 svnserve,这将限制它仅导出该路径下的仓库。例如

$ svnserve -d -r /usr/local/repositories
…

使用 -r 选项实际上会修改程序视为远程文件系统空间根目录的位置。客户端随后将使用从 URL 中删除了该路径部分的 URL,从而留下更短(更不显眼)的 URL

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

svnserve 通过 inetd

如果您希望 inetd 启动该进程,则需要传递 -i (--inetd) 选项。在示例中,我们显示了在命令行中运行 svnserve -i 的输出,但请注意,这不是您实际启动守护进程的方式;请参阅示例后面的段落以了解如何配置 inetd 以启动 svnserve

$ svnserve -i
( success ( 1 2 ( ANONYMOUS ) ( edit-pipeline ) ) )

当使用 --inetd 选项调用时,svnserve 会尝试通过 stdinstdout 使用自定义协议与 Subversion 客户端进行通信。这是通过 inetd 运行程序的标准行为。IANA 已将端口 3690 预留给 Subversion 协议,因此在类 Unix 系统上,您可以在 /etc/services 中添加如下行(如果它们还不存在)

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

如果您的系统使用的是经典的类 Unix inetd 守护进程,则可以在 /etc/inetd.conf 中添加以下行

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

确保“svnowner”是一个拥有访问您的仓库的适当权限的用户。现在,当客户端连接到服务器上的 3690 端口时,inetd 将生成一个 svnserve 进程来为其提供服务。当然,您可能还需要在配置行中添加 -r,以限制导出哪些仓库。

svnserve 通过隧道

调用 svnserve 的第三种方法是使用 -t 选项以“隧道模式”运行。此模式假设像 RSHSSH 这样的远程服务程序已成功验证了用户,并且现在正在以该用户身份调用一个私有 svnserve 进程。(请注意,您,用户,很少,甚至从未有过在命令行中使用 -t 调用 svnserve 的理由;相反,SSH 守护进程会为您执行此操作。)svnserve 程序会正常运行(通过 stdinstdout 进行通信),并假设流量正在自动通过某种隧道重定向回客户端。当 svnserve 被像这样的隧道代理调用时,请确保已验证的用户对仓库数据库文件具有完全的读写权限。它本质上与本地用户通过 file:// URL 访问仓库相同。

此选项在名为“通过 SSH 隧道”的部分中进行了更详细的描述。

svnserve 作为 Windows 服务

如果您的 Windows 系统是 Windows NT(2000、2003、XP、Vista)的后代,则可以将 svnserve 作为标准 Windows 服务运行。这通常比使用 --daemon (-d) 选项以独立守护进程方式运行它要好得多。使用守护进程模式需要启动一个控制台,键入命令,然后让控制台窗口无限期地运行。但是,Windows 服务在后台运行,可以自动在启动时启动,并且可以使用与其他 Windows 服务相同的管理界面启动和停止。

您需要使用命令行工具 SC.EXE 定义新的服务。与 inetd 配置行类似,您必须指定 svnserve 的确切调用,以便 Windows 在启动时运行

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

这将定义一个名为“svn”的新 Windows 服务,并在启动时执行特定的 svnserve.exe 命令(在本例中,以 C:\repos 为根目录)。然而,前面的示例中存在一些注意事项。

首先,请注意,svnserve.exe 程序必须始终使用 --service 选项调用。任何其他 svnserve 选项都必须在同一行上指定,但您不能添加冲突的选项,例如 --daemon (-d)--tunnel--inetd (-i)。不过,-r--listen-port 之类的选项是可以的。其次,在调用 SC.EXE 命令时要小心空格:key= value 模式在 key=value 之间不能有空格,并且在 value 之前必须有且只有一个空格。最后,要小心您要调用的命令行中的空格。如果目录名称包含空格(或需要转义的其他字符),请将 binpath 的整个内部值放在双引号中,并对其进行转义

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

还要注意,binpath 这个词具有误导性——它的值是一个命令行,而不是可执行文件的路径。这就是为什么您需要在它包含嵌入的空格时将其用引号括起来的原因。

定义服务后,可以使用标准 GUI 工具(服务管理控制面板)或在命令行中停止、启动或查询它

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

还可以通过删除其定义来卸载(即取消定义)服务:sc delete svn。请务必先停止服务!SC.EXE 程序还有许多其他子命令和选项;运行 sc /? 以了解有关它的更多信息。

内置身份验证和授权

当客户端连接到 svnserve 进程时,会发生以下情况

  • 客户端选择一个特定的仓库。

  • 服务器处理仓库的 conf/svnserve.conf 文件,并开始执行其中定义的任何身份验证和授权策略。

  • 根据情况和授权策略,

    • 客户端可能会被允许匿名发出请求,而无需接收身份验证质询,或者

    • 客户端可能会在任何时候被要求进行身份验证,或者

    • 如果在“隧道模式”下运行,客户端将声明自己已在外部进行身份验证。

在撰写本文时,服务器只知道如何发送 CRAM-MD5 [41] 身份验证质询。本质上,服务器会向客户端发送少量数据。客户端使用 MD5 哈希算法来创建数据和密码组合的指纹,然后将指纹作为响应发送。服务器使用存储的密码执行相同的计算以验证结果是否相同。 实际密码不会通过网络传输。

当然,客户端也可以通过隧道代理(例如 SSH)在外部进行身份验证。在这种情况下,服务器只需检查它正在运行的用户,并将其用作已验证的用户名。有关更多信息,请参阅名为“通过 SSH 隧道”的部分

正如您已经猜到的那样,仓库的 svnserve.conf 文件是控制身份验证和授权策略的中心机制。该文件与其他配置文件具有相同的格式(请参阅名为“运行时配置区域”的部分):节名用方括号 ([]) 标记,注释以哈希 (#) 开头,每个节都包含可以设置的特定变量 (variable = value)。让我们浏览这些文件,了解如何使用它们。

创建“用户”文件和领域

现在,svnserve.conf[general] 节包含您需要的所有变量。首先更改这些变量的值:选择一个用于存放用户名和密码的文件名,并选择一个身份验证领域

[general]
password-db = userfile
realm = example realm

您定义的 realm 是一个名称。它告诉客户端它们正在连接的哪种“身份验证命名空间”;Subversion 客户端会在身份验证提示中显示它,并将其用作一个键(以及服务器的主机名和端口)来缓存磁盘上的凭据(请参阅 名为“客户端凭据缓存”的部分)。password-db 变量指向一个单独的文件,该文件包含一个用户名和密码列表,使用相同的熟悉格式。例如

[users]
harry = foopassword
sally = barpassword

password-db 的值可以是用户文件的绝对路径或相对路径。对于许多管理员来说,将该文件保存在存储库的 conf/ 区域中,与 svnserve.conf 放在一起,非常容易。另一方面,您可能希望两个或多个存储库共享同一个用户文件;在这种情况下,该文件可能应该保存在一个更公开的位置。共享用户文件的存储库也应该配置为具有相同的 realm,因为用户列表本质上定义了一个身份验证 realm。无论文件保存在哪里,请确保正确设置文件的读写权限。如果您知道 svnserve 将以哪个用户身份运行,请根据需要限制对用户文件的读取权限。

设置访问控制

svnserve.conf 文件中还有两个变量需要设置:它们决定未经身份验证(匿名)用户和已认证用户被允许执行的操作。变量 anon-accessauth-access 可以设置为 nonereadwrite 值。将值设置为 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

详细了解 authzfile 文件的语法,请参阅 名为“基于路径的授权”的部分。请注意,authz-db 变量与 anon-accessauth-access 变量并不互斥;如果所有变量都同时定义,则必须满足所有规则才能允许访问。

通过 SSH 建立隧道

svnserve 的内置身份验证非常方便,因为它避免了创建真实系统帐户的需要。另一方面,一些管理员已经建立了完善的 SSH 身份验证框架。在这些情况下,所有项目的用户都拥有系统帐户,并且能够“SSH 登录”服务器机器。

svnserve 中使用 SSH 很容易。客户端只需使用 svn+ssh:// URL 方案连接即可

$ whoami
harry

$ svn list svn+ssh://host.example.com/repos/project
harry@host.example.com's password:  *****

foo
bar
baz
…

在此示例中,Subversion 客户端正在调用本地 ssh 进程,连接到 host.example.com,以用户 harry 的身份进行身份验证,然后在远程机器上以用户 harry 的身份生成一个私有 svnserve 进程。svnserve 命令在隧道模式 (-t) 下被调用,其网络协议通过隧道代理 ssh 在加密连接上进行“隧道传输”。svnserve 知道它以用户 harry 的身份运行,如果客户端执行提交操作,则已认证的用户名将用作新修订版的作者。

这里要理解的重要一点是,Subversion 客户端没有连接到正在运行的 svnserve 守护进程。这种访问方法不需要守护进程,如果存在守护进程,它也不会注意到。它完全依赖于 ssh 生成一个临时 svnserve 进程的能力,该进程在网络连接关闭时会终止。

当使用 svn+ssh:// URL 访问存储库时,请记住,是 ssh 程序在提示进行身份验证,而不是 svn 客户端程序。这意味着没有自动密码缓存(请参阅 名为“客户端凭据缓存”的部分)。Subversion 客户端通常会与存储库建立多个连接,但用户通常不会注意到这一点,因为有密码缓存功能。但是,当使用 svn+ssh:// URL 时,用户可能会因为 ssh 在每次出站连接时反复询问密码而感到烦恼。解决办法是在类 Unix 系统上使用独立的 SSH 密码缓存工具,例如 ssh-agent,或在 Windows 上使用 pageant

在通过隧道运行时,授权主要由对存储库数据库文件的操作系统权限控制;这与 Harry 直接通过 file:// URL 访问存储库非常相似。如果多个系统用户要直接访问存储库,您可能希望将它们放到一个公共组中,并且您需要小心处理 umask。(请务必阅读 名为“支持多种存储库访问方法”的部分)。但即使在隧道传输的情况下,也可以使用 svnserve.conf 文件来阻止访问,只需将 auth-access = readauth-access = none 设置即可。[42]

您可能会认为 SSH 隧道的故事到此就结束了,但事实并非如此。Subversion 允许您在运行时 config 文件中创建自定义隧道行为(请参阅 名为“运行时配置区域”的部分)。例如,假设您希望使用 RSH 而不是 SSH[43]。在 config 文件的 [tunnels] 部分中,只需按如下方式定义它即可

[tunnels]
rsh = rsh

现在,您可以通过使用与新变量名称匹配的 URL 方案来使用此新隧道定义:svn+rsh://host/path。当使用新的 URL 方案时,Subversion 客户端实际上将在后台运行命令 rsh host svnserve -t。如果您在 URL 中包含用户名(例如,svn+rsh://username@host/path),客户端也会将其包含在命令中 (rsh username@host svnserve -t)。但您可以定义比这更智能的新隧道方案

[tunnels]
joessh = $JOESSH /opt/alternate/ssh -p 29934

此示例演示了几件事。首先,它展示了如何让 Subversion 客户端启动一个非常具体的隧道二进制文件(位于 /opt/alternate/ssh 的二进制文件),并使用特定选项。在这种情况下,访问 svn+joessh:// URL 将以 -p 29934 作为参数调用特定的 SSH 二进制文件,如果希望隧道程序连接到非标准端口,这将很有用。

其次,它展示了如何定义一个自定义环境变量,该变量可以覆盖隧道程序的名称。设置 SVN_SSH 环境变量是覆盖默认 SSH 隧道代理的便捷方法。但是,如果您需要为不同的服务器设置多个不同的覆盖,每个覆盖可能连接到不同的端口或将不同的选项集传递给 SSH,则可以使用此示例中演示的机制。现在,如果我们要设置 JOESSH 环境变量,它的值将覆盖隧道变量的整个值,将执行 $JOESSH,而不是 /opt/alternate/ssh -p 29934

SSH 配置技巧

不仅可以控制客户端调用 ssh 的方式,还可以控制服务器机器上 sshd 的行为。在本节中,我们将展示如何控制 sshd 执行的精确 svnserve 命令,以及如何让多个用户共享一个系统帐户。

初始设置

首先,找到将用于启动 svnserve 的帐户的主目录。确保该帐户安装了 SSH 公钥/私钥对,并且该用户可以通过公钥身份验证登录。密码身份验证将不起作用,因为所有以下 SSH 技巧都围绕着使用 SSH authorized_keys 文件展开。

如果不存在,请创建 authorized_keys 文件(在 Unix 上,通常是 ~/.ssh/authorized_keys)。此文件中的每一行都描述了一个被允许连接的公钥。这些行通常采用以下形式

  ssh-dsa AAAABtce9euch… user@example.com

第一个字段描述密钥的类型,第二个字段是密钥本身的 Base64 编码,第三个字段是注释。但是,鲜为人知的是,整行可以以 command 字段开头

  command="program" ssh-dsa AAAABtce9euch… user@example.com

当设置了 command 字段时,SSH 守护进程将运行指定的程序,而不是 Subversion 客户端要求的典型的 svnserve -t 调用。这为一些服务器端的技巧打开了大门。在以下示例中,我们将文件的行缩写为

  command="program" TYPE KEY COMMENT

控制调用的命令

由于我们可以指定执行的服务器端命令,因此可以轻松地指定要运行的特定 svnserve 二进制文件,并向其传递额外的参数

  command="/path/to/svnserve -t -r /virtual/root" TYPE KEY COMMENT

在此示例中,/path/to/svnserve 可能是围绕 svnserve 的自定义包装器脚本,它设置了 umask(请参阅 名为“支持多种存储库访问方法”的部分)。它还展示了如何在虚拟根目录中锚定 svnserve,就像在将 svnserve 作为守护进程运行时经常做的那样。这样做是为了限制对系统部分的访问,或者只是为了减轻用户在 svn+ssh:// URL 中输入绝对路径的负担。

也可以让多个用户共享一个帐户。与其为每个用户创建单独的系统帐户,不如为每个人生成一个公钥/私钥对。然后将每个公钥放在 authorized_users 文件中,每行一个,并使用 --tunnel-user 选项

  command="svnserve -t --tunnel-user=harry" TYPE1 KEY1 harry@example.com
  command="svnserve -t --tunnel-user=sally" TYPE2 KEY2 sally@example.com

此示例允许 Harry 和 Sally 通过公钥身份验证连接到同一个帐户。他们每个人都有一个自定义命令会被执行;--tunnel-user 选项告诉 svnserve -t 假设命名参数是经过身份验证的用户。如果没有 --tunnel-user,则所有提交看起来都像是来自一个共享系统帐户。

最后,需要提醒您:在共享帐户中通过公钥授予用户对服务器的访问权限,可能仍然允许其他形式的 SSH 访问,即使您已在 authorized_keys 中设置了 command 值。例如,用户可能仍然可以通过 SSH 获取 shell 访问权限,或者能够通过您的服务器执行 X11 或一般的端口转发。为了尽可能少地授予用户权限,您可能需要在 command 后面立即指定一些限制性选项。

  command="svnserve -t --tunnel-user=harry",no-port-forwarding,\
           no-agent-forwarding,no-X11-forwarding,no-pty \
           TYPE1 KEY1 harry@example.com


[41] 请参见 RFC 2195。

[42] 请注意,使用任何类型的 svnserve 强制访问控制实际上是毫无意义的;用户已经可以直接访问存储库数据库。

[43] 我们不建议这样做,因为 RSH 的安全性明显低于 SSH。