svnserve,一个自定义的服务器

svnserve是一个轻型的服务器,可以同客户端通过在TCP/IP基础上的自定义有状态协议通讯,客户端通过使用开头为svn://或者svn+ssh://svnserve的URL来访问一个svnserve服务器。这一小节将会解释运行svnserve的不同方式,客户端怎样实现服务器的认证,怎样配置版本库恰当的访问控制。

调用服务器

有许多调用svnserve的方式,如果调用时没有参数,你只会看到一些帮助信息,然而,如果你计划使用inetd启动进程,你可以传递-i--inetd)选项:

$ svnserve -i
( success ( 1 2 ( ANONYMOUS ) ( edit-pipeline ) ) )

当用参数--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进程来做服务。

在一个Windows系统,有第三方工具可以将svnserve作为服务运行,请看Subversion的网站的工具列表。

svnserve的第二个选项是作为独立“守护”进程,为此要使用-d选项:

$ svnserve -d
$               # svnserve is now running, listening on port 3690

当以守护模式运行svnserve时,你可以使用--listen-port=--listen-host=选项来自定义“绑定”的端口和主机名。

也一直有第三种方式,使用-t选项的“管道模式”,这个模式假定一个分布式服务程序如RSHSSH已经验证了一个用户,并且以这个用户调用了一个私有svnserve进程,svnserve运作如常(通过stdinstdout通讯),并且可以设想通讯是自动转向到一种通道传递回客户端,当svnserve被这样的通道代理调用,确定认证用户对版本数据库有完全的读写权限,(见服务器和访问许可:一个警告。)这与本地用户通过file:///URl访问版本库同样重要。

一旦svnserve已经运行,它会将你系统中所有版本库发布到网络,一个客户端需要指定版本库在URL中的绝对路径,举个例子,如果一个版本库是位于/usr/local/repositories/project1,则一个客户端可以使用svn://host.example.com/usr/local/repositories/project1 来进行访问,为了提高安全性,你可以使用svnserve-r选项,这样会限制只输出指定路径下的版本库:

$ svnserve -d -r /usr/local/repositories
…

使用-r可以有效地改变文件系统的根位置,客户端可以使用去掉前半部分的路径,留下的要短一些的(更加有提示性)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的值可以是用户文件的绝对或相对路径,对许多管理员来说,把文件保存在版本库conf/下的svnserve.conf旁边是一个简单的方法。另一方面,可能你的多个版本库使用同一个用户文件,此时,这个文件应该在更公开的地方,版本库分享用户文件时必须配置为相同的域,因为用户列表本质上定义了一个认证域,无论这个文件在哪里,必须设置好文件的读写权限,如果你知道运行svnserve的用户,限定这个用户对这个文件有读权限是必须的。

设置访问控制

svnserve.conf有两个或多个参数需要设置:它们确定未认证(匿名)和认证用户可以做的事情,参数anon-accessauth-access可以设置为noneread或者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只能识别“整体”的访问控制,一个用户可以有全体的读/写权限,或者只读权限,或没有访问权限,没有对版本库具体路径访问的细节控制,很多项目和站点,这种 访问控制已经完全足够了,然而,如果你希望单个目录访问控制,你会需要使用包括mod_authz_svn(见“每目录访问控制”一节)的Apache,或者是使用pre-commit钩子脚本来控制写访问(见“钩子脚本”一节),Subversion的分发版本包含一个commit-access-control.pl和一个更加复杂的svnperms.py脚本可以作为pre-commit脚本使用。

SSH认证和授权

svnserve的内置认证会非常容易得到,因为它避免了创建真实的系统帐号,另一方面,一些管理员已经创建好了SSH认证框架,在这种情况下,所有的项目用户已经拥有了系统帐号和有能力“SSH到”服务器。

SSH与svnserve结合很简单,客户端只需要使用svn+ssh://的URL模式来连接:

$ whoami
harry

$ svn list svn+ssh://host.example.com/repos/project
harry@host.example.com's password:  *****

