本手册是为描述 Subversion 1.5 而编写的。如果您正在运行较新版本的 Subversion,我们强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您 Subversion 版本的书籍。
svnserve 程序是一个轻量级服务器,它能够通过使用定制的、有状态的协议,在 TCP/IP 上与客户端通信。客户端通过使用以 svn://
或 svn+ssh://
模式开头的 URL 来联系 svnserve 服务器。本节将解释运行 svnserve 的不同方法,客户端如何向服务器进行身份验证,以及如何为您的仓库配置适当的访问控制。
有几种不同的方法可以运行 svnserve 程序
运行 svnserve 作为一个独立的守护进程,监听请求。
让 Unix inetd 守护进程在每次收到特定端口的请求时,临时启动 svnserve。
让 SSH 通过加密隧道启动一个临时的 svnserve。
将 svnserve 作为 Microsoft Windows 服务运行。
最简单的选项是将 svnserve 作为一个独立的“守护进程”进程运行。为此,使用 -d
选项
$ svnserve -d $ # svnserve is now running, listening on port 3690
当在守护进程模式下运行 svnserve 时,您可以使用 --listen-port
和 --listen-host
选项来定制要“绑定”的精确端口和主机名。
一旦我们如前所述成功启动 svnserve,它就会让系统上的每个仓库都对网络可用。客户端需要在仓库 URL 中指定一个 绝对路径。例如,如果一个仓库位于 /var/svn/project1
,客户端将通过 svn://host.example.com/var/svn/project1
来访问它。为了提高安全性,您可以将 -r
选项传递给 svnserve,这会限制它只导出该路径下的仓库。例如
$ svnserve -d -r /var/svn …
使用 -r
选项实际上会修改程序视为远程文件系统空间根目录的位置。然后,客户端将使用从其中删除该路径部分的 URL,从而留下更短(并且更不显眼)的 URL
$ svn checkout svn://host.example.com/project1 …
如果您希望 inetd 启动该进程,则需要传递 -i
(--inetd
) 选项。在下面的示例中,我们展示了在命令行运行 svnserve -i
的输出,但请注意,这不是实际启动守护进程的方式;有关如何配置 inetd 来启动 svnserve,请参阅示例后的段落。
$ 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 进程来为其提供服务。当然,您可能还想在配置行中添加 -r
,以限制导出哪些仓库。
调用 svnserve 的第三种方法是使用 -t
选项,以隧道模式进行。这种模式假设一个远程服务程序(例如 rsh 或 ssh)已经成功验证了用户的身份,并且现在正在 以该用户的身份 调用一个私有的 svnserve 进程。(请注意,您(用户)很少,甚至几乎永远不会有理由在命令行中使用 -t
调用 svnserve;相反,SSH 守护进程会为您完成此操作。)svnserve 程序会正常运行(通过 stdin
和 stdout
通信),并假设流量正在通过某种隧道自动重定向回客户端。当 svnserve 被像这样通过隧道代理调用时,请确保经过身份验证的用户对仓库数据库文件具有完全的读写权限。这与本地用户通过 file://
URL 访问仓库本质上是一样的。
本选项在本节后面,在 名为“通过 SSH 隧道”的部分 中进行了更详细的描述。
如果您的 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
之前必须有且只有一个空格。最后,要注意要调用的命令行中的空格。如果一个目录名称包含空格(或其他需要转义的字符),请将 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
文件,并开始执行它所描述的任何身份验证和授权策略。
根据定义的策略,可能会发生以下情况之一
客户端可能被允许以匿名方式发出请求,而无需接收身份验证质询。
客户端可能在任何时候被要求进行身份验证。
如果在隧道模式下运行,客户端将声明自己已经通过外部(通常通过 SSH)进行了身份验证。
默认情况下,svnserve 服务器只知道如何发送 CRAM-MD5 [41] 身份验证质询。本质上,服务器向客户端发送少量数据。客户端使用 MD5 哈希算法来创建数据和密码组合的指纹,然后将指纹作为响应发送。服务器使用存储的密码执行相同的计算,以验证结果是否相同。 实际密码不会在网络上传输。
如果您的 svnserve 服务器是在具有 SASL 支持的情况下构建的,它不仅知道如何发送 CRAM-MD5 质询,而且还可能知道许多其他身份验证机制。有关如何配置 SASL 身份验证和加密,请参阅本节后面的 名为“使用 svnserve 与 SASL”的部分。
当然,客户端也可以通过隧道代理(例如 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-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
服务器进程不仅理解对存储库的这些“blanket”访问控制,而且还理解对存储库内特定文件和目录施加的更细粒度的访问限制。要使用此功能,您需要定义一个包含更多详细规则的文件,然后将 authz-db
变量设置为指向它
[general] password-db = userfile realm = example realm # Specific access rules for specific locations authz-db = authzfile
我们在本章后面详细讨论了 authzfile
文件的语法,在 名为“基于路径的授权”的部分 中。请注意,authz-db
变量与 anon-access
和 auth-access
变量不是相互排斥的;如果所有变量都同时定义,则必须满足所有规则,才能允许访问。
对于许多团队来说,内置的 CRAM-MD5 身份验证是他们从 svnserve 中需要的全部内容。但是,如果您的服务器(和您的 Subversion 客户端)使用 Cyrus Simple Authentication and Security Layer (SASL) 库构建,那么您将可以使用多种身份验证和加密选项。
通常,当 Subversion 客户端连接到 svnserve 时,服务器会发送一个问候语,该问候语会宣传它支持的功能列表,客户端会用一个类似的功能列表进行响应。如果服务器配置为需要身份验证,则它会发送一个挑战,其中列出可用的身份验证机制;客户端通过选择一种机制进行响应,然后在一定数量的往返消息中执行身份验证。即使不存在 SASL 功能,客户端和服务器也天生知道如何使用 CRAM-MD5 和 ANONYMOUS 机制(请参阅 名为“内置身份验证和授权”的部分)。如果服务器和客户端链接到 SASL,那么可能还有一些其他身份验证机制可用。但是,您需要在服务器端明确配置 SASL 才能宣传它们。
要在服务器上激活特定的 SASL 机制,您需要执行两件事。首先,在存储库的 svnserve.conf
文件中创建一个 [sasl]
部分,其中包含一个初始键值对
[sasl] use-sasl = true
其次,创建一个名为 svn.conf
的主 SASL 配置文件,将其放在 SASL 库可以找到的位置,通常位于 SASL 插件所在的目录。您需要在您的特定系统上找到插件目录,例如 /usr/lib/sasl2/
或 /etc/sasl2/
。(请注意,这不是存储库中存在的 svnserve.conf
文件!)
在 Windows 服务器上,您还需要编辑系统注册表(使用 regedit 等工具),告诉 SASL 在哪里查找内容。创建一个名为 [HKEY_LOCAL_MACHINE\SOFTWARE\Carnegie Mellon\Project Cyrus\SASL Library]
的注册表项,并在其中放置两个项:一个名为 SearchPath
的项(其值为包含 SASL sasl*.dll
插件库的目录的路径),以及一个名为 ConfFile
的项(其值为包含您创建的 svn.conf
文件的父目录的路径)。
由于 SASL 提供了多种身份验证机制,因此尝试描述每种可能的服务器端配置将是愚蠢的(并且远远超出了本书的范围)。相反,我们建议您阅读 SASL 源代码的 doc/
子目录中提供的文档。它详细介绍了每种机制以及如何为每种机制适当地配置服务器。在本讨论中,我们只演示了配置 DIGEST-MD5 机制的简单示例。例如,如果您的 subversion.conf
(或 svn.conf
)文件包含以下内容
pwcheck_method: auxprop auxprop_plugin: sasldb sasldb_path: /etc/my_sasldb mech_list: DIGEST-MD5
您已告诉 SASL 向客户端宣传 DIGEST-MD5 机制,并将用户密码与位于 /etc/my_sasldb
的专用密码数据库进行核对。然后,系统管理员可以使用 saslpasswd2 程序在数据库中添加或修改用户名和密码
$ saslpasswd2 -c -f /etc/my_sasldb -u realm username
以下几点警告:首先,确保 saslpasswd2 的“realm”参数与您在存储库的 svnserve.conf
文件中定义的 realm 相匹配;如果它们不匹配,身份验证将失败。此外,由于 SASL 的一个缺点,通用 realm 必须是一个没有空格的字符串。最后,如果您决定使用标准 SASL 密码数据库,请确保 svnserve 程序可以读取该文件(以及可能写入该文件,如果您使用的是 OTP 等机制)。
这只是配置 SASL 的一种简单方法。还有许多其他身份验证机制可用,密码可以存储在其他位置,例如 LDAP 或 SQL 数据库中。有关详细信息,请参阅完整的 SASL 文档。
请记住,如果您将服务器配置为仅允许某些 SASL 身份验证机制,这将强制所有连接的客户端也具有 SASL 支持。任何没有 SASL 支持的 Subversion 客户端(包括所有 1.5 之前的客户端)都将无法进行身份验证。一方面,这种限制可能是您想要的(“我的客户端必须全部使用 Kerberos!”)。但是,如果您仍然希望非 SASL 客户端能够进行身份验证,请务必将 CRAM-MD5 机制宣传为一种选项。所有客户端都能够使用 CRAM-MD5,无论它们是否具有 SASL 功能。
如果特定机制支持,SASL 也能够执行数据加密。内置的 CRAM-MD5 机制不支持加密,但 DIGEST-MD5 支持,SRP 等机制实际上需要使用 OpenSSL 库。要启用或禁用不同级别的加密,您可以在存储库的 svnserve.conf
文件中设置两个值
[sasl] use-sasl = true min-encryption = 128 max-encryption = 256
min-encryption
和 max-encryption
变量控制服务器要求的加密级别。要完全禁用加密,请将这两个值都设置为 0。要启用对数据的简单校验和(即,防止篡改并保证数据完整性而不进行加密),请将这两个值都设置为 1。如果您希望允许但不要求加密,请将最小值设置为 0,并将最大值设置为某个位长度。要无条件地要求加密,请将这两个值都设置为大于 1 的数字。在我们之前的示例中,我们要求客户端执行至少 128 位加密,但最多执行 256 位加密。
svnserve 的内置身份验证(和 SASL 支持)非常方便,因为它避免了创建真实系统帐户的需要。另一方面,一些管理员已经建立了完善的 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
,以用户 harryssh
(根据 SSH 用户配置)的身份进行身份验证,然后在远程机器上以用户 harryssh
身份启动一个私有 svnserve 进程。 svnserve 命令以隧道模式 (-t
) 调用,其网络协议通过隧道代理 ssh 在加密连接上“隧道化”。如果客户端执行提交,则经过身份验证的用户名 harryssh
将用作新修订的作者。
这里要理解的重要一点是,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
来阻止访问。[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 的方式,还可以控制服务器机器上 sshd 的行为。在本节中,我们将展示如何控制 sshd 执行的确切 svnserve 命令,以及如何让多个用户共享一个系统帐户。
首先,找到将用于启动 svnserve 的帐户的主目录。确保该帐户安装了 SSH 公钥/私钥对,并且该用户可以通过公钥身份验证登录。密码身份验证将无法正常工作,因为所有以下 SSH 技巧都围绕着使用 SSH authorized_keys
文件展开。
如果不存在,请创建 authorized_keys
文件(在 Unix 上,通常为 ~/.ssh/authorized_keys
)。此文件中的每一行都描述一个允许连接的公钥。这些行通常采用以下形式:
ssh-dsa AAAABtce9euch… [email protected]
第一个字段描述密钥类型,第二个字段是密钥本身的 base64 编码,第三个字段是注释。但是,鲜为人知的是,整行可以由一个 command
字段引导:
command="program" ssh-dsa AAAABtce9euch… [email protected]
当设置了 command
字段时,SSH 守护进程将运行指定的程序,而不是 Subversion 客户端要求的典型的隧道模式 svnserve 调用。这为一系列服务器端技巧打开了大门。在以下示例中,我们将文件的行缩写为:
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 假设指定的参数是经过身份验证的用户。如果没有 --tunnel-user
,则所有提交看起来都像是来自同一个共享系统帐户。
最后需要提醒您的是:即使您已在 authorized_keys
中设置了 command
值,授予用户通过公钥在共享帐户中访问服务器的权限,也可能仍然允许其他形式的 SSH 访问。例如,用户可能仍然可以通过 SSH 获取 shell 访问权限,或者能够通过您的服务器执行 X11 或通用端口转发。为了尽可能地减少用户的权限,您可能希望在 command
之后指定一些限制性选项。
command="svnserve -t --tunnel-user=harry",no-port-forwarding,no-agent-forw arding,no-X11-forwarding,no-pty TYPE1 KEY1 [email protected]
请注意,所有这些都必须在一行上 - 确实在一行上 - 因为 SSH authorized_keys
文件甚至不允许使用传统的反斜杠字符 (\
) 用于续行。我们之所以在书面页面上显示它有换行符,只是为了使其适合物理页面。