本文档旨在描述 Subversion 的 1.6.x 系列。如果您正在运行其他版本的 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 作为 Microsoft Windows 服务。

  • 运行 svnserve 作为 launchd 作业。

以下各节将详细介绍这些用于 svnserve 的各种部署选项。

svnserve 作为守护进程

最简单的选项是运行 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 访问它。为了提高安全性,您可以向 svnserve 传递 -r 选项,该选项限制它仅导出该路径下的存储库。例如:

$ svnserve -d -r /var/svn
…

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

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

通过 inetd 运行 svnserve

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

$ svnserve -i
( success ( 2 2 ( ) ( edit-pipeline svndiff1 absent-entries commit-revprops d\
epth log-revprops partial-replay ) ) )

当使用 --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 进程来为其提供服务。当然,您可能还想将 -r 添加到配置行,以限制导出哪些存储库。

通过隧道运行 svnserve

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

此选项将在本章后面的 名为“通过 SSH 隧道运行”的部分 中进行更详细的描述。

svnserve 作为 Windows 服务

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

您需要使用命令行工具 SC.EXE 定义新的服务。就像 inetd 配置行一样,您必须为 Windows 在启动时运行指定一个精确的 svnserve 调用。

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 作为 launchd 作业

Mac OS X(10.4 及更高版本)使用 launchd 来管理系统范围和用户范围的进程(包括守护进程)。launchd 作业由 XML 属性列表文件中的参数指定,launchctl 命令用于管理这些作业的生命周期。

当配置为作为 launchd 作业运行时,svnserve 会在需要处理传入的 Subversion svn:// 网络流量时按需自动启动。这比需要您手动调用 svnserve 作为长时间运行的后台进程的配置要方便得多。

要将 svnserve 配置为 launchd 作业,首先创建一个名为 /Library/LaunchDaemons/org.apache.subversion.svnserve.plist 的作业定义文件。示例 6.1,“一个 svnserve launchd 作业定义示例” 提供了这样一个文件的示例。

示例 6.1。一个 svnserve launchd 作业定义示例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>org.apache.subversion.svnserve</string>
        <key>ServiceDescription</key>
        <string>Host Subversion repositories using svn:// scheme</string>
        <key>ProgramArguments</key>
        <array>
            <string>/usr/bin/svnserve</string>
            <string>--inetd</string>
            <string>--root=/var/svn</string>
        </array>
        <key>UserName</key>
        <string>svn</string>
        <key>GroupName</key>
        <string>svn</string>
        <key>inetdCompatibility</key>
        <dict>
            <key>Wait</key>
            <false/>
        </dict>
        <key>Sockets</key>
        <dict>
            <key>Listeners</key>
            <array>
                <dict>
                    <key>SockServiceName</key>
                    <string>svn</string>
                    <key>Bonjour</key>
                    <true/>
                </dict>
            </array>
        </dict>
    </dict>
</plist>

[Warning] 警告

launchd 系统可能有点难以学习。幸运的是,有关本节中描述的命令的文档已存在。例如,在命令行中运行 man launchd 以查看 launchd 本身的联机帮助页,运行 man launchd.plist 以阅读有关作业定义格式的信息,等等。

创建作业定义文件后,可以使用 launchctl load 激活作业。

$ sudo launchctl load \
       -w /Library/LaunchDaemons/org.apache.subversion.svnserve.plist

说清楚一点,此操作实际上还没有启动 svnserve。它只是告诉 launchd 如何在 svn 网络端口上收到传入的网络流量时启动 svnserve;它将在处理完流量后将其终止。

[Note] 注意

因为我们希望 svnserve 成为一个系统范围的守护进程,所以我们需要使用 sudo 以管理员身份管理此作业。还要注意,定义文件中的 UserNameGroupName 键是可选的——如果省略,该作业将以加载作业的用户身份运行。

停用作业也很容易——使用 launchctl unload

$ sudo launchctl unload \
       -w /Library/LaunchDaemons/org.apache.subversion.svnserve.plist

launchctl 还提供了一种方法让您查询作业的状态。如果作业已加载,则将有一行与作业定义文件中指定的 Label 匹配。

$ sudo launchctl list | grep org.apache.subversion.svnserve
-       0       org.apache.subversion.svnserve
$

内置身份验证和授权

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

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

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

  • 根据定义的策略,可能会发生以下情况之一:

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

    • 客户端可能在任何时候都被要求进行身份验证。

    • 如果在隧道模式下运行,客户端将声明自己已被外部身份验证(通常由 SSH 身份验证)。

默认情况下,svnserve 服务器只知道如何发送 CRAM-MD5[44] 身份验证挑战。本质上,服务器会向客户端发送少量数据。客户端使用 MD5 哈希算法对数据和密码组合生成指纹,然后将指纹作为响应发送。服务器使用存储的密码进行相同的计算,以验证结果是否相同。 实际密码不会在网络上传输。

如果你的 svnserve 服务器使用 SASL 支持构建,它不仅知道如何发送 CRAM-MD5 挑战,而且可能还知道许多其他身份验证机制。请参阅本章后面的 “使用 svnserve 与 SASL” 部分,了解如何配置 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 文件并排,会很方便。另一方面,你可能希望两个或多个存储库共享同一个用户文件;在这种情况下,文件可能应该保存在更公开的位置。共享用户文件的存储库也应配置为具有相同的领域,因为用户列表实际上定义了身份验证领域。无论文件保存在哪里,都要确保适当地设置文件的读写权限。如果你知道 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 变量并不相互排斥;如果所有变量都已定义,则必须满足 所有 规则才能允许访问。

