WebアプリケーションでのSSHで不具合を出さないためにknown_hostsを理解する

PHPSSHを使ってファイルを転送する際にうまく転送できず,原因が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へ設定した.

f:id:a-mochan:20191220000318p:plain
構成図

起きた事象と原因

ブラウザでアクセスして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つめの解決策は事前にサーバBSSHでアクセスしておき,known_hostsにホスト鍵を登録しておく方法である.この方法ではサーバBのホスト鍵が何らかの理由で変わると,known_hostsで保存されているホスト鍵と一致しなくなりホスト鍵の検証に失敗するので注意する.また接続するサーバが増えた時も事前にSSHでアクセスしknown_hostsにホスト鍵を追加しておく必要があることに注意する.

解決策2

2つめの解決策は,クライアントのSSHの挙動を設定するファイルである/etc/ssh/ssh_configStrictHostKeyCheckingnoとする方法である.これでサーバBへの初回アクセス時に対話的に聞かれることなく自動でknown_hostsにホスト鍵が追加される.StrictHostKeyCheckingのパラメータは以下のようになっていてデフォルトはaskである.2

StrictHostKeyChecking 意味
ask 初回のSSHでサーバにアクセスする際にknown_hostsへサーバのホスト鍵を登録するか対話的に確認される
yes known_hostsへの新規追加はできず,既にknown_hostsで定義されているサーバへしかアクセスできない
no 初回のSSHでサーバにアクセスする際に自動でknown_hostsにホスト鍵を追加

StrictHostKeyCheckingnoにする時は,利便性とセキュリティ面を考慮して設定しよう.

まとめ

  • 公開鍵認証のSSHを使ったファイル転送を実現するのにknown_hosts周りを調べた
  • /etc/ssh/ssh_configはマニュアルページにまだまだたくさんの設定項目がまとめられているので困ったら参考にしようと思う
  • known_hostsに追加されているデータが何なのか,追加・削除にどんな意味があるのか等の概要を理解できた