mod_selinuxのアーキテクチャを考える(前編)
mod_selinuxはapache/httpdのプラグインで、利用者のHTTP要求の認証(BASICとかDIGESTとか)結果に基づいて、PHPスクリプトや静的コンテンツを参照するContents Handlerの実行権限を切り替える。
やっている事は単純で、ap_hook_handlerの先頭で mod_selinux が処理を乗っ取り、一時的にワーカースレッドを作成して権限を縮退させる。で、以降のContents Handlerの実行はワーカースレッドに託されるという形になる。
だが、このデザインだと問題になる事が2点。
- ap_invoke_handler()が再入可能ではない
- Bounds domainの範囲でしかドメイン遷移ができない
前者の問題について。CGIが自サーバ内のURLを含むLocation: ヘッダを返した場合など、internal redirectionという処理が走り、cgi_handlerのコンテキストでap_invoke_handler()が再び呼ばれる事がある。
この場合、再びHTTP認証が実行され、その結果、cgi_hanclerのコンテキストとは異なるsecurity contextが指定される事があり得る。が、既にsecurity contextを変更する権限を放棄しているハズなので、詰む。この場合はエラーを返してごめんなさいするしかない。
この問題は、SELinuxに限った話ではなく、CAP_SETUIDを放棄するシナリオでも同じ。
後者の問題について。これはSELinux固有の話で、マルチスレッドのアプリがドメイン遷移を行う場合、遷移先のドメインは bounds domain 制約を満足する必要がある。
(いや、これは元々私の提案だが・・・。)
ドメイン httpd_t が user_webapp_t の bounds domain であるという状態は、user_webapp_t の持っている権限は全て httpd_t も持っているという事。つまり、ドメイン遷移元のhttpd_tは、システム管理系のツール等もWeb経由で実行させると考えると、相当に広範な権限を付与する必要がある・・・事になる。
実際にセキュリティポリシーを書くとなると、これが地味に効いてくる。
端的に言って、書きにくい・・・。(´ω`;;)
まず前者の問題を解決するため、mod_selinuxの構造を上の模式図のように変更してみた。このデザインではsecurity context毎にタスクキューを持ち、ap_handler_hookの先頭でHTTP認証に応じてキューにタスクを振り分けるという形になる。
キューは on demand で生成され、適合する security context が存在しない場合にはTask Launcher Threadにキュー生成要求を行ってこれを作成してもらう。
このデザインだと、ap_invoke_handler()の延長でinternal redirectionが走った場合でも、再帰して呼び出されたap_handler_hookの先頭で行う処理は『適切なキューに処理要求を投げる』だけなので問題は生じない。
・・・と、ここまで実装してハタと気が付いた。
これって本質的には (1)プロトコル処理サーバ と (2)アプリケーションサーバ の分離と同じことだよねと。
だとすると、上のデザインでは各キューに紐付いていたワーカースレッドをワーカープロセスとして分離すると、最初に挙げた2つの問題のうち後者の Bounds domain の制限を取り払う事ができるようになる。
つまり、利用者のcredentials(uid/gid/security context)毎にワーカープロセスをon demandで起動し、HTTPリクエストの解析はapache/httpd自身で行う一方で、静的コンテンツの参照も含めたHTTPリクエストの処理をワーカープロセスで実行するようにすれば、問題は全て解決するのではなかろうか。
幸いな事に、この手のプロトコルとしてFastCGIが既に定義されており、PHPやRuby、Javaなどメジャーどころの言語はこれに対応している。
...とすると、mod_selinuxを拡張するよりも、mod_fcgidに対する追加機能としてSELinux(やその他のセキュリティ機構を包含する)仕組みを提案した方が、メンテナンスが楽になるのかな、と考え中。