mod_mrubyでsuEXECのようなアクセス制御を実装できるようにしました。
しかも、CGIだけでなくモジュール型のPHPやmod_perl等、所謂DSOも簡単にsuEXECのようにサーバプロセスと権限を分離して実行することができます。
以下に、実装例を紹介します。
mod_mrubyで実装してみる
mod_mrubyのビルドで、組み込んでおいた方が良いmrbgemを自動で組み込んでビルドしてくれるbuildスクリプトを作りました。mod_mrubyを簡単に試したいという方は是非使ってみてください。
cd mod_mruby
sh build.sh
上記コマンド一発で、便利なmrbgemを組み込んだmod_mrubyモジュールがビルドされます。
その後、httpd.confに以下のように設定します。
mrubyFixupsLast /etc/httpd/hooks/suexec.rb
続いて、capset.rbに以下のように実装します。
Apache.errlogger 4, "prepare cap"
これによって、親サーバプロセスは80ポートをbindするためにrootで起動しますが、その時にLinuxのCapabilityを一般ユーザである子サーバプロセスが引き継ぐ事ができます。このpost_configというフックは、Apacheの親サーバプロセスの初期化処理(root)のタイミングだと思ってください。
さらに、suexec.rbを以下のように記述します。
pid = Process.fork {
c = Capability.new
r = Apache::Request.new
c.get_proc
cap = [Capability::CAP_SETUID, Capability::CAP_SETGID]
c.set Capability::CAP_PERMITTED, cap
c.set Capability::CAP_EFFECTIVE, cap
c.setuid(uid)
c.clear Capability::CAP_EFFECTIVE, cap
c.clear Capability::CAP_PERMITTED, cap
r.run_handler
Apache.errlogger 4, "suexec for #{r.filename}. set to uid #{uid}."
}
Process.waitpid pid
このように、リクエスト処理を行う直前のフェイズであるFixupsで、子サーバプロセスがさらに親から見た孫プロセスをforkし、さらにそのプロセスにsetuidとsetgidが可能な権限を付与しておきます。そして、CAP_PERMITTEDとCAP_EFFECTIVEを孫プロセスにセットし、孫プロセス自身を任意のuid(サンプルでは501)に変更します。
そして、Webアプリから権限昇格をさせないように、Webアプリ実行前にきちんとCAP_EFFECTIVEとCAP_PERMITTEDをclearしておいてから、r.run_handlerにてコンテンツを処理します。
これによって、例えばsleepするだけのphpスクリプトをmod_phpで動作させると、以下のようなプロセスの状態になります。
501 29976 0.0 0.1 316740 7128 ? S 11:39 0:00 ¦ ¥_ /usr/sbin/httpd -DFOREGROUND
apache 29937 0.0 0.1 316740 7004 ? S 11:39 0:00 ¥_ /usr/sbin/httpd -DFOREGROUND
apache 29938 0.0 0.1 316740 7004 ? S 11:39 0:00 ¥_ /usr/sbin/httpd -DFOREGROUND
きちんと501でPHPを実行できていますね。
DSOでの権限分離は色々と厄介なのですが、mod_mrubyを使えばこのように柔軟に実装することが可能になります。
まとめ
このように、mod_mrubyを使えば、suEXECやsuPHP、および、DSOに対応したmod_suid2やmod_ruid2のような、サーバプロセスとアプリの権限を分離して実行する処理が簡単に実装できました。
まだまだmod_mrubyはApacheをコントロールできるように改良中ですので、アイデア次第で自分のApacheを簡単かつ強力にカスタマイズすることができるしょう。
mod_mrubyでこんなの作った!とか、こんなの作りたい!とかを教えていただければ、随時対応したいと思っています。
1 Comments.