Git起步

我的学习路径:git basic, http webdav, git-svn, gitosis, gitweb,下面是一些学习记录。

http webdav方式访问

ubuntu下安装非常简单,用apt装上git-core包即可。git仓库的外部访问,首先就是最熟悉的用http协议走apache上的webdav服务,方便啊。git自身的身份认证和权限分配弱了一些,不过可以借助apache实现用户分配,passwd文件直接用svn的也没问题。apache配置示例如下:

Alias /git "/big2/git"
<Location /git/test.git>
    DAV on
    AuthType Basic
    AuthName "Test git freely"
    AuthUserFile "/big2/svn/svnpasswd"
    #Require user Fwolf
    Require valid-user
</Location>

但这样远不如使用ssh/git协议访问仓库速度快和稳定,但更适合公开/对外发布。另外和svn的webdav方式不同,git webdav是一种dumb transport,不仅慢而且比较傻,只能进行简单的存、取文件操作,所以hooks是不起作用的(很多文章中说的提交后要在hooks/post-update里启用git-update-server-info,指的是git/ssh方式更新后,为webdav对外发布作准备)不会自动进行和git相关的其它处理。

最好用的还是gitosis,后面会提到。有些内容先以webdav方式下来记,使用的时候根据不同的方式转换用户角色即可。

创建服务端的仓库

其实和svn创建仓库也是比较类似的,找一空目录,比如叫test.git,执行:

$ git --bare init
$ sudo chown www-data:www-data * -R

客户端就可以下载了,但还有些问题:

$ git clone https://domain.tld/git/test.git gittest
Initialized empty Git repository in /home/fwolf/dev/gittest/.git/

error: server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt

首先遇到的是证书错误,git使用curl,所以证书的处理和svn也不一样,用firefox浏览git网站,在Page Info->View Certificate->Details中把网站的根证书(Issued By/root)导出为一个pem文件,然后将这个pem文件的内容添加到/etc/ssl/certs/ca-certificates.crt的末尾就可以了。接着遇到的是401身份验证错误:

error: The requested URL returned error: 401

git不像svn会自动提示用户名/密码,因为它用的是webdav的原生功能,所以需要在$HOME下创建文件.netrc(记得将权限修改为600):

machine domain.tld
login Fwolf
password your_passwd

最后,还是仓库本身不完善的问题:

warning: remote HEAD refers to nonexistent ref, unable to checkout.

在服务器上仓库目录,执行$ git update-server-info并再次chown到www-data就行了。初次push得指明服务器上的origin对应本地master分支/branch:

$ git push origin master

而下载之前,还要设置下载后放到那个分支、与哪个分支的现有内容进行merge:

$ git config branch.master.remote origin
$ git config branch.master.merge master
$ git pull  # 注意如果服务器是新建的空repo,得随便push点东西才好pull

gitosis

还是通过apt安装,gitosis使用SSH key来认证用户,但用户不需要在主机上开账号,而是公用主机上的一个受限账号。首先创建这个账号(名称可变):

sudo adduser \
    --system \
    --shell /bin/sh \
    --gecos 'git user' \
    --group \
    --disabled-password \
    --home /big2/gitosis \
    git

然后生成自己用户的ssh密钥,可以在git用户的$HOME下操作:

ssh-keygen -t rsa

不要密码,可以起名叫git_rsa[.pub],然后用公钥来初始化gitosis的目录:

$ sudo -H -u git gitosis-init < /home/fwolf/.ssh/git_rsa.pub 
Initialized empty Git repository in /big2/gitosis/repositories/gitosis-admin.git/
Reinitialized existing Git repository in /big2/gitosis/repositories/gitosis-admin.git/

可以看出gitosis自己也在用git仓库的形式存储东西,传递公钥进去是为了配置自动登录,将公钥写入git用户的$HOME/.ssh/authorized_keys。接下来就启用gitosis仓库的post-update hook:

$ sudo chmod 755 repositories/gitosis-admin.git/hooks/post-update

