Apache を suEXEC 付きでビルドしてバーチャルホストで PHP-CGI を使う

2012/5/29
Original: http://blog.osyoyu.com/2012/05/php-cgi-with-suexec-on-vhosts/

通常、ApacheはCGIやPHPをApacheの実行ユーザー(httpd.confのuser, groupの設定値。デフォルトはdaemonとかnobodyとか)で実行します。この場合、複数人で使用しているサーバーの場合他の人がアップロードしているファイルをCGIを通じて操作できます(そういうのやめてほしいけど)。

suEXECを使えば、指定したユーザーの権限でCGIが動作するようになります。
所有者の権限で実行できるので、パスワードなどを記録したファイルは自分だけに見えるようにしても実行に支障がなくなり、便利です。

環境

その他、自分の環境とは異なるところがあれば適宜読み替えてください。

Debian 系のディストリビューションでは apache2-suexec-custom というとっても扱いやすいパッケージがあるので、それを利用できるならそちらのほうがいいかも。

諸注意

必要なもの

ApacheとPHPのビルド

Apacheをビルドします。(おそらく大半の設定は2.2互換です)
$ ./configure \
	--with-mpm-prefork \
	--enable-cgid \
	--enable-actions \
	--enable-mods-shared=all \
	--enable-suexec \
	--with-suexec-caller=apache \
	--with-suexec-docroot=/var/www \
	--with-suexec-uidmin=1000
$ make
# make install

suEXEC関連のオプションについて簡単な解説をしておきます。
--enable-suexec: 見た目通り、suEXECを有効化するオプションです。
--with-suexec-caller=apache: suEXECを呼び出すユーザーを指定します。Apacheのhttpd.confのuserと同じ物を指定しておけば大丈夫です。このユーザー以外はsuEXECを呼び出せません。
--with-suexec-docroot=/var/www: suEXECが対象に取れるCGIがおいてあるディレクトリの指定です。このディレクトリの外に置かれているCGIに関しては、suEXECを使って実行することができません。
--with-suexec-uidmin=1000: suEXECが対象に取れるユーザー(not suEXEC実行するユーザー。要は実行したいファイルの所有者)の最低UIDを指定します。これはいわゆるシステムユーザー(ftpとか、postfixとか)にCGIを実行されるのを防止するためです。デフォルト値は100ですが、自分の環境では100番台もシステムユーザーがあった上、一般ユーザーはすべて1000以上のUIDが振られていたので1000を指定しています。
(ここではいくつかのsuEXEC関連の設定を省略しています。すべてのオプションは公式ドキュメントに載っています。

なお、httpd.conf は適宜CGIを実行できるように設定してください (AddHandlerの編集など)。
(今回必要なオプション以外は省いています。適宜設定してください)

suEXECが正しくビルドできているか、確認しておきます。

# /usr/local/apache/bin/suexec -V
 -D AP_DOC_ROOT="/var/www"
 -D AP_GID_MIN=100
 -D AP_HTTPD_USER="apache"
 -D AP_LOG_EXEC="/usr/local/apache2/logs/suexec_log"
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_UID_MIN=1000
 -D AP_USERDIR_SUFFIX="www"

のように出力されたら成功です。

suexec policy violation: see suexec log for more details

が出力されたら、とりあえずrootで実行しているか確認してください。

PHPのビルドに関しては、特別なことはないので通常通りオプションを指定してビルドしてください(パッケージシステムからのインストールでもうまくいくようならそれでもいいかも)。
ただし、--disable-cgi は絶対に指定しないでください。

VirtualHostの設定

次に、suEXECを有効にしたいVirtualHostの設定を書きます。
	DocumentRoot /var/www/VirtualHost1

	SuexecUserGroup hoge piyo

		Options FollowSymlinks ExecCGI MultiViews

		Require all granted

	ScriptAlias /php-cgi /var/www/php-cgi/hoge/php-cgi
	AddHandler application/x-httpd-php .php
	Action application/x-httpd-php /php-cgi

まあ、例のごとく色々省いていますが、重要なのは以下の行です:

SuexecUserGroup hoge piyo

ここでは、suEXECにどのユーザー/グループでCGIを起動させるかを指定しています。
ここを指定しないと、通常通りApacheの権限で起動されます。

14-16行目はPHP-CGIを呼び出すための設定ですが、これは後で解説します。
PHPを使わず、#! を使って実行パスを指定する普通のCGIしか使わないのであれば設定はここで終了です。

PHP-CGIのための設定

先にも書いたように、PHP-CGIの呼び出しを通常のCGIの呼び出しのように #!/usr/bin/php-cgi にするのであれば、ここから先は必要ありませんが世の中に溢れかえるPHPファイルは99%そうではないので、互換性とかそういうもののために以下の設定をします。

まずは、PHP-CGIのラッパーとなるシェルスクリプトを用意します。

#!/bin/sh
exec /usr/local/bin/php-cgi

これは、/var/www/php-cgi/VirtualHost1 の中に php-cgi というファイル名で保存してください。
VirtualHost1 ディレクトリとこのファイルの所有権/グループはPHPを実行したいユーザーのもの(この場合、hoge/piyo)にしてください。rootにすると、suEXECが文句いい始めます。
また、実行権限を付与してください(忘れがちです)。 ls -l で見た時に、-rwxr-xr-x 1 hoge piyo 38 May 29 00:58 php-cgi のようになれば大丈夫です。

内容としては、本当にphp-cgiに渡しているだけです。PHPを起動する際にオプションが必要であるのならば、ここに記述してください。
なお、

ここまでのディレクトリ構造を一度整理します。

user:group path
root:root /var/www
hoge:piyo /var/www/VirtualHost1 (ここにHTMLやCGI, PHPファイルなどを配置)

root:root /var/www/php-cgi (これはディレクトリ)
hoge:piyo /var/www/php-cgi/VirtualHost1 (この中に上記の php-cgi ファイル(ラッパー)を置く)

では、最後にバーチャルホストのconfに書き足す内容の解説をします。

ScriptAlias /php-cgi /var/www/php-cgi/osyoyu/php-cgi
AddHandler application/x-httpd-php .php
Action application/x-httpd-php /php-cgi

まず、.php ファイルが呼ばれた時、15行目の設定によって application/x-httpd-php であると判断されます。
次に、16行目の Action ディレクティブ によって自動的に /php-cgi に渡されます。
最後に、14行目に定義されている ScriptAlias ディレクティブ によって、/php-cgi の本体である /var/www/php-cgi/osyoyu/php-cgi に渡されます。

おそらく、こんな感じで設定すればうまくsuEXECできるのではないでしょうか。

確認

suEXECを通して実行されているか、簡単に確認するには各言語から whoami コマンドを叩いて、出力するのが一番早いです。

Ruby の例

#!/usr/local/bin/ruby
# encoding: utf-8

puts `whoami`

PHP の例

<?php
print(exec('whoami'));
?>

出力が hoge になれば成功です。apacheやdaemon, nobodyのようなものになった場合はどこかの設定が間違っています。

もしできていなければ、ログを確認しながらどこがおかしいのかひとつひとつ確認していくといいと思います。
suEXECのログは /usr/local/apache2/logs/suexec_log , Apache 自身のエラーログは /usr/local/apache2/logs/error_log,
あとは各VirtualHostのErrorLogを確認してください。

おしまい。最後のほう説明が適当になってごめんなさい。これを複数のバーチャルホストに対して設定する場合、全てのバーチャルホスト専用のphp-cgiのラッパーファイルを作成するようにしてください。