2018-02-16

RubyのCGIをGoogle Cloud Platformの無料枠で動かしたい ~(2)SELinuxの設定

SELinuxでCGIスクリプトが500エラーになる

RubyのCGIをGCP(Google Cloud Platform)の無料枠で動かしたい~(1)セットアップ の続きです。

CGIスクリプトをブラウザから実行しようとして 500 Internal Server Error が出る場合、SELinuxの設定変更が必要かもしれません。

対策として「SELinuxをオフにすれば動きます」という大胆なブログ記事も見かけますが、できることならセキュアな状態で使いたいものです。GCPは世界中から狙われやすそうですし。
SELinuxを有効にしたままCGIを動かせるよう設定します。

問題を切り分ける(本当にSELinuxのせい?)

SELinuxを一時的に停止して、本当にSELinuxのせいなのかを切り分けます。
# setenforce 0
# getenforce ←確認
Permissive
permissiveモードは完全無効化とは違い、アクセス制御は行ないませんがログを出力します。

まずは超簡単なシェルスクリプトを /var/www/cgi-bin/test.cgi あたりに配置してみます。
#!/bin/sh
echo Content-type:text/plain
echo
echo ok!
パーミッションは755にでもして、コマンドラインで実行できることを確認します。
# cd /var/www/cgi-bin
# ./test.cgi
Content-type:text/plain

ok!
シェル上では問題なく実行できるのに http://~/cgi-bin/test.cgi にブラウザでアクセスしてエラーが出るようなら、そもそもSELinuxとは別のところに問題がありそうです。
  • Apacheの設定は正しいですか? apachectl configtest の結果は?
  • httpd.conf編集後に systemctl reload httpd しました?
  • cgiファイルのパーミッションは適切ですか?(Apacheユーザに実行権限はあります?)

シェルスクリプトで問題なかったら、次はRubyのCGIスクリプトを /cgi-bin に置きます。
#!/usr/local/bin/ruby
puts "Content-type: text/plain"
puts
puts "Ruby!"
まずはコマンドラインで実行します。ここで動かないならRubyが正しく導入できていないことになります。
次にブラウザからアクセスしてみます。エラーになるなら、スペルミスやファイルパーミッションなどを確認します。

ここまで確認して問題がなかったなら、SELinuxを元に戻してみます。
# setenforce 1
# getenforce
Enforcing
これで再びエラーが出るようになったなら、さすがにSELinuxが原因と言えそうです。

SELinuxでCGIを動かすための基本設定 (httpd_enable_cgi)

そもそもCGIが無効になっていると動きません。
# getsebool httpd_enable_cgi
httpd_enable_cgi --> off
offになっていたらonにします。
# setsebool -P httpd_enable_cgi 1

SELinuxコンテキストの設定 (httpd_sys_script_exec_t)

SELinuxでは「コンテキスト」という概念があって、適切なコンテキストが設定されていないと動作が阻止されます。
CGIの場合、ファイルが httpd_sys_script_exec_t というタイプになっている必要があります。

コンテキストは ls コマンドの -Z オプションで表示できます。
# ls -aZ /var/www/cgi-bin
drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 .
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 ..
-rwxr-xr-x. root root unconfined_u:object_r:user_home_t:s0 test.cgi
末尾が _t となっている部分がタイプです。test.cgiファイルは user_home_t タイプになっており、これではSELinuxに止められます。

chcon コマンドでタイプを変更します。
# chcon -t httpd_sys_script_exec_t test.cgi
# ls -Z test.cgi
-rwxr-xr-x. root root unconfined_u:object_r:httpd_sys_script_exec_t:s0 test.cgi

GCEのCentOS7なら、ここまですれば全く動かないということはないと思いますが、それでも動かない場合はログを見ながら原因を探ることになります。
# audit2allow -wa
ログの読み方と対処法はここでは説明しきれないので、ハマってしまったらがんばってください……。

/var/www/html 以下でもCGIスクリプトを動かしたい (httpd_sys_script_exec_t)

