本手册是为描述 Subversion 1.2 而编写的。如果您运行的是更新版本的 Subversion,我们强烈建议您访问 https://svnbooks.subversion.org.cn/ 并查阅适合您 Subversion 版本的版本。

svnserve,自定义服务器

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

调用服务器

调用 svnserve 程序有几种不同的方法。如果在不带任何选项的情况下调用它,您只会看到一条帮助信息。但是,如果您打算让 inetd 启动该进程,那么您可以传递 -i (--inetd) 选项

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

当使用 --inetd 选项调用时,svnserve 尝试通过 stdinstdout 使用自定义协议与 Subversion 客户端通信。这是通过 inetd 运行的程序的标准行为。IANA 已为 Subversion 协议保留了端口 3690,因此在类 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 进程来为其提供服务。

在 Windows 系统上,存在第三方工具可以将 svnserve 作为服务运行。请查看 Subversion 网站以获取这些工具的列表。

第二个选项是将 svnserve 作为独立的 “守护程序” 进程运行。为此,请使用 -d 选项

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

在守护程序模式下运行 svnserve 时,您可以使用 --listen-port=--listen-host= 选项来定制要 “绑定” 的确切端口和主机名。

还有第三种调用 svnserve 的方法,那就是 “隧道模式”,使用 -t 选项。这种模式假设远程服务程序(例如 RSHSSH)已成功验证了一个用户,现在正在调用一个私有的 svnserve 进程 作为该用户svnserve 程序的行为正常(通过 stdinstdout 通信),并假设流量正在通过某种类型的隧道自动重定向回客户端。当 svnserve 被像这样的隧道代理调用时,请确保已验证的用户对存储库数据库文件具有完全的读写权限。(参见 服务器和权限:一个警告)。它本质上与本地用户通过 file:/// URL 访问存储库相同。

一旦 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 进程时,会发生以下事情

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

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

  • 根据情况和授权策略,

    • 客户端可能会被允许匿名进行请求,而无需接收身份验证挑战,或者

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

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

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

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

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

创建“users” 文件和领域

目前,svnserve.conf 中的 [general] 节包含您需要的所有变量。首先,定义一个包含用户名和密码的文件,以及一个身份验证领域

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

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

[users]
harry = foopassword
sally = barpassword

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

设置访问控制

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

请注意,svnserve 只理解 “总括” 访问控制。用户要么拥有全局读写访问权限,要么拥有全局只读访问权限,要么没有访问权限。对于存储库中的特定路径,没有详细的访问控制。对于许多项目和站点来说,这种级别的访问控制已经足够了。但是,如果您需要按目录的访问控制,则需要使用 Apache 和 mod_authz_svn(参见 名为“按目录的访问控制”的部分),或者使用 pre-commit 钩子脚本来控制写访问权限(参见 名为“钩子脚本”的部分)。Subversion 发行版附带了 commit-access-control.pl 和更复杂的 svnperms.py 脚本,可在 pre-commit 脚本中使用。

SSH 身份验证和授权

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

将 SSH 与 svnserve 结合使用非常容易。客户端只需使用 svn+ssh:// URL 模式连接即可

$ whoami
harry

$ svn list svn+ssh://host.example.com/repos/project
[email protected]'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 设置为即可。

您可能会认为 SSH 隧道的介绍到此就结束了,但事实并非如此。Subversion 允许您在运行时 config 文件中创建自定义隧道行为(请参阅 名为“运行时配置区域”的部分)。例如,假设您想使用 RSH 而不是 SSH。在 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 将调用特定的 SSH 二进制文件,并使用 -p 29934 作为参数——如果您希望隧道程序连接到非标准端口,这将很有用。

其次,它展示了如何定义一个自定义环境变量,该变量可以覆盖隧道程序的名称。设置 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.... [email protected]

第一个字段描述了密钥类型,第二个字段是密钥本身的 uuencode 编码,第三个字段是注释。但是,鲜为人知的是,整行可以在前面加上一个 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

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

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

  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,则所有提交都将显示为来自同一个共享系统帐户。

最后提醒一句:通过公钥让用户访问共享帐户中的服务器,仍然可能允许其他形式的 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 [email protected]


[24] 请参阅 RFC 2195。

TortoiseSVN 官方中文版 1.14.7 发布