本手册旨在描述 Apache™ Subversion® 1.7.x 系列。如果您运行的是其他版本的 Subversion,强烈建议您访问 https://svnbook.subversion.org.cn/ 并查阅适合您 Subversion 版本的手册。

httpd,Apache HTTP 服务器

Apache HTTP 服务器是一个 重型 网络服务器,Subversion 可以利用它。通过自定义模块,httpd 使 Subversion 仓库可以通过 WebDAV/DeltaV[55] 协议提供给客户端,该协议是 HTTP 1.1 的扩展。该协议采用作为万维网核心的无处不在的 HTTP 协议,并添加了写入功能,特别是版本化写入功能。结果是一个标准化、健壮的系统,它作为 Apache 2.0 软件的一部分方便地打包,得到众多操作系统和第三方产品的支持,并且不需要网络管理员再打开另一个自定义端口。[56] 虽然 Apache-Subversion 服务器比 svnserve 具有更多功能,但设置起来也稍微复杂一些。灵活性通常意味着更大的复杂性。

以下讨论中包含许多对 Apache 配置指令的引用。虽然提供了一些使用这些指令的示例,但完整描述它们超出了本章的范围。Apache 团队维护着优秀的文档,可在其网站上公开获取,网址为 https://httpd.apache.ac.cn。例如,配置指令的通用参考位于 https://httpd.apache.ac.cn/docs-2.0/mod/directives.html

此外,在您对 Apache 设置进行更改时,很可能在某个过程中会出错。如果您还不熟悉 Apache 的日志记录子系统,您应该了解它。在您的 httpd.conf 文件中,有指令指定 Apache 生成的访问和错误日志的磁盘位置(分别是 CustomLogErrorLog 指令)。Subversion 的 mod_dav_svn 也使用 Apache 的错误日志记录接口。您始终可以浏览这些文件的内容,以获取可能揭示问题来源的信息,而这些信息在其他情况下并不明显。

先决条件

要通过 HTTP 网络化您的存储库,您基本上需要四个组件,它们包含在两个包中。您需要 Apache httpd 2.0 或更高版本,随附的 mod_dav DAV 模块,Subversion 以及与 Subversion 一起分发的 mod_dav_svn 文件系统提供程序模块。一旦您拥有所有这些组件,网络化您的存储库的过程就变得很简单,例如

  • 使用 mod_dav 模块启动并运行 httpd

  • mod_dav_svn 后端安装到 mod_dav,它使用 Subversion 的库来访问存储库

  • 配置您的 httpd.conf 文件以导出(或公开)存储库

您可以通过从源代码编译 httpd 和 Subversion 或在您的系统上安装它们的预构建二进制包来完成前两项。有关如何编译 Subversion 以与 Apache HTTP Server 一起使用以及如何为此目的编译和配置 Apache 本身的最新信息,请参阅 Subversion 源代码树顶层的 INSTALL 文件。

基本 Apache 配置

一旦您在系统上安装了所有必要的组件,剩下的就是通过其 httpd.conf 文件配置 Apache。使用 LoadModule 指令指示 Apache 加载 mod_dav_svn 模块。此指令必须位于任何其他与 Subversion 相关的配置项之前。如果您的 Apache 是使用默认布局安装的,那么您的 mod_dav_svn 模块应该已安装在 Apache 安装位置的 modules 子目录中(通常为 /usr/local/apache2)。LoadModule 指令语法简单,将命名模块映射到磁盘上共享库的位置。

LoadModule dav_svn_module     modules/mod_dav_svn.so

Apache 将 LoadModule 配置项的库路径解释为相对于其自身服务器根目录的路径。如果按照前面所示进行配置,Apache 将在其自身的 modules/ 子目录中查找 Subversion DAV 模块共享库。根据 Subversion 在您的系统上的安装方式,您可能需要为该库指定完全不同的路径,甚至可能是绝对路径,例如以下示例所示。

LoadModule dav_svn_module     C:/Subversion/lib/mod_dav_svn.so

请注意,如果 mod_dav 作为共享对象编译(而不是直接静态链接到 httpd 二进制文件),您也需要为其提供类似的 LoadModule 语句。确保它位于 mod_dav_svn 行之前。

LoadModule dav_module         modules/mod_dav.so
LoadModule dav_svn_module     modules/mod_dav_svn.so

在您的配置文件中的稍后位置,您现在需要告诉 Apache 您在哪里保存您的 Subversion 存储库(或存储库)。Location 指令具有类似 XML 的表示法,以一个开始标签开头,以一个结束标签结尾,中间包含各种其他配置指令。Location 指令的目的是指示 Apache 在处理指向给定 URL 或其子 URL 的请求时执行一些特殊操作。对于 Subversion,您希望 Apache 只将指向版本化资源的 URL 的支持传递给 DAV 层。您可以指示 Apache 将所有路径部分(URL 中跟随服务器名称和可选端口号的部分)以 /repos/ 开头的 URL 的处理委托给存储库位于 /var/svn/repository 的 DAV 提供程序,使用以下 httpd.conf 语法。

<Location /repos>
  DAV svn
  SVNPath /var/svn/repository
</Location>

如果您计划支持多个 Subversion 存储库,这些存储库将驻留在本地磁盘上的同一个父目录中,您可以使用一个替代指令——SVNParentPath——来指示该公共父目录。例如,如果您知道将在目录 /var/svn 中创建多个 Subversion 存储库,这些存储库将通过以下 URL 访问:http://my.server.com/svn/repos1http://my.server.com/svn/repos2 等,您可以使用以下示例中的 httpd.conf 配置语法。

<Location /svn>
  DAV svn

  # Automatically map any "/svn/foo" URL to repository /var/svn/foo
  SVNParentPath /var/svn