CGIファイルを /var/www/cgi-bin ではない場所に置きたい場合、先ほどと同じくCGIファイルのタイプを httpd_sys_script_exec_t にすればOKです。
# cd /var/www/html
# ls -Z test.cgi
-rwxr-xr-x. root root unconfined_u:object_r:httpd_sys_content_t:s0 test.cgi
# chcon -t httpd_sys_script_exec_t test.cgi
# ls -Z test.cgi
-rwxr-xr-x. root root unconfined_u:object_r:httpd_sys_script_exec_t:s0 test.cgi

/var/www/cgi-bin では動作するのに /var/www/html では動かない、という場合はApacheの設定を見直します。Options に ExecCGI は入ってますか?

SELinuxコンテキスト設定の永続化と適用 (restorecon)

ひととおり動作することが確認できたら、デフォルトのタイプ設定として永続化することができます。
パスは正規表現で指定します。
# semanage fcontext -a -t httpd_sys_script_exec_t '/var/www/html/.*\.cgi'
# semanage fcontext -lC
SELinux fcontext                                   type               Context
/var/www/html/.*\.cgi                              all files          system_u:object_r:httpd_sys_script_exec_t:s0
これで /var/www/html/ 以下の(サブディレクトリも含む)拡張子.cgiのファイルのデフォルトタイプが httpd_sys_script_exec_t になりました。

デフォルト設定すればいちいち chcon しなくても済むのですが、残念ながらファイルを新しく作ると自動的にデフォルトタイプになるわけではありません。
初期タイプは、ファイルの属するディレクトリから受け継ぎます。
# touch /var/www/html/test2.cgi
# ls -aZ /var/www/html
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 .
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 ..
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 test2.cgi
ファイルのデフォルトタイプを適用するには restorecon を使います。
# restorecon -v test2.cgi
restorecon reset /var/www/html/test2.cgi context unconfined_u:object_r:httpd_sys_content_t:s0->unconfined_u:object_r:httpd_sys_script_exec_t:s0
# ls -Z test2.cgi
-rwxr-xr-x. root root unconfined_u:object_r:httpd_sys_script_exec_t:s0 test2.cgi

結局ファイルを作るたびにタイプ変更のコマンドを叩く必要があるなら、デフォルト設定って何の役に立つの? という気持ちになるかもしれませんが、restorecon は chcon と違ってタイプを具体的に指定しなくて済むのはメリットです。

CGIスクリプトからファイルに書き込みたい (httpd_sys_rw_content_t)

CGIの実行は可能になったものの、CGIスクリプトからファイルに書き込もうとするとSELinuxによって阻止されます。
たとえ書き込み先のファイル権限が 666 であってもです。

書き込み先のファイルにはSELinuxのタイプ httpd_sys_rw_content_t が必要です。
ファイルをCGIスクリプトから作成したい場合は、書き込み先のディレクトリにも同タイプを設定します。

chcon で毎回設定してもいいですが、たとえば data という名前のディレクトリは一律でデータ用とするなら、デフォルト設定にしてもよいでしょう。
# semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/html(/.*)?/data(/.*)?'
これで /var/www/html 以下の data ディレクトリと、dataディレクトリ内のすべてのファイルやディレクトリのデフォルトタイプが httpd_sys_rw_content_t になります。

data ディレクトリを httpd_sys_rw_content_t タイプにしておくと、CGIスクリプトでデータファイルを新規に作成した場合、ファイルのタイプは自動的に httpd_sys_rw_content_t になります。新しく作ったファイルはディレクトリのタイプを受け継ぐためです。

CGIスクリプトから通信したい (httpd_can_network_connect)

CGIスクリプトからローカルファイルの読み書きは可能になりましたが、スクリプト内で通信をしようとするとSELinuxに阻止されます。
curlを呼んだりしたい場合は、通信の許可設定が必要です。
# setsebool -P httpd_can_network_connect 1

httpd関連だけSELinuxを止めたい(permissive_httpd_t)

とりあえず以上の設定でCGIスクリプトはひととおり動くはずですが、どうしてもエラーが解消しきれなくて時間がない場合、SELinuxを丸ごと permissive モードにするのではなく、httpd 関連のみ permissive にする方法もあります。
# semanage permissive -a httpd_t
# semodule -l | grep permissive
permissive_httpd_t      (null)
permissivedomains       (null)

問題が解決したら元に戻します。
# semanage permissive -d httpd_t

0 件のコメント:

コメントを投稿