本文档尚在编写中,内容可能会发生较大变化,可能不准确地描述 Apache™ Subversion® 软件的任何已发布版本。将此页面添加书签或以其他方式推荐给其他人可能不是一个明智的决定。请访问 https://svnbook.subversion.org.cn/ 获取此书的稳定版本。

基于路径的授权

Apache 和 svnserve 都能够为用户授予(或拒绝)权限。通常,这是针对整个仓库进行的:用户可以读取仓库(或不能),并且可以写入仓库(或不能)。

但是,也可以定义更细粒度的访问规则。一组用户可能被授权写入仓库中的某个目录,而其他用户则不能;另一个目录甚至可能只有少数几个特殊人员才能读取。甚至可以限制对每个文件的访问。

两个 Subversion 服务器都使用一个通用的文件格式来描述这些基于路径的访问规则。在本节中,我们将解释该文件格式,以及如何配置 Subversion 服务器以使用它来管理基于路径的授权。

基于路径的访问控制入门

Subversion 通过 mod_authz_svn 模块在 Apache 中提供基于路径的访问控制,该模块必须使用 LoadModule 指令在 httpd.conf 中加载,与加载 mod_dav_svn 本身的方式相同。要为您的仓库启用此模块的使用,您需要添加 AuthzSVNAccessFileAuthzSVNReposRelativeAccessFile 指令(同样在 httpd.conf 文件中),这些指令指向您自己的访问规则文件。(有关完整说明,请参阅 名为“按目录访问控制”的部分。)

要在 svnserve 中配置基于路径的授权,只需将 authz-db 配置变量(在您的 svnserve.conf 文件中)指向您的访问规则文件即可。

一旦您的服务器知道在哪里查找您的访问规则,就该定义这些规则了。