</Location>

使用此语法,Apache 将委托 Subversion DAV 提供程序处理所有路径部分以 /svn/ 开头的 URL,然后 Subversion DAV 提供程序将假定 SVNParentPath 指令指定的目录中的任何项目实际上都是 Subversion 存储库。这是一种特别方便的语法,因为与使用 SVNPath 指令不同,您不必重新启动 Apache 即可添加或删除托管的存储库。

请确保在定义新的 Location 时,它不会与其他导出的位置重叠。例如,如果您的主 DocumentRoot 导出到 /www,请不要在 <Location /www/repos> 中导出 Subversion 存储库。如果请求针对 URI /www/repos/foo.c,Apache 将不知道是在 DocumentRoot 中查找文件 repos/foo.c,还是委托 mod_dav_svn 从 Subversion 存储库返回 foo.c。结果通常是服务器返回的错误,形式为 301 Moved Permanently

在这个阶段,您应该认真考虑权限问题。如果您已经运行 Apache 一段时间作为您的常规 Web 服务器,您可能已经拥有了一系列内容——网页、脚本等等。这些项目已经配置了一组权限,允许它们与 Apache 协同工作,或者更确切地说,允许 Apache 与这些文件协同工作。当 Apache 用作 Subversion 服务器时,它也需要正确的权限来读取和写入您的 Subversion 仓库。

您需要确定一个满足 Subversion 要求的权限系统设置,而不会破坏任何先前存在的网页或脚本安装。这可能意味着更改 Subversion 仓库的权限以匹配 Apache 为您提供服务的其他内容使用的权限,或者它可能意味着在 httpd.conf 中使用 UserGroup 指令来指定 Apache 应该以拥有 Subversion 仓库的用户和组的身份运行。没有一种单一的正确方法来设置您的权限,每个管理员都有不同的原因以某种方式做事。请注意,权限相关问题可能是配置 Subversion 仓库以供 Apache 使用时最常见的疏忽。

身份验证选项

在这一点上,如果您将 httpd.conf 配置为包含以下内容

<Location /svn>
  DAV svn
  SVNParentPath /var/svn
</Location>

您的仓库对全世界 匿名 可访问。在您配置一些身份验证和授权策略之前,您通过 Location 指令提供的 Subversion 仓库将对所有人普遍可访问。换句话说

  • 任何人都可以使用 Subversion 客户端签出仓库 URL(或其任何子目录)的工作副本。

  • 任何人都可以通过将 Web 浏览器指向仓库 URL 来交互式浏览仓库的最新修订版。

  • 任何人都可以提交到仓库。

当然,您可能已经设置了一个预提交钩子脚本以防止提交(参见 名为“实现仓库钩子”的部分)。但是,正如您在阅读时会发现,也可以使用 Apache 的内置方法以特定方式限制访问。

[Tip] 提示

需要身份验证可以防止无效用户直接访问存储库,但不能保护有效用户的网络活动隐私。有关如何配置服务器以支持 SSL 加密以提供额外的保护层,请参阅名为“使用 SSL 保护网络流量”的部分

基本身份验证

验证客户端的最简单方法是通过 HTTP 基本身份验证机制,该机制仅使用用户名和密码来验证用户的身份。Apache 提供了htpasswd 实用程序[57] 用于管理包含用户名和密码的文件。

[Warning] 警告

基本身份验证极其不安全,因为它以几乎纯文本的形式在网络上传输密码。有关使用更安全的摘要机制的详细信息,请参阅名为“摘要身份验证”的部分

首先,创建一个密码文件并授予 Harry 和 Sally 访问权限

$ ### First time: use -c to create the file
$ ### Use -m to use MD5 encryption of the password, which is more secure
$ htpasswd -c -m /etc/svn-auth.htpasswd harry
New password: *****
Re-type new password: *****
Adding password for user harry
$ htpasswd -m /etc/svn-auth.htpasswd sally
New password: *******
Re-type new password: *******
Adding password for user sally
$

接下来,确保 Apache 可以访问提供基本身份验证和相关功能的模块:mod_auth_basicmod_authn_filemod_authz_user。在许多情况下,这些模块被编译到 httpd 本身中,但如果不是,您可能需要使用 LoadModule 指令显式加载其中一个或多个模块

LoadModule auth_basic_module   modules/mod_auth_basic.so
LoadModule authn_file_module   modules/mod_authn_file.so
LoadModule authz_user_module   moduels/mod_authz_user.so

确保 Apache 可以访问所需的功能后,您需要在 <Location> 块中添加更多指令,以告诉 Apache 您希望使用哪种类型的身份验证以及如何执行。

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Basic
  AuthName "Subversion repository"
  AuthType Basic
  AuthBasicProvider file
  AuthUserFile /etc/svn-auth.htpasswd
</Location>

这些指令的工作原理如下

  • AuthName 是您为身份验证域选择的任意名称。大多数浏览器在提示用户名和密码时会在对话框中显示此名称。

  • AuthType 指定要使用的身份验证类型。

  • AuthBasicProvider 指定要用于该位置的基本身份验证提供程序。在我们的示例中,我们希望查询本地密码文件。

  • AuthUserFile 指定要使用的密码文件的位置。

但是,此 <Location> 块还没有做任何有用的事情。它只是告诉 Apache,如果 需要授权,它应该向 Subversion 客户端请求用户名和密码。(当需要授权时,Apache 也需要身份验证。)但是,这里缺少的是告诉 Apache 哪些 客户端请求需要授权的指令;目前,没有。最简单的方法是指定所有 请求都需要授权,方法是在块中添加 Require valid-user

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Basic
  AuthName "Subversion repository"
  AuthType Basic
  AuthBasicProvider file
  AuthUserFile /etc/svn-auth.htpasswd

  # Authorization: Authenticated users only
  Require valid-user