使用 svnserve 与 SASL

对于许多团队来说,内置的 CRAM-MD5 身份验证是他们从 svnserve 需要的全部功能。但是,如果你的服务器(和你的 Subversion 客户端)使用 Cyrus 简单身份验证和安全层 (SASL) 库构建,那么你可以使用许多身份验证和加密选项。

通常,当 Subversion 客户端连接到 svnserve 时,服务器会发送一个问候语,宣传它支持的功能列表,客户端会以类似的功能列表进行响应。如果服务器配置为要求身份验证,则它会发送一个挑战,其中列出可用的身份验证机制;客户端通过选择其中一种机制进行响应,然后身份验证会在一定数量的往返消息中完成。即使 SASL 功能不存在,客户端和服务器本质上也了解如何使用 CRAM-MD5 和 ANONYMOUS 机制(请参阅 “内置身份验证和授权” 部分)。如果服务器和客户端链接到 SASL,则可能还有其他身份验证机制可用。但是,你需要在服务器端显式配置 SASL 以宣传它们。

使用 SASL 进行身份验证

要在服务器上激活特定的 SASL 机制,你需要执行两项操作。首先,在存储库的 svnserve.conf 文件中创建一个 [sasl] 节,其中包含一个初始键值对

          [sasl]
          use-sasl = true

其次,在 SASL 库可以找到的位置创建名为 svn.conf 的主 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 机制的一个简单示例。例如,如果你的 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

需要提醒你几句:首先,确保传递给 saslpasswd2realm 参数与你在存储库的 svnserve.conf 文件中定义的相同领域匹配;如果不匹配,身份验证将失败。此外,由于 SASL 中存在一个缺陷,公共领域必须是一个不包含空格的字符串。最后,如果你决定使用标准 SASL 密码数据库,请确保 svnserve 程序可以读取该文件(如果使用的是 OTP 等机制,可能还需要写入权限)。

这只是配置 SASL 的一种简单方法。可以使用许多其他身份验证机制,并且密码可以存储在其他地方,例如 LDAP 或 SQL 数据库中。有关详细信息,请参阅完整的 SASL 文档。

请记住,如果您将服务器配置为仅允许某些 SASL 身份验证机制,这将强制所有连接的客户端也具有 SASL 支持。任何没有 SASL 支持的 Subversion 客户端(包括所有 1.5 之前的客户端)都无法进行身份验证。一方面,这种限制可能正是您想要的(我的所有客户端都必须使用 Kerberos!)。但是,如果您仍然希望非 SASL 客户端能够进行身份验证,请确保将 CRAM-MD5 机制作为一种选择进行宣传。所有客户端都可以使用 CRAM-MD5,无论它们是否具有 SASL 功能。

SASL 加密

如果特定机制支持,SASL 也可以执行数据加密。内置的 CRAM-MD5 机制不支持加密,但 DIGEST-MD5 支持,而 SRP 等机制实际上需要使用 OpenSSL 库。要启用或禁用不同级别的加密,您可以在存储库的 svnserve.conf 文件中设置两个值。

[sasl]
use-sasl = true
min-encryption = 128
max-encryption = 256

min-encryptionmax-encryption 变量控制服务器要求的加密级别。要完全禁用加密,请将这两个值都设置为 0。要启用数据的简单校验和(即,防止篡改并保证数据完整性而无需加密),请将这两个值都设置为 1。如果您希望允许(但不强制)加密,请将最小值设置为 0,并将最大值设置为某个位长。要无条件地要求加密,请将这两个值都设置为大于 1 的数字。在前面的示例中,我们要求客户端至少进行 128 位加密,但最多不超过 256 位加密。

通过 SSH 隧道连接

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

将 SSH 与 svnserve 结合使用非常简单。客户端只需使用 svn+ssh:// URL 方案进行连接。

$ whoami
harry

$ svn list svn+ssh://host.example.com/repos/project
harryssh@host.example.com'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 = readauth-access = none 来阻止访问。[45]

您可能认为 SSH 隧道的介绍到此为止,但事实并非如此。Subversion 允许您在运行时 config 文件中创建自定义隧道行为(参见 名为“运行时配置区域”的部分)。例如,假设您想使用 RSH 而不是 SSH。[46]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)。

[Warning] 警告

请注意,在定义基于 RSH 的隧道时,我们在隧道命令行中添加了 -- 选项结束参数。这样做是为了防止格式错误的主机名被视为隧道命令的另一个选项。您应该对其他隧道程序(例如 SSH)也这样做。

但是您可以定义比这更智能的新隧道方案。

[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 调用。这为许多服务器端技巧打开了大门。在以下示例中,我们将文件的各行缩写为以下形式。

  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_keys 文件中,每行一个,并使用 --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 假设命名的参数是经过身份验证的用户。如果没有 --tunnel-user,则所有提交看起来都像是来自同一个共享系统帐户。

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

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

请注意,所有这些都必须在一行上——真正地在一行上——因为 SSH authorized_keys 文件甚至不允许使用传统的反斜杠字符 (\) 来续行。我们之所以在书页上用换行符显示它,只是为了让它能够适应书页的物理尺寸。



[44] 请参见 RFC 2195。

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

[46] 我们实际上不推荐这样做,因为RSH的安全性能明显低于SSH。