本文档旨在描述 Subversion 1.1。如果您运行的是更新版本的 Subversion,我们强烈建议您访问 https://svnbooks.subversion.org.cn/ 并查阅与您的 Subversion 版本相匹配的书籍版本。

svnserve,自定义服务器

svnserve 程序是一个轻量级服务器,能够通过 TCP/IP 使用自定义的有状态协议与客户端通信。客户端通过使用以以下开头 URL 来联系 svnserve 服务器:svn://svn+ssh://模式。本节将解释运行 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。

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

内置身份验证和授权

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

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

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

  • 根据情况和授权策略,

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

    • 客户端可能随时被要求进行身份验证,或者

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

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

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

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

创建“users”文件和领域

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

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

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

[users]
harry = foopassword
sally = barpassword

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

设置访问控制

svnserve.conf文件中,还有两个变量需要设置:它们决定未经身份验证(匿名)用户和经过验证的用户可以执行的操作。该anon-accessauth-access可以设置为以下值:none, read,或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

请注意,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

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

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

[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 技巧都围绕使用 SSHauthorized_keys文件。

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

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

第一个字段描述密钥类型,第二个字段是密钥本身的 uuencoded 格式,第三个字段是注释。但是,鲜为人知的是,整行可以由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]

此示例允许哈利和莎莉通过公钥身份验证连接到同一个帐户。他们每个人都有一个将要执行的自定义命令;--tunnel-user选项告诉svnserve -t 假设指定的参数是经过身份验证的用户。如果没有--tunnel-user,它将显示所有提交都来自一个共享的系统帐户。

最后要提醒您:通过公钥将用户授予对共享帐户中服务器的访问权限,仍然可能允许其他形式的 SSH 访问,即使您已将command值设置为authorized_keys。例如,用户可能仍然可以通过 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]


[21] 请参阅 RFC 2195。

TortoiseSVN 官方中文版 1.14.7 发布