</Location>

有关 Require 指令和其他设置授权策略方法的更多详细信息,请参阅 “授权选项” 部分。

[Note] 注意

AuthBasicProvider 选项的默认值为 file,因此我们不会在以后的示例中包含它。请注意,如果您在更广泛的上下文中将此值设置为其他值,则需要在 Subversion <Location> 块中将其显式重置为 file,才能获得该行为。

摘要身份验证

摘要身份验证是对基本身份验证的改进,它允许服务器在不通过网络发送未受保护的密码的情况下验证客户端的身份。客户端和服务器都使用用户名、密码、请求的 URI 和服务器提供的 随机数(一次性使用数字)创建不可逆的 MD5 哈希值,并且每次需要身份验证时都会更改随机数。客户端将其哈希值发送到服务器,然后服务器验证哈希值是否匹配。

配置 Apache 以使用摘要身份验证非常简单。您需要确保 mod_auth_digest 模块可用(而不是 mod_auth_basic),然后对我们之前的示例进行一些小的更改。

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthDigestProvider file
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Authenticated users only
  Require valid-user
</Location>

请注意,AuthType 现在设置为 Digest,并且我们为 AuthUserFile 指定了不同的路径。摘要身份验证使用与基本身份验证不同的文件格式,该格式使用 Apache 的 htdigest 实用程序创建和管理[58],而不是 htpasswd。摘要身份验证还具有 领域 的附加概念,该概念必须与 AuthName 指令的值匹配。

[Note] 注意

对于摘要身份验证,使用 AuthDigestProvider 选择身份验证提供程序,如前面的示例所示。与 AuthBasicProvider 指令一样,fileAuthDigestProvider 选项的默认值,因此除非您需要覆盖从更广泛的配置上下文中继承的不同值,否则此行不是严格要求的。

密码文件可以按如下方式创建

$ ### First time: use -c to create the file
$ htdigest -c /etc/svn-auth.htdigest "Subversion repository" harry
Adding password for harry in realm Subversion repository.
New password: *****
Re-type new password: *****
$ htdigest /etc/svn-auth.htdigest "Subversion repository" sally
Adding user sally in realm Subversion repository
New password: *******
Re-type new password: *******
$

授权选项

此时,您已经配置了身份验证,但没有配置授权。Apache 能够向客户端发出挑战并确认身份,但尚未告知如何允许或限制对拥有这些身份的客户端的访问。本节介绍两种控制对存储库访问的策略。

整体访问控制

最简单的访问控制方式是授权某些用户对仓库进行只读访问或读写访问。

您可以通过在 <Location> 块内直接添加 Require valid-user 来限制对所有仓库操作的访问。来自 名为“摘要身份验证”的部分 的示例只允许成功验证的客户端对 Subversion 仓库执行任何操作。

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Authenticated users only
  Require valid-user
</Location>

有时您不需要进行如此严格的控制。例如,Subversion 自己的源代码仓库位于 http://svn.collab.net/repos/svn,允许世界上任何人都执行只读仓库任务(例如检出工作副本和浏览仓库),但将写入操作限制为已验证的用户。 LimitLimitExcept 指令允许这种类型的选择性限制。与 Location 指令一样,这些块具有起始和结束标签,您需要将它们嵌套在您的 <Location> 块内。

LimitLimitExcept 指令上存在的参数是受该块影响的 HTTP 请求类型。例如,要允许匿名只读操作,您将使用 LimitExcept 指令(传递 GETPROPFINDOPTIONSREPORT 请求类型参数),并将前面提到的 Require valid-user 指令放在 <LimitExcept> 块内,而不是直接放在 <Location> 块内。

<Location /svn>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Authenticated users only for non-read-only
  #                (write) operations; allow anonymous reads
  <LimitExcept GET PROPFIND OPTIONS REPORT>
    Require valid-user
  </LimitExcept>
</Location>

这些只是一些简单的示例。有关 Apache 访问控制和 Require 指令的更深入信息,请查看 Apache 文档教程集合中的 Security 部分,网址为 https://httpd.apache.ac.cn/docs-2.0/misc/tutorials.html

按目录访问控制

可以使用 mod_authz_svn 设置更细粒度的权限。此 Apache 模块获取从客户端到服务器传递的各种不透明 URL,要求 mod_dav_svn 对其进行解码,然后可能根据配置文件中定义的访问策略否决请求。

如果您从源代码构建了 Subversion,mod_authz_svn 会自动构建并与 mod_dav_svn 一起安装。许多二进制发行版也会自动安装它。要验证它是否已正确安装,请确保它紧随 httpd.confmod_dav_svnLoadModule 指令之后。

LoadModule dav_module         modules/mod_dav.so
LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so

要激活此模块,您需要配置您的 <Location> 块以使用 AuthzSVNAccessFile 指令,该指令指定一个包含您仓库中路径的权限策略的文件。(稍后我们将讨论该文件的格式。)

Apache 非常灵活,您可以选择三种通用模式之一来配置您的块。首先,选择以下基本配置模式之一。(以下示例非常简单;有关 Apache 身份验证和授权选项的更多详细信息,请查看 Apache 自身的文档。)

最开放的方法是允许所有人访问。这意味着 Apache 永远不会发送身份验证挑战,所有用户都被视为 匿名。(参见 示例 6.2,“匿名访问的示例配置”。)

示例 6.2. 匿名访问的示例配置

<Location /repos>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: None

  # Authorization: Path-based access control
  AuthzSVNAccessFile /path/to/access/file
</Location>

