PHP-FPM之Chroot执行环境详解
《PHP-FPM之Chroot执行环境详解》要点: PHP应用在PHP-FPM中设立chroot,有很好的隔离作用,提高系统平安性,但是要想建立一个合理的PHP-FPM Chroot环境难度有点大,比用debootstrap等工具建立还要麻烦,下面通过参考相关资料,把PHP-FPM之Chroot执行环境整理出来,分享给大家. 本文以Ubuntu 14.04.2为例,php-fpm使用的是 ppa:ondrej/php5-5.6 提供的PHP5.6版本,跟系统自带以及Debian系统的php-fpm和系统目录结构应该是一致的.CentOS请自行调整. php-fpm的chroot环境配置和所使用的服务器前端没有关联,也不强求Apache/Nginx进行chroot.当然那样更平安――也更复杂. 1.建立目录结构 chroot的目录选择为 /var/www/chroot,其中页面文件放置在 /var/www/chroot/public . 执行下面的命令建立基本的目录结构: bash mkdir -p /var/www/chroot/ cd /var/www/chroot mkdir -p public bin dev tmp usr/sbin/ usr/share/zoneinfo/ var/run/nscd/ var/lib/php5/sessions var/www cp -a /dev/zero /dev/urandom /dev/null dev/ #注3 chmod --reference=/tmp tmp/ chmod --reference=/var/lib/php5/sessions var/lib/php5/sessions #注4 chown -R root:root . #注2 chown -R www-data:www-data public/ #注2 cd var/www ln -s ../.. chroot #注1 下面是此时目录结构,之后还会添加一些新的东西: /var/www/chroot/ ├── bin ├── dev │ ├── null │ ├── urandom │ └── zero ├── public ├── tmp ├── usr │ ├── sbin │ └── share │ └── zoneinfo └── var ├── lib │ └── php5 │ └── sessions ├── run │ └── nscd └── www └── chroot -> ../.. #注1 注1:这个软连接用于解决Apache/nginx传给php-fpm的 SCRIPT_FILENAME 在进入chroot后找不到文件(拜访php页面返回"File not found")的问题. 以nginx为例,通常设置 SCRIPT_FILENAME 为 $document_root$fastcgi_script_name,传给php-fpm的脚本路径就是 /var/www/chroot/public/index.php .而由于php-fpm处在chroot环境下,所以它实际试图去拜访的路径就变成了 /var/www/chroot + /var/www/chroot/public/index.php 当然是不存在的. 所以使用一个软连接把chroot环境下的 /var/www/chroot 链接到根目录,就能够正常拜访脚本了. 当然也可以将 SCRIPT_FILENAME 设置成 /public$fastcgi_script_name .但是这样硬编码不利于配置的迁移,仅能用于chroot的环境,切换回非chroot环境的话还必要修改配置.所以不建议这么做.(顺便说一句,有很多老教程里也不使用 $document_root,直接硬编码根目录,当然也是不可取的) 注2:chroot环境并不是100%平安的.由于php-fpm在chroot环境中的执行权限是www-data,仍然建议把非必要的目录的拥有者设置为root来减少不必要的访问权限.chroot不等于平安,参考 chroot最佳实践 中列出的一些原则.从更平安的角度上讲之后最好也将bin、lib、sbin等目录的读写权限去掉,只留可执行权限,不过也没大差别了…… 注3:cp -a除了拷贝文件内容外也会复制文件的权限、模式等信息,可以很便利的直接拿来拷贝zero、urandom和null这三个关键的设备文件.mknod似乎是更为稳妥的方式,不过cp -a我使用起来似乎也没问题. 注4:chmod --reference=XXX会参考XXX的权限设置后面的权限.tmp就不提了,关键是后面的 var/lib/php5/sessions 是php存放session文件的目录,必要让www-data有读写的权限.建议设置完之后再看一眼.当然后面会有测试. 2.PHP-FPM的配置 建立一个新的php-fpm的执行pool来搭建chroot环境.并不建议直接修改php-fpm.conf,因为这样是全局生效的,如果有多个php站点的话会共用一套chroot环境. 其实很多php-fpm的教程都忽略了php-fpm的pool的配置,导致很多人一台服务器上所有站点都共用一套配置,尤其是共用一套php.ini的配置,实际上是不合理的.应当根据站点的需求单独建立pool并在其中调整参数. 在 /etc/php5/fpm/pool.d/ 下新建 chroot.conf (注意必需以.conf结尾,才能被php-fpm.conf调用): [chroot] user = www-data group = www-data listen = /var/run/php-chroot.sock listen.owner = www-data listen.group = www-data pm = dynamic pm.max_children = 5 pm.start_servers = 1 pm.min_spare_servers = 1 pm.max_spare_servers = 3 chroot = /var/www/chroot chdir = /public ;security.limit_extensions = .php php_flag[display_errors] = on php_value[date.timezone] = Asia/Hong_Kong ;php_admin_value[session.gc_probability] = 1 ;php_admin_value[open_basedir] = "/tmp/:/public/:/var/www/chroot/public/" 前面的参数都比拟熟悉了.只需要简单的设置chroot为配置好的环境根目录就可以开启chroot了.通过执行 php5-fpm -t 测试一下之后,用 service php5-fpm reload 即可启用新的pool.当然Apache/nginx对应的配置中要设置好后端. 提一下最后几行.倒数第四行打开了 display_errors,以便之后对chroot下的php的功能进行测试,测试完了记得注释掉. 设置 session.gc_probability 允许php进程自行对session进行删除回收.正常情况下session是由php添加的cron任务清理的,但是似乎php不会自动清理chroot环境下的session.当然也可以自己在cron.d下添加自动执行的脚原来清理,就不用开启这个选项了. 3.修复Chroot环境下PHP的各项功能 在/var/www/chroot/public下新建一个test.php,写入以下内容:
代码如下:
php <?php session_start(); header( "Content-Type: text/plain" ); echo( gethostbyname( "localhost" )."n" ); print_r( getdate() ); mail( "your@address","subject","message" ); 这里主要测试的功能是:session、DNS解析、时间和日期、邮件mail()函数. 拜访上面的测试页面,提示No such directory or file或者Permission denied说明session配置不正确. gethostbyname 返回的不是127.0.0.1或者::1,则说明DNS解析没有生效.提示 timezone database is corrupt 之类的,说明时间和日期有错误.mail()也会有各种错误提示. session就不提了,设置好目录权限就没有问题.主要处理一下后面三个问题,也是php的chroot环境主要必要处理的内容. 3.1 域名解析/时区等问题 mail()的解决办法大同小异,放后面谈.前面的域名解析等问题这里我介绍两种解决办法.办法1是参考Kienzl的简便办法,办法2是大部分教程采用的办法. 办法1:使用nscd nscd是(e)glibc的“Name Service Caching Daemon”.除了处理gethostbyname()这样的函数外,也处理getpwnam()等需要拜访/etc/passwd的函数.(e)glibc拜访nscd的unix socket,/var/run/nscd/socket 来通过nscd获取这些内容,如果不能连接到nscd则转而自行进行解析. 也就是说,只要装好nscd,并且让chroot环境里的程序能够访问到socket连接上nscd,就可以把chroot环境内的解析哀求转由chroot外顺利进行了.由于/var/run一般是tmpfs,硬链接无法跨文件系统使用,所以可以使用 mount -bind 来把/var/run/nscd目录mount到chroot环境中同样的位置去即可. 同样的道理,用 mount -bind 把/usr/share/zoneinfo目录mount到chroot环境里,配合在php-fpm的pool里设置date.timezone就可以非常直接而暴力的办理时区问题. 先执行 apt-get install nscd 安装nscd,然后为了能够让 mount -bind 自动执行,把下面的脚本存为 /etc/init.d/php-chroot bash #!/bin/sh ### BEGIN INIT INFO # Provides: php5-fpm-chroot-setup # Required-Start: nscd # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Bind-mounts needed sockets and data into a php-fpm-chroot ### END INIT INFO CHROOT=/var/www/chroot DIRS="/var/run/nscd /usr/share/zoneinfo" case "$1" in start) $0 stop 2>/dev/null for d in $DIRS; do mkdir -p "${CHROOT}${d}" mount --bind -o ro "${d}" "${CHROOT}${d}" done ;; stop) for d in $DIRS; do umount "${CHROOT}${d}" done ;; *) echo "Usage: $N {start|stop}" >&2 exit 1 ;; esac exit 0 执行 update-rc.d php-chroot defaults 来让脚本在启动时执行.如果有多个chroot环境以及多个目录必要bind-mount,可以自行添加一个循环改写. 这个方法的好处是简单易行,不需要拷贝大量etc下的配置文件和库文件到chroot环境中.使用nscd在解决域名访问的问题过程中也顺道解决了/etc/passwd和/etc/group.但是bind-mount和nscd的平安性尚没有确切的说法,只能说so far so good.另外 mount -bind 会消耗一定的系统资源,有评论称大约一个mount 大概会消耗500k内存,所以对于大量的chroot环境(几百个)不见得适合. 办法2:拷贝/etc配置文件和库文件 这是最传统而常用的办法,也相对比较复杂.到底拷贝哪些配置、哪些库文件因发行版和软件版本而异,很难有定论也不好调试.而且一旦系统升级,对应的库文件也需要进行更新,工作量很大.我没有采用这个办法,但是简要的介绍一些比较靠谱的办法分享一下. 域名解析.必要拷贝/etc/resolv.conf,/etc/hosts,/etc/nsswitch.conf到chroot环境下的etc目录下.还必要拷贝一系列的库文件,主要是libnss_*.so,libresolv.so,libsoftokn3.so.具体libnss_*.so拷贝哪些,可以打开nsswitch.conf看列出了哪些. 时区配置.拷贝/etc/localtime,/usr/share/zoneinfo/zone.tab,和/usr/share/zoneinfo目录下所使用时区的文件. 其它常用配置./etc/passwd和/etc/group有时也是必要的,但是内容 似乎可以伪造,至少可以选择性的填写不用完全拷贝主系统里的. 如果使用的时候仍然出现问题,可以使用strace来查看php进行了哪些调用使用了哪些库文件.先执行:
代码如下:
bash ps aux | grep php | grep 'chroot' #chroot是php的pool名 查看pool的进程pid(可以在pool设置里先把子进程数目限制到1个便利调试).然后执行: bash 这个方法很麻烦,尤其是第一次安装设置和后续系统更新时.当然身为运维人员写写shell脚本简化工作肯定是基本功了.这种方法没有额外的内存消耗,可以部署大量chroot环境,当然硬盘消耗会高一点,而且平安性也经历了长久的考验 3.2 修复mail() 如果是使用WordPress,也可以利用MailChimp等插件不使用系统自身的邮件服务.事实上因为垃圾邮件的标准日益严格,和VPS主机商的限制,我现在更倾向于干脆不在系统里部署邮件服务了,所以php的mail()函数算是被废掉了……当然如果必要的话也可以很简单的设置好的. php的mail()函数是使用system()调用sendmail进行邮件发送操作,所以必要chroot环境里有能够调用的sendmail程序即可.常见的替代品是mini_sendmail,这里多介绍ssmtp,msmtp也类似. 前提:处理/bin/sh system()调用产生的命令行是 /bin/sh -c command .在chroot环境中调用外部程序必需存在/bin/sh,一个基本的shell.通常选择拷贝dash:
代码如下:
bash #cp /bin/dash /var/www/chroot/bin/sh 注意运行 ldd /bin/dash 观察必要拷贝哪些库文件.我这里的回显是:
代码如下:
bash ldd /bin/dash ??? linux-vdso.so.1 =>? (0x00007fff779fe000) ??? libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f165620f000) ??? /lib64/ld-linux-x86-64.so.2 (0x00007f16567fc000) 第一条那个只列了个文件名,=>后面也没有文件的基本上都是不用管的.剩下的库文件基本的原则是如果列出的是/lib64,就拷贝到chroot环境下的/lib64,如果列出的是/lib,虽然有很多发行版,大部分库文件包含libc.so是在/lib/x86_64-linux-gnu/目录下的,也直接拷贝到chroot环境的/lib目录下即可,是可以正常找到的. 但是! 前面那句 “必需存在/bin/sh,一个基本的shell” 其实并不是真的,对于mail()只要有一个能接受-c参数调用后面的命令的程序就可以了.所以Kienzl写了这样一个程序: c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define MAXARG 64 int main( int argc,char* const argv[] ) { char* args[ MAXARG ] = {}; if( argc < 3 || strcmp( argv[1],"-c" ) != 0 ) { fprintf( stderr,"Usage: %s -c <cmd>n",argv[0] ); return 1; } { char* token; int i = 0; char* argStr = strdup( argv[2] ); while( ( token = strsep( &argStr," " ) ) != NULL ) { if( token && strlen( token ) ) args[ i++ ] = token; if( i >= MAXARG ) return 2; } } return execvp( args[0],args ); } 保留成sh.c执行: gcc sh.c -o sh -static 然后把sh拷贝到chroot环境的/bin目录下即可. 这样一个不完全的shell从一定程度上也算是增强了chroot环境的平安性了. 办法1:使用mini_sendmail mini_sendmail似乎专为chroot环境而生.调用mini_sendmail后,它会转而拜访本机的25端口,通过本机的邮件服务来发送邮件.所以如果主环境有安装postfix/exim4等邮件服务的话可以使用mini_sendmail来在chroot环境中发送邮件,这是最简单的方法. mini_sendmail的安装很简单:
代码如下:
bash wget http://www.acme.com/software/mini_sendmail/mini_sendmail-1.3.8.tar.gz tar zxf mini_sendmail-1.3.8.tar.gz cd mini_sendmail-1.3.8 make cp mini_sendmail /var/www/chroot/usr/sbin/sendmail 最后一行自行修改chroot环境的目录.切记要拷贝到chroot环境的/usr/sbin目录下并且命名为sendmail.否则的话要在pool里自行设置ini参数的sendmail_path来指导php找到sendmail程序. 由于mini_sendmail默认便是静态链接,所以也无需拷贝其它的库文件了. 办法2:使用ssmtp/msmtp 对于本机没有安装邮件服务的情况,就不能使用mini_sendmail了.ssmtp和msmtp都支持把接收到的邮件发送哀求转而通过其它SMTP服务器来发送.需要注意的是由于ssl支持需要更多更复杂的库文件和配置,所以不建议为两者编译ssl支持……下面以ssmtp为例介绍一下. bash wget ftp://ftp.debian.org/debian/pool/main/s/ssmtp/ssmtp_2.64.orig.tar.bz2 tar jxf ssmtp_2.64.orig.tar.bz2 cd ssmtp_2.64 ./configure --prefix=/ #别忘了prefix make #千万别手抖make install cp ssmtp /var/www/chroot/usr/sbin mkdir -p /var/www/chroot/etc/ssmtp cp ssmtp.conf revaliases /var/www/chroot/etc/ssmtp #配置文件 cd /var/www/chroot/usr/sbin ln -s ssmtp sendmail 同样记得ldd然后把对应的库文件拷贝过去.另外ssmtp必要/etc/passwd和/etc/group,如果上面没有使用nscd必要拷贝/伪造这两个文件. ssmtp必要配置.ssmtp.conf文件配置如下: bash root=admin@example.com #其实这行好像可以乱写 mailhub=smtp.example.com #smtp服务器地址 hostname=myexample.com #此处的hostname似乎会用于产生默认的“root@myexample.com”形式的发件人地址 AuthUser=admin@example.com #此处使用真实的登录用户名 AuthPass=password #暗码 FromLineOverride=YES #允许改写发件人 revaliases里配置每个用户在使用ssmtp时使用的“发件人”地址和smtp服务器地址.可以不配置,但是文件要有.具体格式是:
代码如下:
bash # 当地用户名:发件人地址:smtp服务器[:端口(默认25)] root:admin@example.com:smtp.example.com www-data:noreply@example.com:smtp.example.com 可以使用chroot(指真正的chroot命令)做个测试:
代码如下:
bash chroot /var/www/chroot /bin/sh???????????? #此时/bin/sh一定要是真正的shell echo "Subject: test"|sendmail -v username@server.com? #替换邮件地址为本身的 此时php的mail()函数应该就可用了. 4.其它问题 配置完chroot环境跋文得将php的pool设置里display_error关闭. /var/www/chroot/ ├── bin │ └── sh ├── dev │ ├── null │ ├── urandom │ └── zero ├── etc │ └── ssmtp │ ├── revaliases │ └── ssmtp.conf ├── lib │ └── libc.so.6 ├── lib64 │ └── ld-linux-x86-64.so.2 ├── public ├── tmp ├── usr │ ├── sbin │ │ ├── sendmail -> ssmtp | │ └── ssmtp │ └── share │ └── zoneinfo │ ├── 大量时区的目录结构 │ └── zone.tab └── var ├── lib │ └── php5 │ └── sessions ├── run │ └── nscd │ ├── nscd.pid │ └── socket └── www └── chroot -> ../.. 以上便是本文的全部内容,希望大家喜欢. 《PHP-FPM之Chroot执行环境详解》是否对您有启发,欢迎查看更多与《PHP-FPM之Chroot执行环境详解》相关教程,学精学透。编程之家 52php.cn为您提供精彩教程。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |