tohokuaikiのチラシの裏

技術的ネタとか。

PHPの動かし方3つ

長年PHPと付き合ってきて動作モードが3つあることは知ってたが、なんとなくだったので自分用にまとめてみた。以下のサンプルでのOSはCentOS系。

モジュール版

一番簡単な方法。Apacheのconfで LoadModule php7_module modules/libphp7.so として読み込む方法。一番最初にPHP使った時はローカル環境でApache立ててモジュール版PHPを動かしていたような気がする。

メリット

  • 動作が早い。Apacheと同じプロセスなので

デメリット

  • サーバ全体でこのモジュール版の動作になる。小回りが利かない。PHPのバージョン違いとかできない。
  • Apacheの権限で動作する

今、これ動かすなら普通に apt-get php とかすればいいのかな?簡単なので、起動方法は省略。

PHP-FPM(FastCGI Process Manager )版

CGIなんだけど、一般的なmod_cgiのようにリクエストごとにプロセスを増やすのではなくCGIサーバとして1つのプロセスを常駐させておいてApacheはそのプロセスに橋渡しをする。

メリット

  • 純粋はCGI版だとプロセスが増えて負荷が増えるが、共通で1個なんでそれほどでもない。
  • PHPのバージョン違いはプロセスを増やせばできる。
  • 実行ユーザはプロセスごとに設定できる

デメリット

  • DAEMONを2つ用意しないといけない。

意外とデメリットがそれほどでもないので、開発者用にPHP提供するならPHP-FPMが一番よさげ。

設定

インストール

PHPの違うバージョンはremiリポジトリを使う。remiリポジトリの導入は省略。

#  yum install -y php74-php-fpm

これをDEAMONとして起動&自動起動する。

# systemctl enable php74-php-fpm
# systemctl start php74-php-fpm

設定

PHP-FPM

プロセスの設定ディレクトリは以下になる。PHP7.4の場合

/etc/opt/remi/php74/php-fpm.d/

ここに起動させたいプロセス分だけ設定ファイルを ****.conf とかで設置する。 /etc/opt/remi/php74/php-fpm.d/t-ito.conf とか。 基本的には、そこに www.confがあるのでそれをコピーして以下の項目を変更する。

# なんとなく設定セグメント
[t-ito]
# プロセスに接続するソケットファイル、Apacheの設定で使う。
listen = /var/opt/remi/php74/run/php-fpm/t-ito.sock
# プロセスを実行するユーザ
user = t-ito
# プロセスを実行するグループ
group = wheel

設定したら、PHP-FPM再起動

# systemctl restart php74-php-fpm
Apache

PHP拡張子はPHP-FPMのソケットにつなぎに行くようにする。

    <Directory "/var/www/demo/htdocs">
      <FilesMatch \.(php|phar)$>
          SetHandler "proxy:unix:/var/opt/remi/php74/run/php-fpm/t-ito.sock|fcgi://localhost"
      </FilesMatch>
    </Directory>

CGI + suExec (SuexecUserGroupの場合 )

普通にPerlとかのCGIと同じように動かす。ただし、なんやしらんけど、行頭の #!/usr/bin/php みたいなのは要らない。

で、それだとApache権限で動作するんだけど、それだけでなく、suExecでプログラムの実行ユーザも変更してくれる。

UserDir 方式だと、ファイルオーナーの権限で動作し、SuexecUserGroup方式だとSuexecUserGroupオプションで指定されているユーザ・グループの権限で動作する。

メリット

  • ユーザーごとの権限で動作するので、ほかのディレクトリに侵入できない(レンタルサーバーとかの共有サーバーで優位)
  • ディレクトリパーミションを777にしなくてもどんどん<?php mkdir() ; ?> とかできる。

デメリット

  • CGIなのでプロセスをApacheからフォークする必要があって、動作速度が遅いのと負荷はそれなりにある。
  • 脆弱性があるスクリプトを置くと、そのユーザーディレクトリは755だろうがなんだろうが全滅。

設定

環境

/usr/sbin/suexec -Vコマンドを実行すると以下のように出る。この値はsuexecコマンドをコンパイルした時点で決まるので、後から変更できない(らしい)。

 -D AP_DOC_ROOT="/var/www"
 -D AP_GID_MIN=1000
 -D AP_HTTPD_USER="apache"
 -D AP_LOG_SYSLOG
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_UID_MIN=1000
 -D AP_USERDIR_SUFFIX="public_html"

設定

ということで設定。

インストール

CGIプログラム /opt/remi/php74/root/bin/php-cgi が欲しいからこれが入るようなパッケージを導入。php-fpmあたりに入ってるのかな?

#yum install -y php74-php-fpm
PHPCGI実行プログラム