在偏执程度的另一端,您可以配置 Apache 来验证所有客户端。此块无条件地要求通过 Require valid-user 指令进行身份验证,并定义了一种验证有效用户的方法。(参见 示例 6.3,“经过身份验证的访问的示例配置”。)

示例 6.3. 经过身份验证的访问的示例配置

<Location /repos>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Path-based access control; authenticated users only
  AuthzSVNAccessFile /path/to/access/file
  Require valid-user
</Location>

第三种非常流行的模式是允许经过身份验证和匿名访问的组合。例如,许多管理员希望允许匿名用户读取某些存储库目录,但将对更敏感区域的访问限制为经过身份验证的用户。在此设置中,所有用户最初都以匿名方式访问存储库。如果您的访问控制策略在任何时候都要求真实用户名,Apache 将要求客户端进行身份验证。为此,请同时使用 Satisfy AnyRequire valid-user 指令。(参见 示例 6.4,“混合身份验证/匿名访问的示例配置”。)

示例 6.4. 混合身份验证/匿名访问的示例配置

<Location /repos>
  DAV svn
  SVNParentPath /var/svn

  # Authentication: Digest
  AuthName "Subversion repository"
  AuthType Digest
  AuthUserFile /etc/svn-auth.htdigest

  # Authorization: Path-based access control; try anonymous access
  #                first, but authenticate if necessary
  AuthzSVNAccessFile /path/to/access/file
  Satisfy Any
  Require valid-user
</Location>

下一步是创建授权文件,其中包含对存储库中特定路径的访问规则。我们将在本章后面的 名为“基于路径的授权”的部分 中介绍如何操作。

禁用基于路径的检查

mod_dav_svn 模块会进行大量工作来确保您标记为 不可读 的数据不会意外泄露。这意味着它需要密切监控所有由命令(如 svn checkoutsvn update)返回的路径和文件内容。如果这些命令遇到根据某些授权策略不可读的路径,则该路径通常会被完全省略。在历史记录或重命名跟踪的情况下(例如,对很久以前重命名的文件运行类似 svn cat -r OLD foo.c 的命令),如果确定对象的某个旧名称受读限制,则重命名跟踪将简单地停止。

所有这些路径检查有时可能非常昂贵,尤其是在 svn log 的情况下。在检索修订版本列表时,服务器会查看每个修订版本中的每个更改路径并检查其可读性。如果发现不可读的路径,则会将其从修订版本更改路径列表中省略(通常使用 --verbose (-v) 选项查看),并且整个日志消息会被抑制。不用说,这在影响大量文件的修订版本上可能很耗时。这是安全性的代价:即使您根本没有配置 mod_authz_svn 等模块,mod_dav_svn 模块仍然要求 Apache httpd 对每个路径运行授权检查。 mod_dav_svn 模块不知道已安装了哪些授权模块,因此它所能做的就是要求 Apache 调用任何可能存在的模块。

另一方面,也存在一种类似于逃生门的机制,允许您以速度换取安全功能。如果您没有强制执行任何类型的按目录授权(即,不使用 mod_authz_svn 或类似模块),您可以禁用所有这些路径检查。在您的 httpd.conf 文件中,使用 SVNPathAuthz 指令,如 示例 6.5,“完全禁用路径检查” 中所示。

示例 6.5. 完全禁用路径检查

<Location /repos>
  DAV svn
  SVNParentPath /var/svn

  SVNPathAuthz off
</Location>

默认情况下,SVNPathAuthz 指令设置为 on。当设置为 off, 时,所有基于路径的授权检查将被禁用;mod_dav_svn 将停止对它发现的每个路径调用授权检查。

使用 SSL 保护网络流量

通过 http:// 连接到存储库意味着所有 Subversion 活动都以明文形式通过网络发送。这意味着诸如检出、提交和更新之类的操作可能会被未经授权的方 嗅探 网络流量拦截。

如果 Subversion 客户端被编译为使用 OpenSSL,它将获得通过 https:// URL 与 Apache 服务器通信的能力,因此所有流量都使用每个连接会话密钥进行加密。Subversion 客户端使用的 WebDAV 库不仅能够验证服务器证书,而且还可以在服务器挑战时提供客户端证书。

Subversion 服务器 SSL 证书配置

本书不涉及如何生成客户端和服务器 SSL 证书以及如何配置 Apache 以使用它们的描述。许多其他参考资料,包括 Apache 自身的文档,都描述了这个过程。

[Tip] 提示

