サーバを構築する② 〜Apacheの設定〜

続いて、Apacheを入れてサーバにWebサーバ機能を持たせます。

(3/15追記:全体的に整理して、SSLの利用方法を追加しました)

<Apacheのインストール>

$ sudo yum install httpd

yumに-yオプションをつけると質問項目に関してすべてYesと答えたことになるので、インストールが勝手に進むらしいのだけれど、一応確認しながらやりたいのでオプションはつけません。

$ yum list installed | grep httpd

でインストールされたことを確認する。

<サーバ起動時のApacheの自動起動設定>

サーバ起動時に自動的にhttpdが起動するようにランレベルを設定する。ランレベルとはLinuxのOSの状態を数値として表現したもので、数字の0から6までの7種類に分類される。

  • 0 … システムの停止
  • 1 … シングルユーザモード(メンテナンス用ランレベル)
  • 2 … マルチユーザモード(NFSマウントなし)
  • 3 … マルチユーザモード(テキストログイン) 
  • 4 … 未使用
  • 5 … マルチユーザモード(グラフィカルログイン)
  • 6 … システムの再起動

セキュリティを考慮するときは、余分なサービスの起動していないランレベル3を使用する。なお、システムの現在のランレベルは

$ /sbin/runlevel

で確認可能。httpdの現在の起動ランレベルは、以下で確認可能。

$ chkconfig --list httpd

おそらくすべてoffになっているので、以下でランレベルを設定。

$ sudo chkconfig --level 3 httpd on

先のコマンドで、レベル3がonになったかを確認する。

<Apacheの手動start/stop>

手動でApacheをON/OFFするときには、以下のようにする。

$ sudo /etc/init.d/httpd start
$ sudo /etc/init.d/httpd stop

なお、Apacheサーバを起動/停止/再起動させるための以下のコマンドも存在する。

$ sudo /usr/sbin/apachectl start
$ sudo /usr/sbin/apachectl stop
$ sudo /usr/sbin/apachectl restart

さらにいうと、以下のコマンドも存在する。

$ sudo service httpd start
$ sudo service httpd stop
$ sudo service httpd restart

とりあえずはどれを使ってもよい。Apacheが実行中か否かは、以下で確認できる。

$ sudo service --status-all | grep httpd

