みんな大好きSELinux絡みのお話です。
先日、Wordpressの勉強がてらインストール作業を何回かやっていたら、ある時は正常にインストールが完了し、ある時はうまくいかない、そんな現象に遭遇しました。
先にまとめ
長くなったので先にまとめからです。
下記の内容で思い当たるところがあったら、一旦SELinuxをOFFにして試してみるのがいいかもしれません。
- Wordpressを手動インストールし、ブラウザでinstall.phpにアクセスすると「File not found.」と表示される
- 原因はSELinuxのポリシーがあるべき状態で割当たっていなかったこと
- PHPインスコ前に手動で/var/wwwディレクトリを作成したことで、var配下に適用されるSELinuxのコンテキストが割り当てられていた
- 本来は、SELinuxのhttpd系のセキュリティコンテキストが割当たっていないといけなかった
教訓1:自動生成されるものは手動で作ってはならぬ。
教訓2:SELinuxの所為にするな。だいたい自分の所為だ。
発生した事象
一通り必要なソフトウェアをインストールして、設定もしたのにWordpressのinstall.phpファイルが動いてくれない。具体的には、ブラウザでhttp://domain.com/wp-admin/install.phpにアクセスしたときに以下の画面「File not found.」が表示され、どうにも先に進めない状態。
私はインストールを進めるにあたって、簡単なインストール手順を2つ用意して作業をしていました。1つは手順書ライクなもの、もう1つはインストールスクリプトの形で実行すれば全部自動で完了するようなものです。前者の手順書ライクなものは以下のエントリに書いたものです。
手順書ライクなほうでやったときは問題なくインストールを完了できたんです。しかしその手順書を元にちょっと改造してスクリプトの形にしたものは、うまくインストールができませんでした。上に掲載した「File not found.」のメッセージが出力されて先に進めない状態になったんです。
原因調査
「File not found.」と表示されていますので、ファイルが見つからない、つまりWebサーバは動作していると。NginXとPHPがイチャイチャしながらinstall.phpファイルを探したけど見つからなかった、ということだろうと推測しました。とりあえず正常にインストールできた環境も手元にあったので、各環境のNginXのconfファイルを1行1行なめるように比較してみました。が、差異は見つからず。
同様にPHPにうまくリクエストが渡っていないことも考えられたので、php-fpmのconfファイルも舐めるように確認しました。しかしこちらも正常インストールできた環境との間で差異は見つからない。confファイルに書かれている内容は完全一致している。なんだったら、Wordpressのconfなんかもベロリンガして(舐めまわして)確認しました。けど、問題の箇所は特定できず。
ここは基本に帰るべしと、NginXのエラーログを確認してみました。
エラー内容でググって見ると、NginXのconfファイルにおいて、以下の設定が残っていると上記エラーが発生するとの情報がいっぱい出てきました。
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
この/scriptsのところをドキュメントルートのパスに設定しないといかん。でも私はちゃんとやっています。でもエラーがでている。FastCGI sent in stderrってことなので、やっぱりPHPにリクエストを出してそれが返ってくるまでの間のどこかで何かが起こっているのは確かなようです。
php-fpmのログを見てもいまいち「これだっ!」ってのが無い。うーん、PHPがリソースファイルを見つけられていないということか?そうか、パーミッションか!と考え、関連ファイルのパーミッションをべロベルト(ベロリンガの進化形:舐めまわして)してみました。が、怪しい感じの所は見つけられず。
むしろファイルを探せていないんじゃなくて、そもそもPHPへリクエストを回せていないのか?NginXの設定で127.0.0.1:9000を通してPHPへリクエストを渡していたはず。このループバックアドレスは見れる状態(Listen)になっているのだろうか。
# ss -ltup4
(略)
tcp LISTEN 0 128 127.0.0.1:cslistener *:* users:(("php-fpm",pid=1438,fd=8),("php-fpm",pid=1152,fd=8),("php-fpm",pid=1151,fd=8),("php-fpm",pid=1150,fd=8),("php-fpm",pid=1149,fd=8),("php-fpm",pid=1148,fd=8),("php-fpm",pid=1041,fd=6))
なんかそれっぽくListenしているのがあるな。cslistenerって何だろう。調べてみると、ポート9000を指すものらしいので、これはこれでオッケーっぽい。そしてこの情報を得たときに一瞬目に入ったのが以下の記事。
はじめは躓くよCentOS7セットアップ(nginx + PHP + MariaDB on VirtualBox) - Qiita
SE Linuxのせいで権限弾かれて実行できないことがあったので、一旦無効化しておきます。
むむむ、SELinuxか・・・、あんまりピンとは来ないけど試してみよう。
# setenforce 0
# getenforce
Permissive
ブラウザでinstall.phpにアクセスを試みます。ポチッポチッポチッ
・
・・
・・・ようこそ画面が表示された... 貴様かっ!貴様だったのか!
この時点ではどうにも納得できなかったですが、SELinuxをOFFにするとうまく先に進めることがわかりました。
手順を振り返りつつ、SELinuxのことを調べてみた
さて、精神をすり減らしながら調査を進めた結果、原因はSELinuxらしいということがわかりました。(厳密には、SELinuxの仕組みをちゃんと理解していなくて、誤った手順で進めた私に原因があるのですが)
Webド素人の私は作業の冒頭で「あれ?/var/wwwディレクトリが無いぞ?よし作っておこう!」とmkdirでディレクトリを作成していました。これがいけなかった。素のCentOS7では/var/wwwディレクトリは存在しません。私はNginXをWebサーバにしていたんですが、Webの公開ディレクトリといったらやっぱり/var/wwwでしょと考えて、mkdirでディレクトリを手動作成しました。
素の状態はこんな感じになっています。
# ls -l /var
drwxr-xr-x. 2 root root 6 11月 6 2016 adm
drwxr-xr-x. 5 root root 44 7月 15 18:30 cache
drwxr-xr-x. 2 root root 6 8月 7 2017 crash
drwxr-xr-x. 3 root root 34 7月 15 18:30 db
drwxr-xr-x. 3 root root 18 7月 15 18:30 empty
drwxr-xr-x. 2 root root 6 11月 6 2016 games
drwxr-xr-x. 2 root root 6 11月 6 2016 gopher
drwxr-xr-x. 3 root root 18 7月 15 18:26 kerberos
drwxr-xr-x. 24 root root 4096 7月 15 18:30 lib
drwxr-xr-x. 2 root root 6 11月 6 2016 local
lrwxrwxrwx. 1 root root 11 7月 15 18:25 lock -> ../run/lock
drwxr-xr-x. 8 root root 4096 12月 16 16:27 log
lrwxrwxrwx. 1 root root 10 7月 15 18:25 mail -> spool/mail
drwxr-xr-x. 2 root root 6 11月 6 2016 nis
drwxr-xr-x. 2 root root 6 11月 6 2016 opt
drwxr-xr-x. 2 root root 6 11月 6 2016 preserve
lrwxrwxrwx. 1 root root 6 7月 15 18:25 run -> ../run
drwxr-xr-x. 8 root root 87 7月 15 18:30 spool
drwxrwxrwt. 3 root root 85 12月 16 16:28 tmp
drwxr-xr-x. 2 root root 6 11月 6 2016 yp
mkdirして、もう一度見てみると、こう。
# mkdir /var/www
# ls -l /var
drwxr-xr-x. 2 root root 6 11月 6 2016 adm
drwxr-xr-x. 5 root root 44 7月 15 18:30 cache
drwxr-xr-x. 2 root root 6 8月 7 2017 crash
drwxr-xr-x. 3 root root 34 7月 15 18:30 db
drwxr-xr-x. 3 root root 18 7月 15 18:30 empty
drwxr-xr-x. 2 root root 6 11月 6 2016 games
drwxr-xr-x. 2 root root 6 11月 6 2016 gopher
drwxr-xr-x. 3 root root 18 7月 15 18:26 kerberos
drwxr-xr-x. 24 root root 4096 7月 15 18:30 lib
drwxr-xr-x. 2 root root 6 11月 6 2016 local
lrwxrwxrwx. 1 root root 11 7月 15 18:25 lock -> ../run/lock
drwxr-xr-x. 8 root root 4096 12月 16 16:27 log
lrwxrwxrwx. 1 root root 10 7月 15 18:25 mail -> spool/mail
drwxr-xr-x. 2 root root 6 11月 6 2016 nis
drwxr-xr-x. 2 root root 6 11月 6 2016 opt
drwxr-xr-x. 2 root root 6 11月 6 2016 preserve
lrwxrwxrwx. 1 root root 6 7月 15 18:25 run -> ../run
drwxr-xr-x. 8 root root 87 7月 15 18:30 spool
drwxrwxrwt. 3 root root 85 12月 16 16:28 tmp
drwxr-xr-x. 2 root root 6 12月 16 16:34 www
drwxr-xr-x. 2 root root 6 11月 6 2016 yp
よし、できたと。
そして次にやったのが、Wordpressのダウンロードとtarファイルの展開です。wgetコマンドにオプションをつけて、/tmpにアーカイブファイルをダウンロード。で、そいつをtarで展開するときに、こちらもオプションをつけて、/var/wwwに直接wordpressディレクトリを展開しました。
# wget https://ja.wordpress.org/latest-ja.tar.gz -P /tmp
# tar -xzvf /tmp/latest-ja.tar.gz -C /var/www/
こんな感じっすわ。 よしよし、ちゃんと展開されたねと。
# ls -l /var/www
合計 4
drwxr-xr-x. 5 1006 1006 4096 12月 13 10:05 wordpress
ただ、これはダメパターン。SELinuxのセキュリティコンテキストを表示するためにオプションZを付与してlsを叩きます。
# ls -lZ /var |grep www
drwxr-xr-x. root root unconfined_u:object_r:var_t:s0 www
# ls -lZ /var/www
drwxr-xr-x. 1006 1006 unconfined_u:object_r:var_t:s0 wordpress
赤字にしたところ、「var_t」となっています。このフィールドはタイプ/ドメインというSELinuxの情報を表示するところです。ようは、var配下のルールが適用される状態になっているということです。
ドンピシャな解説がRedhatのWebサイトにありました。
本来は、/var/wwwディレクトリ以下が、varのルールを引き継ぐのではなく、httpd系のルールに従う形になっていなければなりません。たとえば、NginXをインストールすると、デフォルトのドキュメントルートディレクトリとして、/usr/share/nginx/htmlディレクトリが自動で作成されます。ここを見てみましょう。
# ls -lZ /usr/share/nginx
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 html
# ls -lZ /usr/share/nginx/html
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 50x.html
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.html
このディレクトリとその中にあるファイルのSELinuxセキュリティコンテキストは「httpd_sys_content_t」になっています。このタイプになっていないとWebサーバ、Webアプリケーションサーバはそのディレクトリや内部のファイルにアクセスができないというわけです。
今回のWordpress+NginXの構成の場合、phpをインストールすると、自動的に/var/wwwディレクトリを作成してくれます。こちらもみてみましょう。
# rm -rf /var/www ←上で手動作成したファイル/ディレクトリを消してます
# ls -l /var |grep www
# yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
# yum -y install --enablerepo=remi,remi-php74 php
# ls -l /var |grep www
drwxr-xr-x. 4 root root 33 12月 16 17:38 www
# ls -lZ /var |grep www
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 www
# ls -lZ /var/www
drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 cgi-bin
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 html
PHPのインストールによって作成された/var/wwwはhttpd系のタイプが割り当てられていました。この状態で、上でやったのと同じように/tmpに置いてあるtarを展開して/var/www配下に流し込んでみます。
# tar -xzvf /tmp/latest-ja.tar.gz -C /var/www/
# ls -lZ /var/www
drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 cgi-bin
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 html
drwxr-xr-x. 1006 1006 unconfined_u:object_r:httpd_sys_content_t:s0 wordpress
Wordpressディレクトリが「httpd_sys_content_t」タイプになりました。手動で/var/wwwディレクトリを作成し、PHPを入れる前にtar展開したときは、「var_t」でした。PHPを入れたことで、/var/www以下がhttpd系のタイプを継承したということでしょう。この状態でWordpressの諸々の設定を行うとうまくインストールが出来ました。
おわりに
終わってみるとだいぶマヌケだったなぁと思いました。実は最初に問題が発生してから2日間ハマっていたんですよ。configが悪いに違いないとアタリをつけて、configの生成箇所に集中していろいろと確認作業をやっていました。
今回はいろいろと変則的にやった結果として発生した事象だったと思います。同じケースに該当する方がそこまでいるとは思えないのですが、SELinuxに阻まれるケースって忘れた頃に遭遇するんですよね。内容としちゃ公開するのも恥ずかしいレベルのハマリ方でした。
誰か1人にでもこの情報が役に立てばと思い、恥を忍んでここまで書いてみました。
あぁ恥ずかしや
教訓:自動生成されるものは手動で作ってはならぬ。
おわり