WebアプリケーションでのSSHで不具合を出さないためにknown_hostsを理解する
PHPでSSHを使ってファイルを転送する際にうまく転送できず,原因がknown_hosts周りだったので改めて調べてみた.うまくいかなかった事象・原因とその解決方法もまとめておく.なお今回はPHPを使用しているが,Rubyやシェルスクリプトでも同様の問題は起きうる.
known_hostsとは
一般にクライアントが公開鍵認証のSSHを使ってサーバへログインする際,通信を暗号化するためにサーバが公開しているホスト鍵を使って通信を始める.ホスト鍵とは,SSHで公開鍵認証や暗号化通信を使用するためにサーバが用意している秘密鍵と公開鍵のペアである.この記事では公開鍵のことをホスト鍵と呼ぶことにする.
known_hostsには「SSHで繋いだサーバのホスト鍵もしくはホスト鍵のフィンガープリント」が登録されている.デフォルトの設定だと初めてサーバにSSHでアクセスする際以下のように聞かれると思う.これにyes
と答えるとknown_hostsに接続先のサーバのホスト鍵もしくはフィンガープリントが登録される.
$ ssh user@192.168.100.200 The authenticity of host '192.168.100.200 (192.168.100.200)' can't be established. ECDSA key fingerprint is SHA256:qKZyNM7JGgEGH7QWLANKBpNlFwM1XT7i45Z5oB7V61Y. Are you sure you want to continue connecting (yes/no)? yes
二度目のアクセスからは「known_hostsに記録されているサーバのホスト鍵もしくはフィンガープリントと実際のホストが返すサーバのホスト鍵もしくはフィンガープリントが一致した場合にそのホストは認証済みである」と判断され確認メッセージは表示されない.ここでは割愛するがホスト公開鍵に対するホスト秘密鍵をサーバが所有していることを確認するには公開鍵暗号技術のチャレンジ&レスポンス方式を用いる.
known_hostsには「なりすまし」による第三者の介入を防ぐという重要な役割がある.二度目以降のSSHによるアクセスにおいてホスト鍵を検証することでホストを認証するので,誤って第三者が用意しているサーバに繋ぐことを防ぐ.
また,ワーム1のようなマルウェアの感染に対する緩和策としても有効である.要するに,初回のSSHに確認が求められるので,被害を受けたサーバから自動で違う新しいサーバへ接続できなくする効果があるというわけだ.
まとめるとknown_hostsとは「公開鍵認証のSSHでログインしたサーバのホスト鍵を記録し,次回からのホスト認証で利用されるファイル」である.
構成
今回はPHPのcmd関数を使って,SSHを用いたrsyncコマンドを実行してサーバAからサーバBへファイルを転送することを想定する.このプログラムはブラウザからApache経由でアクセスされ,プログラムの実行ユーザはwww-data
とする.
サーバAからサーバBへのSSHでは公開鍵認証を使用するため,サーバA上でsu -u www-data ssh-keygen
のようにwww-data
としてパスフレーズ無しの秘密鍵・公開鍵のペアを生成し,公開鍵をサーバBへ設定した.
起きた事象と原因
ブラウザでアクセスしてWebアプリケーションが動いてrsyncコマンドが実行されると,以下のようなメッセージがApacheのエラーログに出力された.
Host key verification failed. rsync: connection unexpectedly closed (0 bytes received so far) [sender] rsync error: error in rsync protocol data stream (code 12) at io.c(235) [sender=3.1.2]
ホスト鍵の検証に失敗している.冒頭で述べたように,「サーバにSSHで初めてアクセスする場合,クライアントのknown_hostsにホスト鍵を登録しなければならない」ためである.
解決策1
1つめの解決策は事前にサーバBへSSHでアクセスしておき,known_hostsにホスト鍵を登録しておく方法である.この方法ではサーバBのホスト鍵が何らかの理由で変わると,known_hostsで保存されているホスト鍵と一致しなくなりホスト鍵の検証に失敗するので注意する.また接続するサーバが増えた時も事前にSSHでアクセスしknown_hostsにホスト鍵を追加しておく必要があることに注意する.
解決策2
2つめの解決策は,クライアントのSSHの挙動を設定するファイルである/etc/ssh/ssh_config
のStrictHostKeyChecking
をno
とする方法である.これでサーバBへの初回アクセス時に対話的に聞かれることなく自動でknown_hostsにホスト鍵が追加される.StrictHostKeyChecking
のパラメータは以下のようになっていてデフォルトはask
である.2
StrictHostKeyChecking | 意味 |
---|---|
ask | 初回のSSHでサーバにアクセスする際にknown_hostsへサーバのホスト鍵を登録するか対話的に確認される |
yes | known_hostsへの新規追加はできず,既にknown_hostsで定義されているサーバへしかアクセスできない |
no | 初回のSSHでサーバにアクセスする際に自動でknown_hostsにホスト鍵を追加 |
StrictHostKeyChecking
をno
にする時は,利便性とセキュリティ面を考慮して設定しよう.
まとめ
- 公開鍵認証のSSHを使ったファイル転送を実現するのにknown_hosts周りを調べた
/etc/ssh/ssh_config
はマニュアルページにまだまだたくさんの設定項目がまとめられているので困ったら参考にしようと思う- known_hostsに追加されているデータが何なのか,追加・削除にどんな意味があるのか等の概要を理解できた