本手册是为描述 Subversion 1.2 而编写的。如果您运行的是更新版本的 Subversion,我们强烈建议您访问 https://svnbooks.subversion.org.cn/ 并查阅适合您 Subversion 版本的版本。
svnserve 程序是一个轻量级的服务器,能够通过使用自定义的状态协议,通过 TCP/IP 与客户端通信。客户端通过使用以 svn://
或 svn+ssh://
模式开头的 URL 来联系 svnserve 服务器。本节将解释运行 svnserve 的不同方法,客户端如何向服务器进行身份验证,以及如何为您的存储库配置适当的访问控制。
调用 svnserve 程序有几种不同的方法。如果在不带任何选项的情况下调用它,您只会看到一条帮助信息。但是,如果您打算让 inetd 启动该进程,那么您可以传递 -i
(--inetd
) 选项
$ svnserve -i ( success ( 1 2 ( ANONYMOUS ) ( edit-pipeline ) ) )
当使用 --inetd
选项调用时,svnserve 尝试通过 stdin 和 stdout 使用自定义协议与 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
选项。这种模式假设远程服务程序(例如 RSH 或 SSH)已成功验证了一个用户,现在正在调用一个私有的 svnserve 进程 作为该用户。svnserve 程序的行为正常(通过 stdin 和 stdout 通信),并假设流量正在通过某种类型的隧道自动重定向回客户端。当 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
)。让我们一起浏览此文件,并学习如何使用它们。
目前,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-access
和 auth-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 脚本中使用。
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 = read
或 auth-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 的方式,还可以控制服务器机器上 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]