Subversion 访问文件的语法与 svnserve.conf 和运行时配置文件使用的熟悉语法相同。以井号 (#) 开头的行将被忽略。在最简单的形式中,每个部分都会命名一个版本化的路径,并且可选地命名包含该路径的仓库。换句话说,除了几个保留的部分之外,部分名称具有以下两种形式之一:[repos-name:path][path](当使用 AuthzSVNAccessFile 时)。如果您通过 AuthzSVNReposRelativeAccessFile 指令配置了每个仓库的访问文件,则应始终仅使用 [path] 形式。经过身份验证的用户名是每个部分中的选项名称,选项的值描述该用户对仓库路径的访问级别:r(只读)或 rw(读/写)。如果用户根本没有被提及,则不允许访问。

[Note] 注意

访问文件部分中使用的路径必须使用 Subversion 的 内部样式 指定,这主要意味着它们以 UTF-8 编码,并且使用正斜杠 (/) 字符作为目录分隔符(即使在 Windows 系统上也是如此)。还要注意,这些路径不使用任何字符转义机制(例如 URI 编码)——路径名中的空格应在访问文件部分名称中精确地表示为它们本身 ([repos-name:path with spaces],例如)。

以下是一个简单的示例,演示了访问配置的一部分,该配置授予 Sally 读取访问权限,并授予 Harry 读取/写入访问权限,用于仓库 calc 中的路径 /branches/calc/bug-142(及其所有子路径)。

[calc:/branches/calc/bug-142]
harry = rw
sally = r
[Warning] 警告

在 1.7 版本之前,Subversion 在访问控制方面以不区分大小写的方式处理仓库名称和路径,在与访问文件的内容进行比较之前,将其内部转换为小写。现在,它以区分大小写的方式进行这些比较。如果您从旧版本升级到 Subversion 1.7,则应检查您的访问文件以确保大小写正确。

授权子系统评估的仓库名称直接从仓库的路径派生。这在两种服务器选项之间是如何发生的有所不同。 mod_dav_svn 仅使用仓库根 URL 的基本名称 [71],而 svnserve 使用从服务根(由其 --root (-r) 命令行选项确定)到仓库的整个相对路径。

[Warning] 警告

每个 mod_dav_svnsvnserve 确定仓库名称的方式的不同,在尝试通过这两个服务器同时提供服务时可能会导致问题。自然地,管理员希望将两个服务器的配置都指向一个通用的访问文件。但是,要使此方法起作用,您必须确保文件的部分名称中的仓库名称部分与每个服务器对仓库名称的理解相兼容——例如,通过将 svnserve 的根配置为与 mod_dav_svn 的配置的 SVNParentPath 相同,或使用每个仓库不同的访问文件,以便部分名称不需要引用仓库本身。

如果您使用的是 SVNParentPath 指令,则必须在部分中指定仓库名称。如果您省略它们,则诸如 [/some/dir] 之类的部分将在 所有 仓库中匹配路径 /some/dir。但是,如果您使用的是 SVNPath 指令,则您可以在部分中仅提供路径——毕竟,只有一个仓库。

权限从路径的父目录继承。这意味着我们可以为 Sally 指定具有不同访问策略的子目录。让我们继续我们之前的示例,并授予 Sally 写入分支的子目录的访问权限,而她原本只能读取该分支。

[calc:/branches/calc/bug-142]
harry = rw
sally = r

# give sally write access only to the 'testing' subdir
[calc:/branches/calc/bug-142/testing]
sally = rw

现在,Sally 可以写入分支的 testing 子目录,但仍然只能读取其他部分。同时,Harry 继续对整个分支拥有完整的读/写访问权限。

还可以通过继承规则显式地拒绝某人的权限,方法是将用户名变量设置为 null。

[calc:/branches/calc/bug-142]
harry = rw
sally = r

[calc:/branches/calc/bug-142/secret]
harry =

在这个示例中,Harry 对整个 bug-142 树拥有读/写访问权限,但对其中的 secret 子目录完全没有访问权限。

[Tip] 提示

需要记住的是,最具体的路径始终优先匹配。服务器会尝试匹配路径本身,然后匹配路径的父路径,然后匹配该路径的父路径等等。最终效果是,在访问文件中提及特定路径将始终覆盖从父目录继承的任何权限。

类似地,指定仓库名称的部分优先于不指定仓库名称的部分:如果 [calc:/some/path][/some/path] 都存在,则对于 calc,将使用前者而忽略后者。

默认情况下,任何人都无法访问任何仓库。这意味着如果您从一个空文件开始,您可能需要授予至少所有用户对仓库根目录的读取权限。您可以通过使用星号变量 (*) 来做到这一点,这表示 所有用户

[/]
* = r

这是一种常见的设置;请注意,部分名称中没有提及仓库名称。这使得所有用户都对所有仓库具有世界可读权限。一旦所有用户都对仓库具有读取权限,您就可以在特定仓库内的特定子目录上授予特定用户显式的 rw 权限。

请注意,虽然所有前面的示例都使用目录,但这仅仅是因为在目录上定义访问规则是最常见的情况。您也可以类似地限制对文件路径的访问。

[calendar:/projects/calendar/manager.ics]
harry = rw
sally = r

访问控制组

访问文件还允许您定义整个用户组,就像 Unix /etc/group 文件一样。为此,请在访问文件中创建一个 groups 部分,然后在该部分中描述您的组:每个变量的名称定义组的名称,其值是属于该组的用户名列表,这些用户名之间用逗号分隔。

[groups]
calc-developers = harry, sally, joe
paint-developers = frank, sally, jane
everyone = harry, sally, joe, frank, jane

与用户一样,组也可以被授予访问控制权限。用 at符号 (@) 前缀来区分它们。

[calc:/projects/calc]
@calc-developers = rw

[paint:/projects/paint]
jane = r
@paint-developers = rw

另一个重要的事实是,组权限不会被单个用户的权限覆盖。相反,会授予所有匹配权限的 组合。在前面的例子中,Jane 是 paint-developers 组的成员,该组具有读写访问权限。结合 jane = r 规则,这仍然赋予 Jane 读写访问权限。组成员的权限只能扩展到组已经拥有的权限之外。限制属于组的用户比他们组的权限更少是不可能的。

组也可以被定义为包含其他组。

[groups]
calc-developers = harry, sally, joe
paint-developers = frank, sally, jane
everyone = @calc-developers, @paint-developers

用户名别名

一些身份验证系统期望并使用我们在这里描述的相对较短的用户名 - harrysallyjoe 等。但其他身份验证系统(例如使用 LDAP 存储或 SSL 客户端证书的系统)可能使用更复杂的用户名。例如,Harry 在受 LDAP 保护的系统中的用户名可能是 CN=Harold Hacker,OU=Engineers,DC=red-bean,DC=com。使用这样的用户名,访问文件可能会因易于打错的长用户名或模糊用户名而变得相当臃肿。

幸运的是,Subversion 1.5 在访问文件语法中引入了用户名别名。用户名别名允许您只需要在将更易于理解的别名分配给它的语句中键入一次正确的复杂用户名。

用户名别名在访问文件的特殊 aliases 部分中定义,该部分中的每个变量名定义一个别名,而这些变量的值携带正在被别名化的真实 Subversion 用户名。

[aliases]
harry = CN=Harold Hacker,OU=Engineers,DC=red-bean,DC=com
sally = CN=Sally Swatterbug,OU=Engineers,DC=red-bean,DC=com
joe = CN=Gerald I. Joseph,OU=Engineers,DC=red-bean,DC=com
…

一旦您定义了一组别名,您就可以在访问文件中的其他地方通过别名来引用用户,这些别名位于您原本可以使用其实际用户名的所有相同位置。只需在别名前面加上一个 & 符号,以将其与常规用户名区分开来。

[groups]
calc-developers = &harry, &sally, &joe
paint-developers = &frank, &sally, &jane
everyone = @calc-developers, @paint-developers

如果您的用户用户名经常更改,您可能也选择使用别名。这样做可以让您只需要在用户名发生更改时更新别名表,而不是对整个访问文件执行全局搜索和替换操作。

高级访问控制功能

从 Subversion 1.5 开始,访问文件语法还支持一些 魔术 令牌,以帮助您根据用户的身份验证类别进行规则分配。其中一个令牌是 $authenticated 令牌。在您原本会在授权规则中指定用户名、别名或组名称的地方使用此令牌,以声明授予使用任何用户名进行身份验证的任何用户的权限。类似地,使用 $anonymous 令牌,除了它匹配所有 使用用户名进行身份验证的人。

[calendar:/projects/calendar]
$anonymous = r
$authenticated = rw

访问文件语法魔术的另一个方便之处是使用波浪号 (~) 字符作为排除标记。在您的授权规则中,在用户名、别名、组名称或身份验证类别令牌前加上波浪号字符,会导致 Subversion 将规则应用于 匹配该规则的用户。虽然有点不必要的模糊,但以下块等同于上一个例子中的块。

[calendar:/projects/calendar]
~$authenticated = r
~$anonymous = rw

一个不太明显的例子可能是这样。

[groups]
calc-developers = &harry, &sally, &joe
calc-owners = &hewlett, &packard
calc = @calc-developers, @calc-owners

# Any calc participant has read-write access...
[calc:/projects/calc]
@calc = rw

# ...but only allow the owners to make and modify release tags.
[calc:/projects/calc/tags]
~@calc-owners = r

访问控制的一些注意事项

如果您使用 Apache 作为您的 Subversion 服务器,并且已经使存储库的某些子目录对某些用户不可读,那么您需要意识到 svn checkout 的可能非最优行为。

根据 Subversion 客户端使用的 HTTP 通信库,它可能会请求将签出或更新的整个有效负载在一个(通常很大)的响应中传递给主要的签出/更新请求。当这种情况发生时,这个单一请求是 Apache 要求用户身份验证的 唯一 机会。这有一些奇怪的副作用。例如,如果存储库的某个子目录只有用户 Sally 可以读,而用户 Harry 签出了父目录,他的客户端将以 Harry 的身份响应初始身份验证挑战。当服务器生成大型响应时,它无法在到达特殊子目录时重新发送身份验证挑战;因此子目录完全被跳过,而不是在适当的时候要求用户以 Sally 的身份重新进行身份验证。

以类似的方式,如果存储库的根目录是匿名世界可读的,则整个签出将无需身份验证完成 - 同样,跳过不可读的目录,而不是在中途请求身份验证。 [72]



[70] 本书中一个常见的主题!

[71] 通过 SVNReposName httpd.conf 指令配置的任何人类可读的存储库名称将被授权子系统忽略。您的访问控制文件部分必须通过前面描述的服务器敏感路径来引用存储库。

[72] 有关更多信息,请参阅博客文章 Authz and Anon Authn Agony,网址为 https://subversion.org.cn/blog/2007-03-27-authz-and-anon-authn-agony.html

TortoiseSVN 官方中文版 1.14.7 发布