<Apacheの設定のための基礎知識>

  • Apache全般に関する設定はhttpd.conf、ディレクトリ単位で設定が必要は場合は.htaccessを使う。
  • Apacheは設定の一部を外部ファイルに分割できる。
  • デ フォルトのhttpd.conf以外の名称を利用するには、Apacheを起動する際のオプションに「-f /..フルパス../新しい設定ファイル」を追加する。任意の設定ファイルを読み込むことができるため、テスト用の設定ファイルと、本番用の設定ファイル を切り換えて使用することができる。
  • httpd.confを修正した場合は、それを反映させるためにApacheの再起動が必要。 Apacheは処理中のリクエストの完結を待って再起動できるため、接続中のクライアントを切断することなく、新しい設定を反映させることができる(# /usr/sbin/apachectl -k graceful)
  • .htaccessでは、ディレクトリをアクセス不能にしたり、パスワードを設定して閲覧可能なユーザを限定できたりする。管理者権限でhttpd.confを触れないような、ホスティングサービスやレンタルサーバのような共有型のサービスで利用できる。
  • .htaccessは、自分が存在しているディレクトリと、その配下のサブディレクトリについて、httpd.confの内容を.htaccessの記載内容で上書きする。何が上書きできるかを決めるのは、httpd.confでのAllowOverrideディレクティブ指定。許可されていない設定を.htaccessでやってしまうと、”500 Internal Server Error”になる。
  • .htaccessに対する追加や修正は即座に反映されるため、Apacheの再起動は不要。
  • .htaccess は多用するとサーバの処理能力が低下する。.htaccessが有効になっていると、例えば「http://~.jp/a/b/c.html」にアクセス すると、~.jp, a, bの各フォルダごとに.htaccessファイルが存在しないかのチェックが入る。マルチユーザ環境(いろんな人がサーバの設定をする環境)のような特別 な理由がないなら、.htaccessは無効にして、代わりにhttpd.confの中でDirectoryやLocatioのコンテナを使ってディレク トリやURL単位の設定をする。.htaccessを無効にするための記述は、AllowOverride None
  • htpd.confや.htaccessでは行単位で設定を行い、1行で1つの事柄を設定する。1行の先頭に設定項目を記述し、設定内容を記述する。Apacheでは、こうした記述子をディレクティブという。ディレクティブと引数の間は、1つ以上の空白文字かタブ文字を挿入する。引数を複数指定する場合は、空白文字で仕切る。引数の中に空白を含む場合は、””でくくる。引数は大文字と小文字は区別されるが、ディレクティブは区別されない。
  • サーバ全体に対して設定を行うなら、どこに設定を追加しても構わない。 特定のディレクトリやURLなどに設定を限定する場合は、〜のようなブロック形式で対象を限定する。このようなブロックを作るもの (<>〜</>)をコンテナ指示子という。Directory、Files、Locationなどが存在。引数部分にはワイルド カード(*)使用可能。正規表現を使用する場合は、DirecrotyMatch、FilesMatch、LocationMatchを使う。他に Limit、Proxy、VirtualHostなどがある。コンテナ指示子はネスト可能。
  • ディレクティブによって組み合わせ可能なコンテナ指示子や、記述可能な設定ファイルに限りがある。組み合わせ可能なコンテナ指示子や設定ファイルのことをコンテキストという。
  • コメントは#。ただし、ディレクティブの後ろに#でコメントをつけることはできない。先頭#のみ有効。
  • Includeディレクティブを使えば、外部設定ファイルを読み込むことができる。ワイルドカード指定もできるが、予期せぬファイルを読み込んでエラーにならないよう注意。読み込む順番が重要になるものもあるので、出来る限り個別に指定する。
  • ディレクティブが使用できるかどうかは、インストールされている拡張モジュールによって決まる。 必要な拡張モジュールがインストールされていない状態で設定しようとすると、Apache起動時や設定ファイルのチェックでエラーになる。設定されたモ ジュールがインストールされている場合にのみ設定を有効にするには、IfModuleコンテナを使用する。引数の前に”!”をつければ、インストールされ ていないことを条件にすることもできる。
  • ディレクティブの引数では、HTTP_USER_AGENTなどの環境変数を使用することがで きる。予め用意されている環境変数名以外に、SetEnvディレクティブで自由に設定することが可能。SetEnvIfディレクティブを使えば、条件に基 づいて環境変数を設定できる。例えば、クライアントのIPアドレスやドメイン名でアクセスを制限したり、特定のクライアントがアクセスしたときだけログを 出力するように設定できる。

<Apacheの設定の基本>

Apache全般の設定は、基本的にhttpd.confを編集することで行う。yumでインストールした場合には、/etc/httpd/conf/httpd.confとなる。また、ディレクトリごとの設定が必要な場合は、.htaccessで設定する。

まず初めに、Apache Killer対策とやらを行う。何でも2.2.19以前のApacheには脆弱性があり、CentOSのyumで提供されるApacheはApace2.2.15なので、自分で対策を施す必要があるとのこと。

対策には二つのモジュール(mod_setenvifとmod_headers)が必要なので、まずそれが入っているかを確認。

$ /usr/sbin/httpd -M | grep headers
$ /usr/sbin/httpd -M | grep setenvif

それぞれ、headers_module (shared), setenvif_module (shared)が表示されればOK。/etc/httpd/conf/httpd.confの末尾に以下を追記する。

 # Drop the Range header when more than 5 ranges.
 # CVE-2011-3192
 SetEnvIf Range (?:,.*?){5,5} bad-range=1
 RequestHeader unset Range env=bad-range

 # We always drop Request-Range; as this is a legacy
 # dating back to MSIE3 and Netscape 2 and 3.
 #
 RequestHeader unset Request-Range

 # optional logging.
 CustomLog logs/range-CVE-2011-3192.log common env=bad-range

追記内容に構文ミスがないかは、以下のどちらかで確認する。

$ /usr/sbin/httpd -t
$ /usr/sbin/apachectl configtest

“Syntax OK”が出ればOK。なお、Apache Killerへの正式な対処方法はこちらに記載されている。

<Apacheの動作確認>

ここまでのhttpd.confの修正を反映させるには、Apacheを再起動させる必要がある。

$ sudo service httpd restart

さて、これでクライアントPCからブラウザで”http://192.168.xxx.xxx/”にアクセスすれば、Apacheのテストページが見れるハズ…なのに、見れない。http.confの中の設定を見直した結果、デフォルトではServerNameの指定がコメントアウトされていたのでこれを有効にしたが、それでもダメ。

試しにiptablesを無効にしてみる。

$ sudo /etc/init.d/iptables stop

結果、アクセスできたので、これが原因。

/etc/sysconfig/iptablesを見ると、tcpは22番(もしくは、先に自分で設定したssh用のポート番号)しか有効になっていないようだったので、新たに80番も受け付けるように追記する。

-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT

意味は、「接続状態が”NEW”かつプロトコルが”tcp”かつ送信先ポートが”80″ならば、通信を許可する」。

これで再度iptablesを有効にすると、今度はiptablesが有効な状態でもブラウザからアクセスできた。

$ sudo /etc/init.d/iptables start

ちなみに、エラーログは”/etc/httpd/logs/error_log”に吐き出されているので、Apacheに何か異常があれば、とりあえず確認した方がいい。

<Apacheのセキュリティ設定>

何はともあれセキュリティ。個人のローカルネットワークで作業している分には後回しでも構わないけれど、会社のサーバを立てようものなら、真っ先にセキュリティから設定しないといけない。下手すると、非公開にすべき情報があっというまにGoogleの検索対象に引っかかったりする(自分がそれをやりかけて冷や汗をかいた)。全然充分ではないかもしれないけれど、とりあえず簡単にやれることだけやっておく。

  • サーバ情報の隠蔽(Apacheのバージョンなどを知られないように)
    • http.confで、ServerTokensディレクティブをProductOnlyに設定
  • デフォルトのページを置き換える(OS等を類推されないように)
    • ドキュメントルートに空のindex.htmlを用意
  • .htaccessを無効にする(勝手にで)
    • http.confを確認すると、デフォルトでこの設定になっていた(<Directory />コンテナ内で、”AllowOverride None”指定)

他にも、ドキュメントルートの変更や、画像の直リンク禁止なども本当はやっておいた方がよい。

<Basic認証の設定>

公開を制限しなければならない情報があるときには、その情報(htmlファイル等)が入ったフォルダを公開フォルダ(デフォルトなら、/var/www/html)に持って行く前に、先にBasic認証もしくはDigest認証の設定をしてから公開フォルダに持っていった方が安全。どちらも設定の手間はそんなに変わらない上に、Digest認証の方が安全なので、通常はDigest認証を使えばよい気がする。でも一応Basic認証もできるようにしておく。

Basic認証を利用するには、以下の3つのモジュールが必要。

  • mod_auth_basic
  • mod_authn_file
  • mod_authz_user

httpd.confの初期状態でも、LoadModuleでロードされているはず。されていなければ、自分でインストールしてロードする。

トップページからBasic認証をかけたい場合は、以下をhttpd.confの末尾に追記すればOK。

<Directory "/var/www/html/">
    AuthType Basic
    AuthName "Restricted Resource"
    AuthUserFile /etc/httpd/conf/htpasswd
    Require user ユーザ名
</Directory>

AuthNameは保護された領域に対するニックネームなので、好きに設定すればOK。これは”realm”(レルム)と呼ばれるもので、クライアントに渡されて、ユーザ名とパスワードと一緒に保護される。また、認証のためのユーザ名とパスワードの入力受付時も、このrealmの内容が表示される。

なお、Require userで複数のユーザ名を指定したい場合は、”Require user user1 user2″のように書く。

次に、AuthUserFileで指定した、ユーザ情報を記録するファイルを作成する。

$ sudo htpasswd -c /etc/httpd/conf/htpasswd ユーザ名

パスワードを要求されるので、設定したいパスワードを入力する。

以上で設定完了。Apacheを再起動させて、トップページ(”http://192.168.xxx.xxx/”)にアクセスして認証の要求と承認が確認されればOK。

Basic認証では、クライアントは、HTTPリクエストのAuthorizationヘッダの中に「ユーザ名:パスワード」をBase64で符号化したものを入れてサーバに送る。Base64なので、例えばLinuxのbase64コマンドで簡単に復号できてしまうため、トラフィックを傍受されると簡単にユーザ名とパスワードがばれてしまう。

<Digest認証の設定>

Basic認証と比べて、利用するモジュールが一つだけ変わる。

  • mod_auth_digest
  • mod_authn_file
  • mod_authz_user

これらも初期設定でロードされているはずだが、なければ自分でインストールする。

トップページからDigest認証をかけたい場合は、以下をhttpd.confの末尾に追記すればOK。

<Directory "/var/www/html/">
    AuthType Digest
    AuthName "Restricted Resource D"
    AuthUserFile /etc/httpd/conf/htdigest
    Require user ユーザ名
</Directory>

Basic認証と比べ、AuthTypeとAuthUserFileが変更される。AuthUserFileで参照するファイルは、以下のように作成する。

$ sudo htdigest -c /etc/httpd/conf/htdigest "Restricted Resource D" ユーザ名

“Restricted Resource D”の部分は、先にAuthNameで設定した名前に合わせる。パスワードを要求されるので、設定したいパスワードを入力する。

以上で設定完了。Apacheを再起動させて、トップページ(”http://192.168.xxx.xxx/”)にアクセスして認証の要求と承認が確認されればOK。

詳しくは調べていないけれど、Digest認証ではユーザ名とパスワードをそのまま垂れ流すのではなく、ハッシュ値を利用するとのこと。

<SSLの設定>

最後に、サーバ⇔クライアント間のhttp通信で暗号化したデータをやりとりするためのSSL(Secure Sockets Layer)を設定する。

ApacheでHTTPSを実装するためには拡張モジュール”mod_ssl”が必要。”/usr/sbin/httpd -M | grep ssl”で引っかからなかったので、初期状態ではインストールされていない模様。ということで、インストールする。

$ sudo yum install mod_ssl

HTTPSの通信に必要なものは3つ。

  • server.key … 秘密鍵
  • server.csr … CSRファイル(公開鍵と、認証局での署名に必要な情報)
  • server.crt … サーバ証明書

すべてopensslコマンドで生成可能。

$ openssl genrsa -aes128 1024 > server.key

genrsaでRSA形式の秘密鍵を作成する。-aes128で、128ビットのAES方法での暗号化を指定。1024で、鍵のバイト長を指定。パスフレーズを求められるので、設定したパスフレーズを入力する。

$ openssl req -new -key server.key > server.csr

色々聞かれるが、公にするものでもないので適当に入力する。

  • Country Name … JP
  • State or Province Name … Kanagawa
  • Locality Name … Yokohama
  • Organization Name … HogeOrg
  • Organization Unit Name … HugaOu
  • Comman Name … 自分のサーバのIPアドレス
  • Email Address … hogehuga@example.jp
  • challenge password … 何も入力しない
  • An optional company name … 何も入力しない
$ openssl x509 -in server.csr -days 3650 -req -signkey server.key > server.crt

有効期限は、長めに10年程度にしている。server.keyのパスワードを聞かれるので、入力する。これで必要なファイルはできあがり。ここまでの一連の行動の意味については、このあたりが参考になりそう。まだ読めてないけれど。

次に、mod_sslの設定を行う。設定に使用するファイルは、/etc/httpd/conf.d/ssl.conf。これを、httpd.confから読み込ませるようにする。mod_sslのロードやバーチャルホストの設定は、ssl.confの中に予め記載されている。httpd.confは初期状態で”Include conf.d/*.conf”となっているので、何もしなくても勝手に読み込んでくれるが、必要であれば個別に読み込ませる。

/etc/httpd/conf.d/ssl.confで重要になるのは、以下の2つのディレクティブ。

  • SSLCertificateFile
  • SSLCertificateKeyFile

これらの値として設定されているパスを、それぞれ自分が用意したserver.crtとserver.keyを指すように修正する。server.crtとserver.keyの置くべき場所は色々あるらしいが、さしあたりこの方と同じように設定する。/etc/httpd/conf 配下にssl.keyディレクトリとssl.crtディレクトリを作成し、その中にそれぞれserver.keyとserver.crtを放り込む。さらに、ディレクトリと各ファイルのパーミッションも変える。

  • ssl.keyフォルダ … オーナーのみ自由にできるように700
  • server.key … オーナーのみが参照できるように400
  • ssl.crtフォルダ … オーナー以外にも公開するため755
  • server.crt … オーナー以外も読めるようにするため644

これに合わせて、/etc/httpd/conf.d/ssl.confの2つのディレクティブを設定する。

  • SSLCertificateFile … /etc/httpd/conf/ssl.crt/server.crt
  • SSLCertificateKeyFile … /etc/httpd/conf/ssl.key/server.key

これで必要な設定は終わったはずなので、httpd.confの構文チェックをして、Apacheを再起動させる。

$ sudo /usr/sbin/httpd -t
$ sudo /etc/init.d/httpd restart

ところが、うまく動かない。/etc/httpd/logs/error_logを確認してみると、server.crtへのアクセスが拒否されたと出ているので、あれこれserver.crtや上位フォルダのパーミッションをいじってみたけれど、改善されない。どころか、パーミッションを777にしても拒否される。

これで直接のパーミッションの設定が原因ではないことがわかったので、他の原因を探った結果、SELinuxとやらが関係してそうだということがわかった。現在のSELinuxのモードを

$ getenforce

で確認してみると、”Enforcing”と出たので、SELinuxが有効になっているらしい。試しに

$ sudo setenforce 0

でSELinuxをPermissiveモード(SELinuxパーミッションのチェックをして警告はするが、実際にアクセスの拒否はしない)にしてみると、Apacheの起動に成功した。

つまり、SELinuxを無効にしてしまえばApacheは普通に動くのだろうけど、SELinuxはLinux全体のセキュリティを担っているような気がしないでもないので、Apacheのためだけに無効にしてしまうのは危険な気がする。ということで、SELinuxが有効な状態でも動作するように頑張ってみる。

$ ls -Z

このコマンドで、各ファイルのタイプとやらがわかるらしい(タイプとはSELinuxの用語で、ファイルやディレクトリに割り当てられるラベルのこと)。いくつか調べてみた。

[root@xxxxx ssl.key]# ls -Z
-r--------. root root unconfined_u:object_r:user_home_t:s0 server.key
[root@xxxxx ssl.crt]# ls -Z
-rw-r--r--. root root unconfined_u:object_r:user_home_t:s0 server.crt
[root@xxxxx conf]# ls -Z
-rw-r--r--. root root unconfined_u:object_r:httpd_config_t:s0 htdigest
-rw-r--r--. root root unconfined_u:object_r:httpd_config_t:s0 htpasswd
-rw-r--r--. root root system_u:object_r:httpd_config_t:s0 httpd.conf

Basic認証やDigest認証のときに作ったファイル(htdigest, htpasswd)と比べると、server.keyとserver.crtはコロンで区切られた3つめがおかしい。この区切りは「ユーザ:ロール:タイプ」らしい。なるほど、3つめがタイプ。これをどうにかして他と合わせてやればよさそう。user_home_tになっているのは、server.keyとsercer.crtを作ったのがログインユーザのホームディレクトリで、それをmvで持ってきたからか?

色々調べた結果、以下のコマンドでタイプを変更できた。

[xxxx@xxxxx ssl.crt]$ sudo /sbin/restorecon -v server.crt
/sbin/restorecon reset /etc/httpd/conf/ssl.crt/server.crt context unconfined_u:object_r:user_home_t:s0->unconfined_u:object_r:httpd_config_t:s0

-vは変更前と変更後を示してくれるオプション。user_home_tがhttpd_config_tに変わっていることがわかる。同様にserver.keyのタイプも変えてやると、SELinuxが動作している状態(Enforcingモード)でもApacheが起動するようになった。

色々調べたことを箇条書きでメモしておくと、

  • 新規作成されたファイル・ディレクトリのタイプは親ディレクトリと同じ
  • mvコマンドでファイルを移動すると,ファイルのタイプは保存される
  • Apache(httpd_tドメイン)は,user_home_tのタイプにアクセスできない

この3つの条件が全部揃ってしまったために発生したのが、今回の問題。で、先ほど実行したコマンドは、元々のタイプ付与のルール(file_contextsに記載?)で改めてタイプを付け直す、というコマンド。つまり、サーバ証明書や秘密鍵を普通のユーザフォルダで作らずに、/etc/httpd/conf/とかで鍵やら証明書やらを作ればよかったのかな。そんなもんわかるかい。とりあえず、ここここが参考になった。

とにもかくにも、Apacheは立ち上がった。さっそく、ブラウザから”https://192.168.xxx.xxx/”にアクセスしてみると…「アクセスできません」と拒否られる。…今度は何だ。

これはもはや自分にとっておなじみになってきた、iptablesの設定が原因。/etc/sysconfig/iptablesに以下を追記する。

-A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT

これでブラウザ上からアクセスできるところまで確認。えらい時間かかってしまった。。。

最後に、SSL利用時に発生する、Apach起動時のパスフレーズ入力を省略するようにする。サービスが再起動を余儀なくされたときに、パスプレーズを入力しないと立ち上がらないのは困るので。方法としては、秘密鍵 (server.key) ファイルをあらかじめ復号化しておき、その復号化したファイルを使用するようにすればよいらしい。

# mv server.key server.key.org
# openssl rsa -in server.key.org > server.key

これで、Apacheを再起動しても自動的にhttpsが有効になるようになった。疲れた。。。

あと、現状ではhttp(80番ポート)とhttps(443番ポート)の両方が有効になっているので、httpsのみで運用したい場合は、80番ポートを閉じるのを忘れずに。/etc/httpd/conf/httpd.conf の中で、”Listen 80″をコメントアウトします。

蛇足で、気になっていたことを一つだけメモ。

/etc/httpd/cof.d/ssl.confの中の、<VirtualHost _default_:443>の記述。ここはいつもこれでいいのか?ここここはよく読んだ方が良さそう。

VirtualHostの引数になりうるのは、以下の3つ。

  • バーチャルホストの IP アドレス
  • NameVirtualHost * と共に使われる、すべてのIPアドレスにマッチする文字 *
  • IP ベースのバーチャルホストで他のものにマッチしない IP アドレス のための文字列 _default_

今回のSSLではIPベースのバーチャルホストを使ったことになるので、IPアドレス(192.168.xxx.xxx)か、_default_で良かった。_default_は、他のバーチャルホストで明示的に挙げられていない すべての IP アドレスにマッチする。名前ベースでバーチャルホストを作る場合は、NameVirtualHostとVirtualHostの両方の引数を*にしなければならないかもしれない(未確認)。

とりあえずここまで。

今回のApacheの環境構築は、主に以下の書籍を参考にさせていただきました。

サーバ初心者からすると一見触りにくい感じの本に見えますが、一度Apacheを触り始めたらすぐにこのレベルの本が必要になると思います。内容も狭く深くというよりは、広くいろんな機能を実際に使えるように書いてくれているので、自分みたいな初心者にはおススメだと思います。