foo
bar
baz
…

在这个例子里,Subversion客户端会调用一个ssh进程,连接到host.example.com,使用用户harry认证,然后会有一个svnserve私有进程以用户harry运行。svnserve是以管道模式调用的(-t),它的网络协议是通过ssh封装的”,被管道代理的svnserve会知道程序是以用户harry运行的,如果客户执行一个提交,认证的用户名会作为版本的参数保存到新的修订本。

这里要理解的最重要的事情是Subversion客户端是连接到运行中的svnserve守护进程,这种访问方法不需要一个运行的守护进程,也不需要在必要时唤醒一个,它依赖于ssh来发起一个svnserve进程,然后网络断开后终止进程。

当使用svn+ssh://的URL访问版本库时,记住是ssh提示请求认证,而svn客户端程序。这意味着密码不会有自动缓存(见“客户端凭证缓存”一节),Subversion客户端通常会建立多个版本库的连接,但用户通常会因为密码缓存特性而没有注意到这一点,当使用svn+ssh://的URL时,用户会为ssh在每次建立连接时重复的询问密码感到讨厌,解决方案是用一个独立的SSH密码缓存工具,像类Unix系统的ssh-agent或者是Windows下的pageant

当在一个管道上运行时,认证通常是基于操作系统对版本库数据库文件的访问控制,这同Harry直接通过file:///的URL直接访问版本库非常类似,如果有多个系统用户要直接访问版本库,你会希望将他们放到一个常见的组里,你应该小心的使用umasks。(确定要阅读“支持多种版本库访问方法”一节)但是即使是在管道模式时,文件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会以-p 29934参数调用特定的SSH程序—对连接到非标准端口的程序非常有用。

第二点,它展示了怎样定义一个自定义的环境变量来覆盖管道程序中的名字,设置SVN_SSH环境变量是覆盖缺省的SSH管道的一种简便方法,但是如果你需要为多个服务器做出多个不同的覆盖,或许每一个都联系不同的端口或传递不同的SSH选项,你可以使用本例论述的机制。现在如果我们设置JOESSH环境变量,它的值会覆盖管道中的变量值—会执行$JOESSH而不是/opt/alternate/ssh -p 29934

SSH配置技巧

不仅仅是可以控制客户端调用ssh方式,也可以控制服务器中的sshd的行为方式,在本小节,我们会展示怎样控制sshd执行svnserve,包括如何让多个用户分享同一个系统帐户。

初始设置

作为开始,定位到你启动svnserve的帐号的主目录,确定这个账户已经安装了一套SSH公开/私有密钥对,用户可以通过公开密钥认证,因为所有如下的技巧围绕着使用SSHauthorized_keys文件,密码认证在这里不会工作。

如果这个文件还不存在,创建一个authorized_keys文件(在UNIX下通常是~/.ssh/authorized_keys),这个文件的每一行描述了一个允许连接的公钥,这些行通常是下面的形式:

  ssh-dsa AAAABtce9euch.... user@example.com

第一个字段描述了密钥的类型,第二个字段是未加密的密钥本身,第三个字段是注释。然而,这是一个很少人知道的事实,可以使用一个command来处理整行:

  command="program" ssh-dsa AAAABtce9euch.... user@example.com

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 harry@example.com
  command="svnserve -t --tunnel-user=sally" TYPE2 KEY2 sally@example.com

这个例子允许Harry和Sally通过公钥认证连接同一个的账户,每个人自定义的命令将会执行。--tunnel-user选项告诉svnserve -t命令采用命名的参数作为经过认证的用户,如果没有--tunnel-user,所有的提交会作为共享的系统帐户提交。

最后要小心:设定通过公钥共享账户进行用户访问时还会允许其它形式的SSH访问,即使你设置了authorized_keyscommand值,举个例子,用户仍然可以通过SSH得到shell访问,或者是通过服务器执行X11或者是端口转发。为了给用户尽可能少的访问权限,你或许希望在command命令之后指定一些限制选项:

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


[24] 见RFC 2195。