来自知名实体的 SSL 证书通常需要付费,但至少,您可以配置 Apache 使用使用 OpenSSL(https://openssl.ac.cn)之类的工具生成的自签名证书。[59]

Subversion 客户端 SSL 证书管理

当通过 https:// 连接到 Apache 时,Subversion 客户端可能会收到两种不同类型的响应

  • 服务器证书

  • 客户端证书的挑战

服务器证书

当客户端收到服务器证书时,它需要验证服务器是否为它声称的实体。OpenSSL 通过检查服务器证书的签名者或 证书颁发机构 (CA) 来做到这一点。如果 OpenSSL 无法自动信任 CA,或者发生其他问题(例如证书过期或主机名不匹配),Subversion 命令行客户端将询问您是否要无论如何信任服务器证书

$ svn list https://host.example.com/repos/project

Error validating server certificate for 'https://host.example.com:443':
 - The certificate is not issued by a trusted authority.  Use the
   fingerprint to validate the certificate manually!
Certificate information:
 - Hostname: host.example.com
 - Valid: from Jan 30 19:23:56 2004 GMT until Jan 30 19:23:56 2006 GMT
 - Issuer: CA, example.com, Sometown, California, US
 - Fingerprint: 7d:e1:a9:34:33:39:ba:6a:e9:a5:c4:22:98:7b:76:5c:92:a0:9c:7b

(R)eject, accept (t)emporarily or accept (p)ermanently?

这段对话本质上与您可能在 Web 浏览器中看到的相同问题相同(Web 浏览器只是另一个像 Subversion 一样的 HTTP 客户端)。如果您选择 (p)ermanent 选项,Subversion 将在您的私有运行时 auth/ 区域中缓存服务器证书,就像您的用户名和密码被缓存一样(参见 名为“缓存凭据”的部分),并且将来会自动信任该证书。

您的运行时 servers 文件还使您能够让 Subversion 客户端自动信任特定的 CA,无论是全局还是按主机方式。只需将 ssl-authority-files 变量设置为以分号分隔的 PEM 编码的 CA 证书列表即可

[global]
ssl-authority-files = /path/to/CAcert1.pem;/path/to/CAcert2.pem

许多 OpenSSL 安装还具有预定义的 默认 CA 集,这些 CA 几乎被普遍信任。要使 Subversion 客户端自动信任这些标准机构,请将 ssl-trust-default-ca 变量设置为 true

客户端证书挑战

如果客户端收到证书挑战,则服务器要求客户端证明其身份。客户端必须发送回由服务器信任的 CA 签名的证书,以及一个 挑战响应,该响应证明客户端拥有与证书关联的私钥。私钥和证书通常以加密格式存储在磁盘上,并受密码保护。当 Subversion 收到此挑战时,它将询问您加密文件的路径以及保护它的密码

$ svn list https://host.example.com/repos/project

Authentication realm: https://host.example.com:443
Client certificate filename: /path/to/my/cert.p12
Passphrase for '/path/to/my/cert.p12':  ********

请注意,客户端凭据存储在 .p12 文件中。要将客户端证书与 Subversion 一起使用,它必须采用 PKCS#12 格式,这是一种可移植的标准。大多数 Web 浏览器都能够以这种格式导入和导出证书。另一种选择是使用 OpenSSL 命令行工具将现有证书转换为 PKCS#12。

运行时 servers 文件还允许您按主机方式自动执行此挑战。如果您设置了 ssl-client-cert-filessl-client-cert-password 变量,Subversion 可以自动响应客户端证书挑战,而无需提示您

[groups]
examplehost = host.example.com

[examplehost]
ssl-client-cert-file = /path/to/my/cert.p12
ssl-client-cert-password = somepassword

更注重安全的个人可能希望排除 ssl-client-cert-password,以避免将密码明文存储在磁盘上。

额外的好东西

我们已经介绍了 Apache 和 mod_dav_svn 的大多数身份验证和授权选项。但是 Apache 提供了一些其他不错的功能。

仓库浏览

Apache/WebDAV 配置为您的 Subversion 仓库带来的最有用益处之一是,您可以通过普通网页浏览器立即查看版本化的文件和目录。由于 Subversion 使用 URL 来标识版本化的资源,因此用于基于 HTTP 的仓库访问的这些 URL 可以直接在网页浏览器中输入。您的浏览器将对该 URL 发出 HTTP GET 请求;根据该 URL 代表的是版本化的目录还是文件,mod_dav_svn 将以目录列表或文件内容进行响应。

URL 语法

如果 URL 不包含有关您要查看的资源的哪个版本的任何信息,mod_dav_svn 将以最新版本进行响应。此功能具有一个奇妙的副作用,即您可以将 Subversion URL 传递给您的同行作为对文档的引用,而这些 URL 将始终指向该文档的最新版本。当然,您甚至可以将这些 URL 用作来自其他网站的超链接。

从 Subversion 1.6 开始,mod_dav_svn 支持用于检查文件和目录的旧版本的公共 URI 语法。该语法使用 URL 的查询字符串部分来指定一个或两个挂钩修订版和操作修订版,Subversion 将使用它们来确定要向您的网页浏览器显示的文件或目录的哪个版本。添加查询字符串名称/值对 p=PEGREV,其中 PEGREV 是一个修订版号,以指定您希望应用于请求的挂钩修订版。使用 r=REV,其中 REV 是一个修订版号,以指定操作修订版。

例如,如果您希望查看项目 /trunk 中的 README.txt 文件的最新版本,请将您的网页浏览器指向该文件的仓库 URL,它可能类似于以下内容

http://host.example.com/repos/project/trunk/README.txt

如果您现在希望查看该文件的某个旧版本,请在 URL 的查询字符串中添加一个操作修订版

http://host.example.com/repos/project/trunk/README.txt?r=1234

如果您要查看的内容在仓库的最新修订版中不再存在怎么办?这就是挂钩修订版派上用场的地方

http://host.example.com/repos/project/trunk/deleted-thing.txt?p=321

当然,您可以组合挂钩修订版和操作修订版说明符来微调您要查看的确切项目

http://host.example.com/repos/project/trunk/renamed-thing.txt?p=123&r=21

前面的 URL 将显示修订版 21 中的对象,该对象在修订版 123 中位于仓库中的 /trunk/renamed-thing.txt。有关这些 挂钩修订版操作修订版 概念的详细说明,请参阅 名为“挂钩修订版和操作修订版”的部分。它们可能有点难以理解。

提醒一下,mod_dav_svn 的此功能仅提供有限的仓库浏览体验。您可以查看目录列表和文件内容,但无法查看修订属性(例如提交日志消息)或文件/目录属性。对于需要更广泛浏览仓库及其历史记录的用户,有几个第三方软件包可以提供此功能。一些示例包括 ViewVC (http://viewvc.tigris.org)、Trac (http://trac.edgewall.org) 和 WebSVN (http://websvnphp.github.io)。这些第三方工具不会影响 mod_dav_svn 的内置 浏览能力,并且通常提供更广泛的功能集,包括显示上述属性集、显示文件修订版之间的内容差异等等。

正确的 MIME 类型

在浏览 Subversion 仓库时,Web 浏览器通过查看 Apache 对 HTTP GET 请求的响应中返回的 Content-Type: 标头来了解如何呈现文件的內容。此标头的值是某种 MIME 类型。默认情况下,Apache 会告诉 Web 浏览器所有仓库文件都是 默认 MIME 类型,通常是 text/plain。但是,如果用户希望仓库文件以更有意义的方式呈现,这可能会令人沮丧——例如,可能希望仓库中的 foo.html 文件在浏览时实际呈现为 HTML。

要实现这一点,您只需要确保您的文件具有正确的 svn:mime-type 设置。我们在 名为“文件内容类型”的部分 中对此进行了更详细的讨论,您甚至可以配置您的客户端,以便在第一次将文件添加到仓库时自动附加正确的 svn:mime-type 属性;请参阅 名为“自动属性设置”的部分

继续我们的例子,如果将 svn:mime-type 属性设置为 text/html 在文件 foo.html 上,Apache 会正确地告诉您的 Web 浏览器将文件渲染为 HTML。您也可以将适当的 image/* MIME 类型属性附加到图像文件,最终使整个网站可以直接从存储库中查看!通常情况下,这没有问题,只要网站不包含任何动态生成的内容。

自定义外观

您通常会从指向版本化文件的 URL 中获得更多用途——毕竟,有趣的内容往往就在那里。但您可能需要浏览 Subversion 目录列表,您会很快注意到用于显示该列表的生成的 HTML 非常基础,当然不是为了美观(甚至有趣)。为了启用这些目录显示的自定义,Subversion 提供了一个 XML 索引功能。在您的存储库的 httpd.conf 文件的 Location 块中,单个 SVNIndexXSLT 指令将指示 mod_dav_svn 在显示目录列表时生成 XML 输出,并引用您选择的 XSLT 样式表

<Location /svn>
  DAV svn
  SVNParentPath /var/svn
  SVNIndexXSLT "/svnindex.xsl"
  …
</Location>

使用 SVNIndexXSLT 指令和一个创意 XSLT 样式表,您可以使您的目录列表与您网站其他部分使用的颜色方案和图像相匹配。或者,如果您愿意,您可以使用 Subversion 源代码分发中的 tools/xslt/ 目录提供的示例样式表。请记住,提供给 SVNIndexXSLT 指令的路径实际上是一个 URL 路径——浏览器需要能够读取您的样式表才能使用它们!

列出存储库

如果您通过 SVNParentPath 指令从单个 URL 提供一组存储库,那么 Apache 也可能将所有可用存储库显示到 Web 浏览器。只需激活 SVNListParentPath 指令

<Location /svn>
  DAV svn
  SVNParentPath /var/svn
  SVNListParentPath on
  …
</Location>

如果用户现在将她的 Web 浏览器指向 URL http://host.example.com/svn/,她将看到位于 /var/svn 中的所有 Subversion 存储库的列表。显然,这可能是一个安全问题,因此默认情况下此功能处于关闭状态。

Apache 日志记录

由于 Apache 本质上是一个 HTTP 服务器,因此它包含非常灵活的日志记录功能。讨论所有日志记录配置方法超出了本书的范围,但我们应该指出,即使是最通用的 httpd.conf 文件也会导致 Apache 生成两个日志:error_logaccess_log。这些日志可能出现在不同的位置,但通常是在 Apache 安装的日志记录区域中创建的。(在 Unix 上,它们通常位于 /usr/local/apache2/logs/ 中。)

error_log 描述了 Apache 在工作时遇到的任何内部错误。该 access_log 文件记录 Apache 收到的每个传入 HTTP 请求。这使得很容易看到,例如,Subversion 客户端来自哪些 IP 地址,特定客户端使用服务器的频率,哪些用户已正确身份验证,以及哪些请求成功或失败。

不幸的是,由于 HTTP 是一种无状态协议,即使是最简单的 Subversion 客户端操作也会生成多个网络请求。查看 access_log 并推断出客户端正在做什么非常困难——大多数操作看起来像一系列神秘的 PROPPATCHGETPUTREPORT 请求。更糟糕的是,许多客户端操作发送几乎相同的请求序列,因此更难将它们区分开来。

mod_dav_svn 然而,可以提供帮助。通过激活 操作日志 功能,您可以要求 mod_dav_svn 创建一个单独的日志文件,描述您的客户端正在执行的哪些高级操作。

为此,您需要使用 Apache 的 CustomLog 指令(在 Apache 的文档中进行了更详细的解释)。请确保在您的 Subversion Location之外 调用此指令。

<Location /svn>
  DAV svn
  …
</Location>

CustomLog logs/svn_logfile "%t %u %{SVN-ACTION}e" env=SVN-ACTION

在此示例中,我们要求 Apache 在标准 Apache logs 目录中创建一个特殊的日志文件 svn_logfile%t%u 变量分别被请求的时间和用户名替换。真正重要的部分是 SVN-ACTION 的两个实例。当 Apache 看到该变量时,它会替换 SVN-ACTION 环境变量的值,该变量由 mod_dav_svn 在检测到高级客户端操作时自动设置。

因此,您不必像这样解释传统的 access_log

[26/Jan/2007:22:25:29 -0600] "PROPFIND /svn/calc/!svn/vcc/default HTTP/1.1" 207 398
[26/Jan/2007:22:25:29 -0600] "PROPFIND /svn/calc/!svn/bln/59 HTTP/1.1" 207 449
[26/Jan/2007:22:25:29 -0600] "PROPFIND /svn/calc HTTP/1.1" 207 647
[26/Jan/2007:22:25:29 -0600] "REPORT /svn/calc/!svn/vcc/default HTTP/1.1" 200 607
[26/Jan/2007:22:25:31 -0600] "OPTIONS /svn/calc HTTP/1.1" 200 188
[26/Jan/2007:22:25:31 -0600] "MKACTIVITY /svn/calc/!svn/act/e6035ef7-5df0-4ac0-b811-4be7c823f998 HTTP/1.1" 201 227
…

您可以浏览更易于理解的 svn_logfile,如下所示

[26/Jan/2007:22:24:20 -0600] - get-dir /tags r1729 props
[26/Jan/2007:22:24:27 -0600] - update /trunk r1729 depth=infinity
[26/Jan/2007:22:25:29 -0600] - status /trunk/foo r1729 depth=infinity
[26/Jan/2007:22:25:31 -0600] sally commit r1730

除了 SVN-ACTION 环境变量外,mod_dav_svn 还填充了 SVN-REPOSSVN-REPOS-NAME 变量,它们分别包含到存储库的文件系统路径和其基本名称。您可能希望在您的 CustomLog 格式字符串中也包含对这两个变量之一或两个变量的引用,尤其是在将来自多个存储库的使用信息组合到单个日志文件中时。

有关所有记录操作的详尽列表,请参阅 名为“高级日志记录”的部分

直写代理

使用 Apache 作为 Subversion 服务器的一个优点是,它可以设置为简单的复制。例如,假设您的团队分布在全球四个办事处。Subversion 存储库只能存在于其中一个办事处,这意味着其他三个办事处将无法访问它——他们在更新和提交代码时可能会遇到明显更慢的流量和响应时间。一个强大的解决方案是设置一个系统,该系统由一个 Apache 服务器和几个 Apache 服务器组成。如果您在每个办事处放置一个从服务器,用户可以从最靠近他们的任何从服务器检出工作副本。所有读取请求都转到其本地从服务器。写入请求会自动路由到单个主服务器。提交完成后,主服务器会自动使用 svnsync 复制工具将新修订版 推送 到每个从服务器。

这种配置可以显著提升用户感知速度,因为 Subversion 客户端流量通常有 80% 到 90% 是读取请求。如果这些请求来自 本地 服务器,那么效果将更加显著。

在本节中,我们将逐步介绍这种单主服务器/多从服务器系统的标准设置。但是请记住,您的服务器必须至少运行 Apache 2.2.0(已加载 mod_proxy 模块)和 Subversion 1.5(mod_dav_svn)。

[Note] 注意

这只是一个 Subversion 写入代理配置的示例。还有其他方法。例如,主服务器可以将更改推送到每个从服务器,也可以让从服务器定期从主服务器拉取更改。或者,主服务器可以将更改推送到一个从服务器,然后该从服务器将相同的更改推送到下一个从服务器,依此类推。鼓励管理员使用本节了解 Subversion WebDAV 代理部署场景中的基本操作,然后实施最适合其组织的具体方法。

配置服务器

首先,以通常的方式配置主服务器的 httpd.conf 文件。使存储库在特定 URI 位置可用,并根据需要配置身份验证和授权。完成此操作后,以完全相同的方式配置每个 从服务器,但将特殊的 SVNMasterURI 指令添加到块中

<Location /svn>
  DAV svn
  SVNPath /var/svn/repos
  SVNMasterURI http://master.example.com/svn
  …
</Location>

此新指令告诉从服务器将所有写入请求重定向到主服务器。(这是通过 Apache 的 mod_proxy 模块自动完成的。)但是,普通读取请求仍然由从服务器处理。请确保您的主服务器和从服务器都具有匹配的身份验证和授权配置;如果它们不同步,可能会导致重大问题。

接下来,我们需要解决无限递归的问题。在当前配置下,想象一下当 Subversion 客户端向主服务器执行提交时会发生什么。提交完成后,服务器使用 svnsync 将新修订版复制到每个从服务器。但由于 svnsync 看起来只是另一个执行提交的 Subversion 客户端,因此从服务器会立即尝试将传入的写入请求代理回主服务器!这会导致混乱。

解决此问题的办法是让主服务器将修订版推送到从服务器上的不同 <Location>。此位置配置为 代理任何写入请求,但接受来自(且仅来自)主服务器 IP 地址的正常提交。

<Location /svn-proxy-sync>
  DAV svn
  SVNPath /var/svn/repos
  Order deny,allow
  Deny from all
  # Only let the server's IP address access this Location:
  Allow from 10.20.30.40
  …
</Location>
设置复制

现在您已在主服务器和从服务器上配置了 Location 块,您需要配置主服务器以复制到从服务器。我们的演练使用 svnsync,它在 名为“使用 svnsync 进行复制”的部分 中有更详细的介绍。

首先,确保每个从服务器存储库都包含一个 pre-revprop-change 钩子脚本,该脚本允许远程修订版属性更改。(这是作为 svnsync 接收方的标准程序。)然后登录到主服务器并配置每个从服务器存储库 URI 以从本地磁盘上的主存储库接收数据。

$ svnsync init http://slave1.example.com/svn-proxy-sync \
               file:///var/svn/repos
Copied properties for revision 0.
$ svnsync init http://slave2.example.com/svn-proxy-sync \
               file:///var/svn/repos
Copied properties for revision 0.
$ svnsync init http://slave3.example.com/svn-proxy-sync \
               file:///var/svn/repos
Copied properties for revision 0.

# Perform the initial replication

$ svnsync sync http://slave1.example.com/svn-proxy-sync \
               file:///var/svn/repos
Transmitting file data ....
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .......
Committed revision 2.
Copied properties for revision 2.
…

$ svnsync sync http://slave2.example.com/svn-proxy-sync \
               file:///var/svn/repos
Transmitting file data ....
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .......
Committed revision 2.
Copied properties for revision 2.
…

$ svnsync sync http://slave3.example.com/svn-proxy-sync \
               file:///var/svn/repos
Transmitting file data ....
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .......
Committed revision 2.
Copied properties for revision 2.
…

完成此操作后,我们配置主服务器的 post-commit 钩子脚本以在每个从服务器上调用 svnsync

#!/bin/sh
# Post-commit script to replicate newly committed revision to slaves

svnsync sync http://slave1.example.com/svn-proxy-sync \
             file:///var/svn/repos > /dev/null 2>&1 &
svnsync sync http://slave2.example.com/svn-proxy-sync \
             file:///var/svn/repos > /dev/null 2>&1 &
svnsync sync http://slave3.example.com/svn-proxy-sync \
             file:///var/svn/repos > /dev/null 2>&1 &

每行末尾的额外部分不是必需的,但它们是一种巧妙的方法,可以允许同步命令在后台运行,这样 Subversion 客户端就不会永远等待提交完成。除了此 post-commit 钩子之外,您还需要一个 post-revprop-change 钩子,以便当用户(例如)修改日志消息时,从服务器也会收到该更改。

#!/bin/sh
# Post-revprop-change script to replicate revprop-changes to slaves

REV=${2}
svnsync copy-revprops http://slave1.example.com/svn-proxy-sync \
                      file:///var/svn/repos \
                      -r ${REV} > /dev/null 2>&1 &
svnsync copy-revprops http://slave2.example.com/svn-proxy-sync \
                      file:///var/svn/repos \
                      -r ${REV} > /dev/null 2>&1 &
svnsync copy-revprops http://slave3.example.com/svn-proxy-sync \
                      file:///var/svn/repos \
                      -r ${REV} > /dev/null 2>&1 &

我们这里唯一遗漏的是如何处理用户级锁(svn lock 类型的锁)。锁由主服务器在提交操作期间强制执行;但有关锁的所有信息在读取操作(如 svn updatesvn status)期间由从服务器分发。因此,一个功能齐全的代理设置需要将锁信息从主服务器完美地复制到从服务器。不幸的是,大多数可能用于实现此复制的机制都或多或少地存在缺陷[60]。许多团队根本不使用 Subversion 的锁定功能,因此这可能对您来说不是问题。遗憾的是,对于那些确实使用锁的团队,我们没有关于如何优雅地解决此缺陷的建议。

注意事项

您的主/从复制系统现在应该可以使用了。但是,需要提醒您注意一些事项。请记住,这种复制在面对计算机或网络崩溃时并不完全健壮。例如,如果某个自动化的 svnsync 命令由于某种原因未能完成,从服务器将开始落后。例如,您的远程用户会看到他们提交了修订版 100,但当他们运行 svn update 时,他们的本地服务器会告诉他们修订版 100 还不存在!当然,这个问题会在下次提交时自动修复,并且随后的 svnsync 成功 - 同步将复制所有等待的修订版。但是,您可能需要设置某种带外监控来注意同步失败并强制 svnsync 在出现问题时运行。

写入代理部署模型的另一个限制涉及版本不匹配 - 也就是安装的 Subversion 版本 - 在主服务器和从服务器之间。Subversion 的每个新版本都可能(并且通常会)在客户端和服务器之间使用的网络协议中添加新功能。由于功能协商针对从服务器进行,因此使用的是从服务器的协议版本和功能集。但是写入操作实际上是传递到主服务器的。因此,始终存在从服务器以对从服务器而言正确但对主服务器而言不正确的方式回答客户端的功能协商请求的风险,如果主服务器运行的是旧版本的 Subversion。这会导致客户端尝试使用主服务器不理解的新功能,并导致失败。在 Subversion 1.7 中存在一些此类已知问题,该版本引入了其 HTTP 协议的主要修订版。如果您在预 1.7 主服务器前面部署 Subversion 1.7 从服务器,则需要使用 SVNAdvertiseV2Protocol Off 指令配置从服务器的 Subversion <Location> 块。

[Tip] 提示

为了获得最佳效果,请尝试在主服务器和从服务器上运行相同版本的 Subversion。

其他 Apache 功能

Apache 作为一款强大的 Web 服务器,它提供的许多功能也可以在 Subversion 中用来增强功能或安全性。Subversion 客户端能够使用 SSL(安全套接字层,前面已经讨论过)。如果您的 Subversion 客户端支持 SSL,它可以使用 https:// 访问您的 Apache 服务器,并享受高质量的加密网络会话。

同样有用的是 Apache 和 Subversion 之间关系的其他功能,例如指定自定义端口(而不是默认的 HTTP 端口 80)或用于访问 Subversion 存储库的虚拟域名,或者通过 HTTP 代理访问存储库。

最后,因为 mod_dav_svn 使用 WebDAV/DeltaV 协议的子集,所以可以通过第三方 DAV 客户端访问存储库。大多数现代操作系统(Win32、OS X 和 Linux)都具有将 DAV 服务器作为标准网络 共享文件夹 挂载的内置功能。这是一个复杂的话题,但实现起来也很奇妙。有关详细信息,请阅读 附录 C,WebDAV 和自动版本控制

请注意,可以对 mod_dav_svn 进行许多其他小的调整,这些调整过于晦涩,无法在本节中提及。有关 mod_dav_svn 响应的所有 httpd.conf 指令的完整列表,请参见 名为“指令” 的部分,该部分位于 第 9 章,Subversion 完整参考 中。



[56] 他们真的很讨厌这样做。

[59] 虽然自签名证书仍然容易受到 中间人 攻击,但与嗅探未受保护的密码相比,这种攻击对于偶然的观察者来说要困难得多。