其实用apt安装的gitosis,这个post-update早已指向其它文件并具有x属性了。

一点提示:把gitosis的repositories和自己原先的git repo目录弄到一起,以后方便,因为gitosis自己的管理也是要通过git来提交的,在客户端先修改$home/.ssh/config

Host git.domain.tld
Compression yes
IdentityFile ~/.ssh/git_rsa # 注意这个是私钥,不是pub公钥

然后从客户端clone gitosis的配置文档:

$ git clone git@git.domain.tld:gitosis-admin.git

注意实际上这个gitosis-admin.git应该是在git用户的$HOME下的repositories目录下,但这么写才能下载。clone得到一个配置文件gitosis.conf和目录keydir/,对了,git配置的修改、上传也是通过git提交,并且由于刚才设置的post-update hook,立即生效。所以现在就能够远程修改、提交配置了。下面是一份比较完整的自说明的gitosis.conf

# example: http://eagain.net/gitweb/?p=gitosis.git;a=blob;f=example.conf
[gitosis]

## Allow gitweb to show all known repositories. If you want gitweb,
## you need either this or a [repo foo] section for each repository
## you want visible in gitweb.
gitweb = no

[group gitosis-admin]
writable = gitosis-admin
# member is key file name, without (.pub)
members = fwolf

# user group
[group all]
# members = jdoe alice bob @anothergroup
members = fwolf
# this user group is writeable to ... repo, eg: test.git.
# writeable = foo bar baz/thud
writeable = test
#readonly = somerepo

[repo test]
## Allow gitweb to show this repository.
gitweb = yes
## Oneline description of the project, mostly for gitweb.
description = Fwolf's test git repository
## Owner of this repository. Used in gitweb list of projects.
owner = Fwolf
## Allow git-daemon to publish this repository.
#daemon = yes
daemon = no

平时就是为其它用户添加SSH key,然后用git@git.domain.tld:repo.git来连接服务器,gitosis会自动比对服务器上的pub key,判断出用户是谁。

gitweb

用apt安装,得到/usr/lib/cgi-bin/gitweb.cgi,由于cgi-bin已经由ubuntu设置好了,apache只要设置一下资源文件路径即可:

Alias /gitweb "/usr/share/gitweb"

然后修改/etc/gitweb.conf

# path to git projects (<project>.git)
$projectroot = "/big2/git";

$site_name = "Fwolf's Git Repositories";
@git_base_url_list = ("ssh://git\@git.domain.tld");

# directory to use for temp files
$git_temp = "/tmp";

# target of the home link on top of all pages
#$home_link = $my_uri || "/";

# html text to include at home page
$home_text = "indextext.html";

# file with project list; by default, simply scan the projectroot dir.
$projects_list = "/big2/gitosis/gitosis/projects.list";
# Avoid use guess repo's address and edit url manually
$strict_export = 1;

# stylesheet to use
$stylesheet = "/gitweb/gitweb.css";
# logo to use
$logo = "/gitweb/git-logo.png";
# the 'favicon'
$favicon = "/gitweb/git-favicon.png";

重启apache后就能够用http://git.domain.tld/cgi-bin/gitweb.cgi访问了。

git-svn混用

首先还要安装git-svn包,git和svn混用的好处就是既可以用git进行本地提交和灵活的分支,又能够用svn实现集中管理和分发,缺点我想可能就是svn的revision号在git里没有了,另外git有些太灵活,不是很好入门。

首先导入svn的现有项目到一个空目录:

$ git svn clone --username your-name -s https://domain.tld/svn/repo
# older versions of git: replace "-s" with "-Ttrunk -bbranches -ttags"

就这么简单,很多工作git-svn都代劳了,剩下就当正常的git目录使用即可,比如为他们指定git仓库的位置,并下载文件:

$ git config remote.origin.url https://domain.tld/git/test.git
$ git config remote.origin.fetch +refs/heads/*:refs/remotes/origin/*
$ git config branch.master.remote origin
$ git config branch.master.merge refs/heads/master
$ git checkout master   #回到master分支
$ git pull

向svn仓库提交:

$ git svn dcommit

从svn仓库接受更新:

$ git svn rebase

在git-svn的使用中还发现,如果在机器A上使用git-svn,但是在另外一台机器B上用纯git进行了push,那么B上的commit是不会被git-svn认出来并dcommit的,只有pull下来,然后再假commit一下才能让git-svn看到。

仓库地址变更

服务器上直接修改仓库目录名和apache设置,客户端上:

$ git config remote.origin.url https://domain.tld/git/testnew.git
$ git pull  # 这个必须要做,不然不能push
Merge made by recursive.
$ git push

git-svn方式下变更svn的地址

首先要修改所有commit中的地址:

$ git filter-branch --msg-filter 'sed "s/domain_old\/trunk/domain_new\/trunk/g"' $(cat .git/packed-refs | awk '// {print $2}' | grep -v 'pack-refs')

然后删掉缓存,修改地址,重新下载:

$ rm -rf .git/svn
# 编辑`.git/config`修改`[svn-remote "svn"]`的url。
$ git svn rebase (--all)

我还曾经遇到过这样的错误:

$ git svn rebase
Unable to determine upstream SVN information from working tree history

不太好处理,这里有些参考信息,我是在一个repo里pull另外一个仓库的内容了,照下面方式处理也不管用:

  • 从svn里重新clone出一份
  • git里创建个branch保存一下
  • git连接上服务器,pull
  • git checkout 到刚才创建的branch
  • git merge –no-ff master
  • git svn rebase/dcommit

效果不好,不仅产生大量的conflict,而且还是只能在新创建的branch上rebase/dcommit。大概是出现了两“棵”树,怎么也捏不到一起,最后只好重建仓库。

其它使用技巧/tips

  • 回上个版本 $ git reset --hard HEAD^
  • 得到一个文件5个版本以前的状态 $ git checkout HEAD~5 foo.c
  • 由于git是分布式的scm,所以$Id被彻底消灭掉了。
  • 不像svn,git能够记录文件的执行属性。
  • 为显示加上颜色 $ git config color.(branch|diff|interactive|status) auto,或者$ git config color.ui true
  • hooks不算仓库的内容,每个repo的hooks可以不同。
  • 当心svn的hooks,如果git里已经commit,却因为svn的hooks使git-svn的dcommit失败,很麻烦。
  • .gitignore里可以用config.php忽略文件,并且用!mt/config.php指明个别文件还是要归入scm管理的。
  • 从现有的客户端目录中生成仓库目录 $ git clone --bare dev_dir repo_dir 每个客户端都是一个完整备份,方便。
  • 用于ignore文件,.gitignore是要提交的版本,.git/info/exclude则是只在你本地生效的版本,不提交。
  • 仅给ssh登录的开发者push/pull的访问权限,将其shell设置为/usr/bin/git-shell(未测试).
  • info/allowed-users可以控制用户对branch/tag的访问权限(未测试)。
  • git --bare init --shared=group可以让仓库被同group的用户共享,不过有gitosis就不用这个了,我没测试。

参考

Update @ 2009-03-27

使用git-svn的时候,有时候会出现git和svn树不太同步的情况,在进行svn rebase的时候总是重新merge文件,这种现象多是由于以前没有正确rebase引起的,而rebase不成功的一种原因就是git的fast forward。简单理解一下,git是按照树形结构排下来的,如果遇到前后两个节点被判定为“重复”的,git就聪明的作了个fast forward,直接使用后者了。这在git里没事,因为都是一颗树,而对于svn就不行了,它还记录着要从前面那个节点开始呢,所以合并的时候就出现了两个源头的情况,自然会产生问题。所以大体上向svn更新的顺序应该是这样的:

  • git push # git更新
  • git pull –no-ff # 此处的–no-ff最为重要,平时可以不用,这里要用
  • git svn rebase # 准备提交到svn之前先和当前的树合并
  • git svn dcommit
  • git pull –no-ff
  • # 后面就是检查了,前面操作正常的话不会产生新的有效更新了
  • git push
  • git svn dcommit
  • git svn rebase
  • git pull
  • git push

附一张比较典型的出错后来回折腾的结果图: git-svn rebase dcommit error

参考 @ 2009-03-29

有了git track以后,新建要和服务器同步的branch:

$ git branch fwolf
Branch fwolf set up to track local branch refs/heads/master.

$ git track fwolf origin
tracking origin/fwolf

$ git checkout fwolf
Switched to branch "fwolf"

$ git push origin fwolf
Total 0 (delta 0), reused 0 (delta 0)
To git@domain.tld:repo.git
 * [new branch]      fwolf -> fwolf

而从其它客户端下载这个branch就简单多了:

$ git branch --track fwolf origin/fwolf
Branch fwolf set up to track remote branch refs/remotes/origin/fwolf.

掌握原理,不用git track也一样可以实现的。

Update @ 2009-04-18

MediaTemple使用的是CentOS,安装git也很简单:

$ rpm -Uvh http://mirror.centos.org/centos/5/os/i386/CentOS/python-iniparse-0.2.3-4.el5.noarch.rpm
$ rpm -Uvh http://mirror.centos.org/centos/5/os/i386/CentOS/yum-3.2.19-18.el5.centos.noarch.rpm http://mirror.centos.org/centos/5/os/i386/CentOS/yum-fastestmirror-1.1.16-13.el5.centos.noarch.rpm  # 版本号可能会有变化
$ rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-2.noarch.rpm
$ yum -y update
$ yum install git

参考:

Update @ 2009-04-27

升级到ubuntu 9.04 jaunty后,客户端push时会遇到错误:

$ git push
/usr/bin/gitosis-serve:5: UserWarning: Unbuilt egg for ClientCookie [unknown version] (/usr/lib/python2.6/dist-packages)
  from pkg_resources import load_entry_point
Traceback (most recent call last):
  File "/usr/bin/gitosis-serve", line 5, in <module>
    from pkg_resources import load_entry_point
  File "/usr/lib/python2.6/dist-packages/pkg_resources.py", line 2562, in <module>  
    working_set.require(__requires__)
  File "/usr/lib/python2.6/dist-packages/pkg_resources.py", line 626, in require
    needed = self.resolve(parse_requirements(requirements))
  File "/usr/lib/python2.6/dist-packages/pkg_resources.py", line 524, in resolve
    raise DistributionNotFound(req)  # XXX put more info here
pkg_resources.DistributionNotFound: gitosis==0.2
fatal: The remote end hung up unexpectedly

半天查不出原因,只是怀疑和python的版本有关系(2.5/2.6),因为gitosis在debian包中的文件,显示应该是对应python 2.5的,不过,试着自己用python(2.6)重新下载安装了一遍gitosis,好了:

	$ git clone git://eagain.net/gitosis
	$ cd gitosis
	$ sudo python setup.py install

不懂Python太吃亏了呀。另外aptitude reinstall gitosis是没用的。

Update @ 2009-05-15

Tsung那里学来一招,可以更改 Git 默认的信息存放目录 .git :

修改 Shell 變數 GIT_DIR => "export GIT_DIR=.test"

Update @ 2010-08-03

升级到 Ubuntu 10.04 Lucid 以后,gitosis 自带的 hook 文件 post-update,就是修改权限等等 commit 后,自动出发 gitosis 更新相关配置的钩子,原来 ln 到的 /usr/share/python-support/gitosis/gitosis-0.2-py2.5.egg/gitosis/templates/admin/hooks/post-update 无效了,更换成 /usr/share/pyshared/gitosis/templates/admin/hooks/post-update 就可以了,参见Bug报告。挺让人无语的bug,我折腾了半天还以为是权限设置出了什么问题呢。

由一个错误学到的一些php安全配置问题

错误

在MediaTemple主机从(dv)3.0升级到3.5之后,我遇到的第一个问题就是一个莫名奇妙的php错误:

[Sat Jul 12 04:51:27 2008] [error] [client 121.42.26.81] PHP Warning:  require_once(/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php) [<a href='function.require-once'>function.require-once</a>]: failed to open stream: Operation not permitted in /var/www/vhosts/fwolf.com/httpdocs/info.php on line 4
[Sat Jul 12 04:51:27 2008] [error] [client 121.42.26.81] PHP Fatal error:  require_once() [<a href='function.require'>function.require</a>]: Failed opening required '/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php' (include_path='.:/var/www/vhosts/fwolf.com/include') in /var/www/vhosts/fwolf.com/httpdocs/info.php on line 4

因为是migration过来的,所以require的这个文件肯定存在,并且apache用户也的确有权访问,那问题出在哪里呢?

如果换一种方式,require直接使用文件的全路径,错误信息就更清楚了:

<b>Warning</b>:  require_once() [<a href='function.require-once'>function.require-once</a>]: open_basedir restriction in effect. File(/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php) is not within the allowed path(s): (/var/www/vhosts/fwolf.com/httpdocs:/tmp) in <b>/var/www/vhosts/fwolf.com/httpdocs/info.php</b> on line <b>4</b><br />
<br />
<b>Warning</b>:  require_once(/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php) [<a href='function.require-once'>function.require-once</a>]: failed to open stream: Operation not permitted in <b>/var/www/vhosts/fwolf.com/httpdocs/info.php</b> on line <b>4</b><br />

<br />
<b>Fatal error</b>:  require_once() [<a href='function.require'>function.require</a>]: Failed opening required '/var/www/vhosts/fwolf.com/include/fwolflib/func/config.php' (include_path='.:/var/www/vhosts/fwolf.com/include') in <b>/var/www/vhosts/fwolf.com/httpdocs/info.php</b> on line <b>4</b><br />

原来是有个open_basedir限制,找了一下是在$HOME/conf/httpd.include里,这个文件是由plesk自动维护的:

<IfModule sapi_apache2.c>
    php_admin_flag engine on
    php_admin_flag safe_mode off
    php_admin_value open_basedir "/var/www/vhosts/fwolf.com/httpdocs:/tmp"
</IfModule>
<IfModule mod_php5.c>
    php_admin_flag engine on
    php_admin_flag safe_mode off
    php_admin_value open_basedir "/var/www/vhosts/fwolf.com/httpdocs:/tmp"
</IfModule>

看到没,只允许包含httpdocs下的文件。open_basedir影响的范围是fopen, require, include之类的函数,在一定程度上加强了安全防护。

问题

但open_basedir也有局限性,它不会影响那些执行系统命令的函数,比如exec, system,如果我想偷主机上另外一位同学的文件(内容),也不见得非要去用require包含过来或者种个hack过去,直接system('cat /path/to/file')不是更省事么?

system函数有时候还是能派上正当用场的,直接禁用不是什么好办法,现在流行chroot,就是用户的/就是自己的$HOME,压根儿就访问不到别人的文件,什么open_basedir, exec, dl都不用禁用,我觉得这才是安全和方便的最佳接合点。

以前用(dv)3.0的时候,手工配置使用fastcgi的php5就是这样,每个用户的cgi用自己的身份,在自己的chroot环境下运行。

不过plesk现在的版本8、将来的版本9都没有要直接使用fastcgi解析php的打算,在“更远的计划里”,才可怜兮兮的有这么一句:

Use PHP via FastCGI rather than Apache module

参见:Parallels Summit 2008 – Day 1,所以就只能自己动手了。

fastcgi

很走运,找到了一个2天前刚刚出炉的脚本:Script for using php-cgi instead of mod_php,专门针对plesk,禁用掉mod_php,然后用它来配置fcgi解析。

使用环境:Plesk 8.X on Centos 5.X,依赖:

  • 禁用mod_php,开启mod_fcgid
  • python-curl, PyXML
  • php开启–enable-fastcgi, –enable-force-cgi-redirect

文件需要解压到/root/bin/下,自己一个子目录,幸好我也是用这个bin目录的。

然后在Server -> Control Panel -> Event Manager里添加自定义事件,在增加、修改、删除domain的时候,自动调用这个脚本。(subdomain的删除没有包含,手工删除文件就可以了)设置好大概就是这个样子:

using php-cgi instead of mod_php, plesk event manager

还要把/etc/httpd/conf.d/php.conf删得只剩一行:

LoadModule php5_module modules/libphp5.so

并且在/etc/httpd/conf.d/fcgid.conf里加一句:

PHP_Fix_Pathinfo_Enable 1

不过,这种方法作到一半我就没有继续了,因为我想起来前几天一位朋友和我提到过的suPHP。

suPHP

个人感觉suPHP是最“正统”的解决方案,它是以文件属主用户的身份来运行,正好使用各个用户的权限实现访问限制。

没找到centos的mod_suphp包,只好下载suphp 0.6.3源码自己编译,不过之前要先修改src/apache2/mod_suphp.c,在324行替换掉两行内容:

//AP_INIT_ITERATE("suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, ACCESS_CONF, "Tells mod_suphp to handle these MIME-types"),
AP_INIT_ITERATE("suPHP_AddHandler", suphp_handle_cmd_add_handler, NULL, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp to handle these MIME-types"),
//AP_INIT_ITERATE("suPHP_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, ACCESS_CONF, "Tells mod_suphp not to handle these MIME-types"),
AP_INIT_ITERATE("suPHP_RemoveHandler", suphp_handle_cmd_remove_handler, NULL, RSRC_CONF | ACCESS_CONF, "Tells mod_suphp not to handle these MIME-types"),

然后就是编译安装那三板斧:

# ./configure\
--with-apxs=/usr/sbin/apxs\
--with-php=/usr/bin/php-cgi\
--with-logfile=/var/log/suphp.log\
--with-min-uid=10000\
--with-min-gid=10000\
--with-apache-user=apache\
--with-apr=/usr/bin/apr-1-config\
--with-setid-mode=owner\
--prefix=/usr\
--sysconfdir=/etc
# make
# make install

/etc/httpd/conf/httpd.conf中加入一句(这一句也可以放到后面的suphp.conf中):

LoadModule suphp_module modules/mod_suphp.so

关闭safe_mode,并且注释掉下面两句:

safe_mode = Off
#AddType application/x-httpd-php .php
#AddType application/x-httpd-php-source .phps

创建suphp的conf文件,使用源码中的conf文件模板:

# cp doc/suphp.conf-example /etc/httpd/conf.d/suphp.conf

修改之:

<Directory /var/www/vhosts>
    RemoveHandler x-httpd-php
#   php_admin_value engine off
    AddHandler x-httpd-php .php .php3 .php4 .php5
    suPHP_AddHandler x-httpd-php
    suPHP_Engine On
    suPHP_ConfigPath /etc/php.ini
</Directory>

禁用mod_php,把php.conf文件换一个扩展名就行了:

# cd /etc/httpd/conf.d
# mv php.conf php.conf.bak

创建suPHP的配置文件/etc/suphp.conf,这个文件和用于apache配置的conf是不一样的,其内容如下,可根据具体环境设定参数:

[global]
;Path to logfile
logfile=/var/log/suphp.log

;Loglevel
;loglevel=info
;info, warn, error
loglevel=warn

;User Apache is running as
;webserver_user=wwwrun
webserver_user=apache

;Path all scripts have to be in
docroot=/var/www/vhosts/

;Path to chroot() to before executing script
;chroot=/mychroot

; Security options
;allow_file_group_writeable=false
allow_file_group_writeable=true
;allow_file_others_writeable=false
allow_file_others_writeable=true
;allow_directory_group_writeable=false
allow_directory_group_writeable=true
;allow_directory_others_writeable=false
allow_directory_others_writeable=true

;Check wheter script is within DOCUMENT_ROOT
check_vhost_docroot=true

;Send minor error messages to browser
;errors_to_browser=false
errors_to_browser=true

;PATH environment variable
env_path=/bin:/usr/bin

;Umask to set, specify in octal notation
;umask=0077
umask=0022

; Minimum UID
;min_uid=100
min_uid=10000

; Minimum GID
;min_gid=100
; Consider of psacln, psaserv
min_gid=200

; Use correct permissions for mod_userdir sites
;handle_userdir=true


[handlers]
;Handler for php-scripts
;x-httpd-php=php:/usr/bin/php
x-httpd-php=php:/usr/bin/php-cgi

;Handler for CGI-scripts
x-suphp-cgi=execute:!self

现在,重启apache,就可以啦!如果发现返回空页面,并且错误log中有如下内容:

Premature end of script headers:

那有可能是因为你把cli模式的php可执行文件拿过来当cgi模式的用了,注意他们的区别:

# php -v
PHP 5.2.6 (cli) (built: May  2 2008 16:06:40) 

# php-cgi -v
PHP 5.2.6 (cgi-fcgi) (built: May  2 2008 16:01:17)

把正确的cgi模式php执行文件设定到/etc/suphp.conf中即可。

chroot的疑惑

由于以前为了安全,ssh权限都是限定在chroot环境下,这样用户无法访问自己$HOME之外的内容。使用了suPHP之后,虽然php文件是以用户身份运行的,但却不是chroot的环境。也就是说,“理论上”在php文件执行的时候,可以访问其他用户的文件,这不也是个安全隐患么?

为了这个问题,我翻阅了好多资料,却发现很少人提起这个东西,suPHP安装不复杂,介绍的也不少,就是没有和chroot搭配的,倒是有人提出和fastcgi搭配使用。后来和michael沟通后才突然醒悟,suPHP的伪装身份和chroot是两种机制,之间没有什么联系,所以也就不存在什么配套使用的问题。至于不想让用户访问别人的文件,完全可以通过设定文件权限来实现嘛,不过还是要在安全方面比以前更加留心:

  • $HOME下系统自动创建的目录,一般属主都是user:psaserv或者root:root,有些对所有人都有rx权限(755),有些则是750权限,私密文件不要往755权限的目录下放。这些目录一般不宜改为750权限,因为有些文件是其他系统服务需要读取的。
  • $HOME下httpdocs, private等目录默认就是禁止所有人访问的,保持这样不要更改,并且httpdocs下的文件你就是搞成777权限,别人也访问不到。
  • 用户自建的文件、目录一般为user:psacln权限,主机上所有host用户所属组都是psacln,所以如果不想让别人访问,又没有上级目录的权限限制的话,一定调整为700权限。
  • 为了使用更方便,可以把$HOME目录的属主设为用户本身,比如chown fwolf:psaserv /var/www/vhosts/fwolf.com,不过这就需要一个个的单独开通了。
  • 如果发现其他系统文件中有泄密的,或者其他用户没有设置好权限,存在安全隐患,请及时告诉我或者相应用户,这样我们才是和谐的一家人嘛 🙂

取消chroot,还有一个好处就是用户几乎能够使用主机上的所有命令了,不像以前那样用哪个就需要把哪个设置到chroot的jail中,方便多了。

chroot的取消不是自动的,我已经给所有用户加上了可指定/bin/bash作为登录shell的权限,用户在plesk的站点设置中,把ssh用户的登录更换为/bin/bash即可,当然如果对安全没有信心,觉得chroot也够用的用户可以保留。

其他

suphp比suexec(就是原来dv3.0升php5的方法)要快一点;比suphp更快的还有suphp_mod_php;再快一些的是mpm-peruser,不过安装配置的麻烦程度也随之递增。

相比而言,suPHP速度还算可以接受(对于负载不是很大的站),配置方便,不用修改每个virtualhost的参数(就是$HOME/conf/vhost.conf),直接改apache的总conf就ok了,当然也比上面fastcgi方式下用event触发脚本来实现更加简洁。

参考