これをDocumentRoot内にコピーする。remiリポジトリPHPを入れると /opt/remi/php74/root/bin/php-cgiとかにあるので、

# cp /opt/remi/php74/root/bin/php-cgi /var/www/demo/php74-cgi
# chown t-ito:wheel /var/www/demo/php74-cgi

とかにコピーしてオーナーチェンジする。

ApacheCGI設定
  • ScriptAlias /php-cgi /var/www/demo/php74-cgi で、/php-cgi にアクセスした場合に/var/www/demo/php74-cgiを実行するようにする。 *1
  • Options ExecCGICGIが実行できるようにする。
  • AddHandler php-cgi .phpphp拡張子のファイルに対してphp-cgiというactionを関連付ける。
  • Action php-cgi /php-cgiphp-cgiというActionに対して/php-cgiというスクリプトを関連付ける。
  • SuexecUserGroupCGI実行ユーザー・グループ権限を設定 SuexecUserGroup ユーザー名 グループ名 (( UserDirを使う場合は、各ユーザーのWebRootディレクトリをAP_USERDIR_SUFFIXに合わす。t-itoユーザーなら /home/t-ito/public_html 以下。…らしい ))

で、これらをサーバ設定ファイルとバーチャルホスト /etc/httpd/conf.d/demo.conf で設定する。

<VirtualHost *:80>
    ServerName demo.example.ne.jp
    DocumentRoot "/var/www/demo/htdocs-suexec"

    ScriptAlias /php-cgi /var/www/demo/php74-cgi

    SuexecUserGroup t-ito wheel
    <Directory "/var/www/demo/htdocs-suexec">
      Options FollowSymLinks ExecCGI
      AllowOverride All
      AddHandler php-cgi .php
      Action php-cgi /php-cgi
    </Directory>
    ErrorLog  /var/www/demo/logs/xxx_error_log
    CustomLog /var/www/demo/logs/xxx_access_log combined
</VirtualHost>
PHPファイルを設置

で、/var/www/demo/htdocs-suexec/phpinfo.php<?php phpinfo とだけ書いたファイルを設置。はい、Internal Server Error

エラー対策

SuexecUserGroup で指定するユーザー&グループは suexec -V の AP_GID_MIN と AP_UID_MIN の番号以上にしなければならない

suexec -V が -D AP_LOG_SYSLOGなので、 # journalctl -f でエラーを見る。

Sep 09 14:37:06 devel05 suexec[904624]: uid: (1031/t-ito) gid: (10/wheel) cmd: php74-cgi
Sep 09 14:37:06 devel05 suexec[904624]: cannot run as forbidden gid (10/php74-cgi)

ははぁ、 cannot run as forbidden gid で10番のグループIDで動いてるのがダメっぽい。

# getent group wheel
wheel:x:10:,t-ito

たしかに10番であるが、suexec -Vでは、 -D AP_GID_MIN=1000とグループIDが1000番未満ではダメだよと。

自ユーザー独自グループなら問題ないだろうとやり直す。

# getent group t-ito
t-ito:x:1031:

当然ながら、1000番以上になっている。

# chown t-ito:t-ito /var/www/demo/php74-cgi
# chown -R t-ito:t-ito /var/www/demo/htdocs-suexec
<VirtualHost *:80>
    ServerName demo.example.ne.jp
    DocumentRoot "/var/www/demo/htdocs-suexec"

    ScriptAlias /php-cgi /var/www/demo/php74-cgi

    SuexecUserGroup t-ito t-ito
    <Directory "/var/www/demo/htdocs-suexec">
      Options FollowSymLinks ExecCGI
      AllowOverride All
      AddHandler php-cgi .php
      Action php-cgi /php-cgi
    </Directory>
    ErrorLog  /var/www/demo/logs/xxx_error_log
    CustomLog /var/www/demo/logs/xxx_access_log combined
</VirtualHost>

で、Apache再起動

ディレクトリは他のユーザーが書き込み出来てはいけない。

はい、またInternal Server Error… で、#journalctl -f すると。

Sep 09 14:43:34 devel05 suexec[905245]: uid: (1031/t-ito) gid: (1031/t-ito) cmd: php74-cgi
Sep 09 14:43:34 devel05 suexec[905245]: directory is writable by others: (/var/www/demo)

ls -l すると

drwxrwsr-x  5 t-ito    wheel         4096 Sep  9 14:20 demo

なるほど…

# chown t-ito:t-ito demo
# chmod 755 demo

これでいけた。なかなか厳しい。

*1:ScriptAliasの説明を読んでもそのような記述は無くて、CGIが実行できるディレクトリを指定するということくらいなんだけど、ファイルを指定している場合はそうなるのかな?