Webサービスのようなインターネットを介して利用するサービスが世の中に普及し,その利用者数が増大している.その結果,システム管理者は,サービス利用者からの多様な要求に応えるために,管理するシステムの構成を状況に応じて迅速かつ柔軟に変化させることが求められている[1], [2].たとえば,Webサービスの利用状況に基づき,サーバをスケールアウト・スケールイン[3], [4]することが要求される.Webサービスへの突発的なアクセス集中に対してサーバをスケールアウトさせ,アクセスが減少するとスケールインすることで,サービスの機会損失の低減や運用コストの削減ができる.また,サービスの機能拡張や規模拡大に伴い,新規サーバを導入[5]していくことも要求される.市場変化をいち早く察知し,サービスそのものを変化させていくことは,サービスの競争力を維持・向上させることにつながる.こうしたサービスを取り巻く環境の変化やサービスそのものの規模の変化に対して,迅速かつ柔軟にシステム構成を変化させながら対応していくことは,サービス成長を加速させるうえでの重要な要素となる.
システム管理者が迅速に変化に対応するためには,システム管理者や開発者(以下,ユーザ)に対して,システム変更が起きるたびに変更後の情報を提供することなく,自由にシステム構成を変更させられることが必要である.一方,サーバへの安全なリモート接続サービスとして広く利用されているSSH(Secure Shell)[6]は,ユーザが利用するサーバのIPアドレスまたはホスト名を指定して接続要求を送る必要があるため,ユーザは利用するサーバのIPアドレスまたはホスト名を把握しておかなければならない.そのため,もしサーバのIPアドレスやホスト名に変更があった場合,管理者は各ユーザへ変更後の情報を知らせる必要があり,ユーザはその変更に追従しなければならない.
現在,ユーザが接続するサーバのIPアドレス・ホスト名やその変更を意識することなくSSH接続ができる手法がいくつか存在する.1つは,クライアントツールがサーバごとの一意のラベル情報をもとに接続先のIPアドレスまたはホスト名を取得する手法であり,例として,GCP(Google Cloud Platform)のgcloudコマンド[7]が挙げられる.この手法を用いると,ユーザはシステムの構成情報やその変更を意識することなく,サーバに紐づいたラベル情報のみを用いて透過的に目的のサーバにSSH接続ができる.しかしながら,この手法では,ユーザに用いるクライアントツールを強制する,かつシステム側の仕様変更に伴いクライアントツールの動作にも変更が生じた場合に,全ユーザに変更を要求することになる.
他の手法として,クライアントとサーバの間に導入したプロキシサーバがリクエストの情報をもとに接続先のIPアドレスまたはホスト名を取得する手法がある.これを実現できる既存のSSHプロキシサーバとして,SSH Piper [8]がある.ユーザは,SSH Piperを介してSSH接続することで,用いるクライアントツールの制限や変更を要求されない,かつシステムの構成情報やその変更を意識することなく,ユーザ名を用いて透過的に目的のサーバにSSH接続ができる.しかしながら,SSH Piperはシステム管理者が接続先サーバを決定するロジックを変更するためにはソースコードに直接変更を加えなければならない.これにより,システム管理者がロジックに変更を加える際は,SSH Piperのソースコードを読み解いて内部の動作を把握したうえで変更箇所の特定とソースコードの修正を行う必要がある.さらに,ソースコードを直接修正した場合,元のSSH Piperにバージョンアップがあった際に,直接修正した箇所の動作に問題が起きないように更新を取り込む必要があるため,SSH Piperのバージョンアップへの追従が困難となる.以上の理由から,SSH Piperはシステムの仕様変更に対する拡張性が低い.
本稿では,ユーザに用いるクライアントツールの制限や変更を要求せず,システム管理者が自由に組み込み可能なフック関数を用いてシステム変化に追従できるSSHプロキシサーバを提案する.提案するSSHプロキシサーバはsshr [9]と名付けた.sshrは,ユーザ名から接続先サーバを決定するためのフック関数をシステム管理者が自由に実装・導入できる.これにより,ユーザ名を用いて透過的に目的のサーバにSSH接続できるため,接続先サーバのIPアドレスやホスト名に変更があった場合でも,管理者は各ユーザへ変更後の情報を知らせる必要がなく,ユーザも変更に追従する必要がない.さらに,sshrはSSHのセッション確立に必要なユーザ認証の処理を拡張するための関数も備えている.たとえば,sshrがユーザから公開鍵認証のリクエストを受けた際に,ユーザの公開鍵を検索する処理をフック関数として組み込み可能である.これにより,システム管理者がユーザの公開鍵をデータベースなどの自由なデータ形式で管理できる.このように,sshrではSSH特有の認証に対しても,プログラマブルに制御するための仕組みをとっている.
本稿の構成を述べる.2章では,既存手法とその課題について言及する.3章では,本稿での提案を述べる.4章では,提案手法を用いたシステムを構築する際に有用な知見を具体例を挙げて説明する.5章では,提案手法の性能評価の結果を述べる.最後に,6章でまとめとする.
本稿は,第12回インターネットと運用技術シンポジウム(IOTS2019)論文集に出版済みの論文[10]およびThe 15th IEEE International Workshop on Security, Trust & Privacy for Software Applications(STPSA 2020)に出版済みの論文[11]をもとに,既存手法や提案手法をシステムに適用する際に有用な知見等の説明を拡充したものである.
サービスへの多様な要求に応じてシステム構成を迅速に変更していくことが求められる状況においては,システムの運用管理も変更に追従できる必要がある.一方,サーバへのリモート接続サービスとして用いられるSSHでは,通常サーバのIPアドレスやホスト名に変更があった場合,管理者は各ユーザへ変更後の情報を知らせる必要があり,ユーザはその変更に追従しなければならない.本章では,このSSHの課題を解決する既存手法を整理し,それらの特徴と課題について述べる.
gcloudはGCP上のリソースを操作するコマンドラインツール[7]である.GCP上の仮想マシンであるインスタンスへのSSH接続は,「gcloud compute ssh」コマンドにインスタンス名を指定して実行することで可能である.「gcloud compute ssh」はssh(1)コマンド[12]のラッパーであり,SSHのリクエストを送る前にインスタンス名をキーにGCPが管理している構成情報からIPアドレスを取得する機能を有している.これにより,ユーザは自身で定義したサーバごとに一意のインスタンス名を知っていれば,対象サーバのIPアドレスの情報やその変更を意識することなく透過的にSSH接続が可能である.
また,他の手法として,HashiCorpが開発するクラスタ管理ツールであるConsul [13]を用いた手法が挙げられる.Consulはクラスタ内のメンバー管理機能と,そのメンバー情報を参照するためのHTTP APIを有している.これらを活用すると,ユーザが定義したサービス名やタグ情報をもとに特定サーバのIPアドレスを取得できるため,この取得した情報をもとにSSHリクエストを送るようなクライアントツール[14]を作成可能である.
いずれの手法においても,ユーザはサーバごとにラベル情報を持たせられるため,同一ユーザで複数のサーバに対して,ラベル情報を用いた透過的なSSH接続が可能である.もし,目的のサーバのIPアドレス等に変更があった場合でも,ユーザはその変更を意識することなく,クライアントツールが変更に追従する役割を担うことができる.一方,これらの手法は,ユーザ側に独自のSSHクライアントツールの使用を強制する.そのため,システム側の仕様変更などに伴いクライアントツールの動作にも変更が生じた場合に,全ユーザが使用しているツールに対してバージョンアップ等の変更を課す.したがって,ユーザがシステム側の仕様変更を意識する必要があることや,その変更に対して追従しなければならない点が課題として挙げられる.
SSH Piper [8]とsshmuxd [15]は,オープンソースソフトウェアとして開発されているSSHプロキシサーバである.両者に共通の特徴は,SSHのリクエストを受けた際に,そのユーザ名から利用する接続先サーバを決定できる点である.これらのプロキシサーバを介することで,ユーザはプロキシサーバのIPアドレスまたはホスト名さえ知っていれば,接続するサーバのIPアドレス・ホスト名やその変更を意識することなく,ユーザ名に紐づいたサーバにSSH接続ができる.両者とも接続先の決定の役割をユーザ側ではなく,プロキシサーバ側に持たせたことで,ユーザが用いるツールの制限や変更を課す必要がない.しかし,これらのプロキシサーバは,システム管理者がユーザ名から接続先サーバを決定するロジックを変更するためにはソースコードに直接変更を加えなければならない.これにより,接続先の決定に用いるユーザとサーバの紐付け情報を管理するデータ形式をソースコードの変更なしでは自由に選択できない.たとえば,SSH Piperでは,ユーザとサーバの紐付け情報をデータベースで管理する場合,指定されたスキーマのテーブルを用意する必要がある.sshmuxdでは,ユーザとサーバの紐付け情報をJSONもしくはYAML形式のファイルに記述する必要がある.もし,これらの接続先決定のロジックを既存の仕組みから拡張したい場合には,ソースコードを読み解いて内部の動作を把握したうえで変更箇所の特定とソースコードの修正を行わなければならない.さらに,ソースコードを直接修正した場合,元のソフトウェアにバージョンアップがあった際に,直接修正した箇所の動作に問題が起きないように更新を取り込む必要があるため,元のソフトウェアのバージョンアップへの追従が困難となる.
また,SSH Piperやsshmuxdのような接続先の決定が可能なSSHプロキシサーバを構築する手法として,sshd [16]を拡張する手法が挙げられる.sshdは,現在最も広く利用されているSSHサーバのデーモンプログラムである.プロキシサーバを一から実装する場合と比べて,sshdを拡張することで,認証の仕組みを独自に実装する必要がなく,sshdの信頼性の高い認証の仕組みを再利用できる利点がある.しかしながら,sshdはモジュールによる機能拡張の仕組みをサポートしていないため,その機能を拡張するためには,ソースコードに直接変更を加えなければならない.
いずれの手法においても,ユーザが用いるツールの制限や変更を課す必要がなく,ユーザ名を用いて目的のサーバに透過的なSSH接続が可能である.もし,目的のサーバのIPアドレス等に変更があった場合でも,ユーザはその変更を意識することなく,プロキシサーバが変更に追従する役割を担うことができる.一方,接続先決定のためのラベル情報として用いられるものがユーザ名に限られる点は,プロキシサーバを用いた場合の制約となる.同一ユーザを複数サーバに紐づける場合は,ユーザ名にサーバのホスト名などの情報を付与し,それらの情報をプロキシサーバが読み取る方法がある.しかし,既存のSSH Piperやsshmuxdではユーザ名に付与された情報を読み取る機能がないため,同一ユーザは1サーバにしか紐づかない.また,既存のプロキシサーバを構築する手法は,機能拡張の仕組みをサポートしていないため,仕様変更に対する拡張性が低いことが課題として挙げられる.
2.1節で述べたとおり,システムの構成変更に追従する役割をクライアントツール側が担う場合は,ユーザに用いるクライアントツールの制限や変更を要求することが課題である.一方,2.2節で述べたとおり,変更に追従する役割をプロキシサーバが担う場合は,ユーザに制限や変更を要求しないが,仕様変更に対するプロキシサーバの機能の拡張性が低いことが課題である.これらの課題を解決するために,ユーザに用いるクライアントツールの制限や変更を要求せず,システム管理者が自由に組み込み可能なフック関数を用いてシステム変化に追従できるSSHプロキシサーバを提案する.提案手法では,システム側に仕様変更があった場合でも,ユーザにその変更を意識させることなく,プロキシサーバに組み込むフック関数のみの修正でプロキシサーバの動作を拡張し,変更に対応可能である.
プロキシサーバを用いた手法の制約として,2.2節で述べたとおり,接続先決定のためのラベル情報として用いられるものがユーザ名に限られることは,提案手法においても同様である.しかしながら,提案手法ではユーザ名を用いた接続先決定の処理をフック関数として自由に実装できるため,1つのユーザ名に対して複数のサーバを紐付けておき,状況に応じて適切なサーバを割り当てることが可能である.このように提案手法では,フック関数を自由に実装・導入することでプロキシサーバの動作を拡張できるため,ユーザとサーバの管理方法に対するシステム管理者の選択性が高い.一方,クライアントの立場では,同一ユーザを用いて複数のサーバの中から意図的に接続先を指定して接続要求を送ることはできない.この点については,提案手法においても既存の課題である.
提案手法では,SSHプロキシサーバに対して,システム管理者が実行したい処理を自由に実装し,フック関数として組み込めるアーキテクチャをとった.図1は,提案手法の概要図である.SSHプロキシサーバがクライアントからの接続要求を受け取り,接続するサーバを決定することで,ユーザはプロキシサーバのIPアドレスまたはホスト名さえ知っていれば,プロキシサーバより後方のシステムの構成やその変更を意識する必要がなくなる.システム管理者の視点では,ユーザに事前に提供するプロキシサーバのIPアドレスやホスト名にさえ変更が起きなければ,ユーザに知らせることなく自由にシステム構成を変更できる.
SSHでサーバに接続する場合,ユーザが目的のサーバを利用する権限を持つかどうかを確認するために,ユーザ認証を行う必要がある[17].提案手法では,認証方式としてパスワード認証および公開鍵認証をサポートしている.プロキシサーバを介してユーザ認証を行う場合にプロキシサーバがクライアント・サーバ間の認証をどのように仲介するかは用いる認証方式で異なる.パスワード認証の場合,プロキシサーバはクライアントから受けた認証リクエストを解釈することなく,SSHサーバにそのままリクエストパケットを送り,SSHサーバ側で認証を行うことができる.一方,公開鍵認証を用いた場合,プロキシサーバは認証をSSHサーバに任せることはできない.なぜなら,SSHセッションはセッションID [17]と呼ばれるセッションごとに一意の識別子を有しており,公開鍵認証では,このセッションIDを含めたデータを秘密鍵で暗号化し,認証リクエストを送るためである.プロキシサーバは,クライアント間とサーバ間の2つのSSHセッションを保持しており,当然ながらそれぞれ異なるセッションIDを有している.このため,プロキシサーバを介した公開鍵認証を行いたい場合,クライアント・プロキシサーバ間およびプロキシサーバ・SSHサーバ間の2段階で認証を行う必要がある.
提案手法では,SSHプロキシサーバに対して,特定の役割をもつ複数のフック関数を組み込み可能であり,それらの関数がSSHリクエストの処理の過程で順次実行される仕組みをとっている.組み込み可能なフック関数として,SSHのリクエストを受けた際にユーザ名をもとに接続先サーバのIPアドレスまたはホスト名を取得する関数がある.その他に,決定した接続先サーバとクライアント間の公開鍵認証を拡張するためのフック関数も備えている.具体的には,クライアント・プロキシサーバ間の認証において,クライアントの公開鍵を検索する関数,およびプロキシサーバ・SSHサーバ間の認証に用いる秘密鍵を検索する関数がある.これらを用いると,SSHプロキシサーバにおける接続先の決定および公開鍵認証をプログラマブルに拡張でき,システム管理者はユーザとサーバの紐付け情報や,認証に用いる公開鍵を自由なデータ形式で管理できる.
提案するアーキテクチャを実現するために,Go言語[18]を用いてsshr [9]というSSHプロキシサーバを実装する.提案手法は,フック関数を用いてプロキシサーバの動作を拡張する設計を採用しているため,その実装であるsshrでは,バージョンアップの際もシステム管理者が利用するフック関数のインタフェースの互換性を保つように継続的に実装する.sshrを実装するための要件を整理すると,まずsshrは複数のクライアントから同時に受けたリクエストを並行で処理できる必要がある.また,sshrがSSHのリクエストを処理するためには,SSH特有の認証の仕組みをサポートしなければならない.Go言語は,並行処理の記述が容易[19]であるという特徴を有するため,1つ目の要件を満たす.さらに,Go言語は,豊富な機能をもつSSHパッケージ[20]が存在するため,SSHプロキシサーバを実装するうえで必要な認証等の多くの機能の実装に,SSHパッケージに備わっている機能を利用できる.これらの実装面の要件に加えて,運用面の要件も存在する.sshrはシステムの多様な変更に合わせて動作を変更することが想定されるため,その変更から本番環境への適用が容易に行える必要がある.Go言語ではソースコードをビルドすることで,単一の実行ファイルが作成される.ビルドに必要な開発環境は,Dockerなどのコンテナ技術を用いることで容易に準備でき,システム管理者間で統一できる.さらに,作成される実行ファイルは,依存する外部ライブラリの個数が少ない,かつ環境変数の設定次第では標準Cライブラリであるglibcに依存しない.これにより,実行ファイルのサーバへのデプロイが容易であることや,デプロイ先のサーバに特別な実行環境を必要とせず可搬性が高いなどの利点がある.そのため,たとえばコード管理をGitで行い,リモートリポジトリへのプッシュを起点に,ビルド,デプロイ,実行までの一連の処理をCIツールで容易に自動化できる.このようにプログラムの修正から実行までを開発プロセスに組み込むことで,保守性や開発効率を向上できる.これらの実装面および運用面の理由からsshrの実装にGo言語を採用した.
2.2節で述べたとおり,既存のSSH Piperやsshmuxdは,システム管理者がその動作を拡張する際に,ソースコードを読み解いて内部の動作を把握したうえで変更箇所の特定と修正が必要であること,さらに,ソースコードを直接修正した場合,元のソフトウェアにバージョンアップがあった際に,直接修正した箇所の動作に問題が起きないように更新を取り込む必要があるため,元のソフトウェアのバージョンアップへの追従が困難となることが課題である.これらの課題を解決するために,sshrは,システム管理者がGo言語で実装したフック関数を組み込んでビルドすることで,プロキシサーバの機能を拡張できる仕組みをとっている.図2は,接続先の決定処理を組み込むためのサンプルコードである.図中のsshrパッケージのFindUpstreamHookに対してシステム管理者側で独自に実装した関数を渡すことでフック関数として組み込むことができる.この仕組みにより,sshrではシステム管理者がプロキシサーバの動作を拡張する際に,ソースコードを読み解き内部の動作を把握する必要がなく,sshrに組み込むフック関数の実装のみを意識すればよい.さらに,sshrにバージョンアップがあった場合も,フック関数はsshrの実装から独立しているため,バージョンアップへの追従も容易である.
他の機能拡張の仕組みとして,ApacheやNginxのようにモジュールを用いて拡張する方法や,本体から独立した拡張コードをプロセス間通信を介して呼び出す方法が挙げられる.フック関数を用いたsshrの拡張の仕組みでは拡張コードを修正するたびにソースコード全体をビルドする必要がある一方,他の2つの方法では,拡張コードをsshrのソースコードと分離してビルドすることができる.しかし,前述のようにソースコードの管理をGitで行い,ソースコードの更新を起点にビルドやデプロイなどの一連の処理をCIツールで容易に自動化できるため,修正のたびにソースコード全体をビルドすることは問題とならない.これに加えて,他の2つの方法では,複数の実行ファイルを管理する必要があるが,sshrでは拡張コードも含めて単一の実行ファイルであるため,実行ファイルのデプロイや管理が容易である.また,sshrでは拡張コードの実装をGo言語に限定するが,他の2つの方法では,拡張コードを実装する言語の選択肢が広いことが利点である.しかし,拡張コードを他言語で実装する場合,sshrを実行するサーバに言語の実行環境が必要であることや,開発の中で複数言語の更新への対応が必要となるため,開発や保守の過程で実行環境の整備や複数言語の更新への追従に工数を要する.以上より,sshrは2.2節で述べた既存のSSHプロキシサーバの機能拡張の課題を解決したうえで,実行ファイルのデプロイや管理が容易で実行環境の整備や言語の更新への対応の工数が少ないフック関数を用いた機能拡張の仕組みを採用する.
sshrでは,以下の3つのフック関数をサポートしている.
(1)は,ユーザ名をもとに接続先サーバのIPアドレスまたはホスト名を取得する関数である.(2)・(3)は公開鍵認証を用いる場合に必要なフック関数である.(2)は,ユーザの公開鍵を検索する関数である.(3)は,sshrから接続先サーバに対して認証を行うために利用する秘密鍵を検索する関数である.(2)・(3)の用途については後述する.
SSHプロキシサーバを実装するうえでの重要な課題として,プロキシサーバを介したクライアント・サーバ間の認証をいかにして行うかという点が挙げられる.3.1節で述べたとおり,公開鍵認証を用いた場合,クライアント・sshrサーバ間およびsshrサーバ・SSHサーバ間の2段階で認証を行う必要がある.ここで,sshrサーバを介した認証では,クライアントの認証を一段階目のクライアント・sshrサーバ間で行い,sshrサーバで認証が通ったクライアントはsshrサーバが決定するSSHサーバへ接続する権限を持つという前提を置いている.この前提は,sshrサーバとSSHサーバのシステム管理者が同一で,SSHサーバがsshrサーバからのSSH接続を信頼できる環境において,sshrサーバが第三者から乗っ取られない限りは問題ない.したがって,sshrサーバを利用する際は,sshrサーバとSSHサーバのシステム管理者が同一であることが制約条件となる.
まず,クライアント・sshrサーバ間において,sshrサーバはクライアントの公開鍵認証を行うために,クライアントの公開鍵を検索するためのフック関数であるFetchAuthorizedKeysHookを備えている.これは,sshdのAuthorizedKeysCommand [21]に相当する機能である.これを用いることで,システム管理者はユーザの公開鍵をデータベースなどの自由なデータ形式で一元的に管理できる.次に,sshrサーバ・SSHサーバ間において,sshrサーバはクライアントの秘密鍵を有していないため,別の秘密鍵を用いる必要がある.クライアントが目的のサーバにSSH接続をする妥当性については,一段階目のクライアント・sshrサーバ間で認証を行っているため,sshrサーバ・SSHサーバ間については,sshrサーバが用いる特定の秘密鍵をSSHサーバですべて許可することで,2段階目の認証を行う.この際に,sshrサーバがSSHサーバへの認証に用いる秘密鍵を検索するためのフック関数としてFetchPrivateKeyHookを備えている.このフック関数により,システム管理者はsshrサーバ・SSHサーバ間の公開鍵認証に用いる秘密鍵の管理方法を選択できる.例として,sshrサーバのローカルストレージにある1つの秘密鍵を用いることや,クライアントごとに用意した秘密鍵を暗号化してデータベースで管理することが挙げられる.このような認証方式を用いることによるセキュリティリスクについては次節で言及する.
プロキシサーバを介した認証に成功し,SSHセッションを確立した後のsshrの動作を説明する.sshrが実行するフック関数は,すべてSSHセッション確立前に実行されるものである.セッション確立後は,sshrはクライアントやサーバから受け取るパケットを解釈することはなく,パケットをそのまま転送するのみである.このため,sshrを用いた場合,セッション確立時にフック関数の実行によるオーバーヘッドが発生する.この点は,5章で評価する.
まず,sshrを用いたSSH接続において,中間者攻撃[6]を受けるリスクとその対策について議論する.OpenSSHを用いた通常のSSH接続では,中間者攻撃への対策として,接続するSSHサーバのホスト鍵が変更した場合,警告が表示される.一方,sshrを介するSSH接続では,システム管理者はクライアントに知らせることなく自由にクライアントが接続するSSHサーバを変更できること,かつクライアントは接続するSSHサーバのIPアドレス・ホスト名やその変更を意識することなく,ユーザ名に紐づいたサーバに透過的に接続できることを目指している.そのため,クライアントが接続するSSHサーバが変更した場合,それに伴うホスト鍵の変更をクライアントに知らせることを想定していない.これにより,もしsshrとSSHサーバの間に中間者としてシステム管理者が想定しないサーバが介入した場合,クライアントはそのことを知る術がないため,中間者によるなりすましや盗聴などのリスクがある.したがって,システム管理者は,sshrおよびSSHサーバを組織内の同一ネットワークに配置するなどして,外部から不正な中間者が入り込まないように対策を行う必要がある.
次に,sshrを用いた場合における第三者から不正アクセスを受けるリスクについて議論する.認証方式としてパスワード認証を用いた場合,sshrサーバはパスワードを読み取ることなく,SSHサーバにパケットを転送する.すなわち,ユーザのパスワードを知っていて,認証を行うことができるのはSSHサーバのみであるため,sshrサーバを用いた場合でも認証を突破されるリスクは変わらない.
公開鍵認証を用いた場合,クライアント・プロキシサーバ間およびプロキシサーバ・SSHサーバ間のセッションごとに2段階で認証を行う必要がある.sshrの認証の仕組みは,3.2節で述べたとおり,sshrサーバからSSHサーバへの公開鍵認証に特定の秘密鍵をすべて受け入れる設定をSSHサーバに行っている.このため,sshrサーバのセキュリティの問題やsshrデーモンの脆弱性などが原因で,sshrサーバが有している秘密鍵が流出した場合や,sshrサーバから秘密鍵を利用した任意のコマンドが実行される場合,SSHサーバへのすべてのSSH接続を許してしまうこととなる.
これらのリスクを最小化するための対策をsshrサーバとSSHサーバのそれぞれについて述べる.まず,sshrサーバでは,保有する秘密鍵の流出を防ぐためにサーバのセキュリティを堅牢にする必要がある.たとえば,sshrサーバではログイン可能なユーザ数や権限を最小限に留めることや,アクセスが可能なIPを組織内のネットワークに絞ることが有効である.次に,sshrを経由して任意のコマンドが実行される場合に備えて,sshrデーモンはroot権限を持たないユーザで実行する.この対策は,任意のコマンドが実行された場合にsshrサーバに対する操作を制限することには有効であるが,コマンドがsshrデーモンと同じ権限で実行されているため,SSHサーバに対する操作は可能となる.
次に,SSHサーバにおけるセキュリティ対策について述べる.まず,SSHサーバではrootでリモートログインを許可しない設定をすることが必要である.たとえば,sshdではsshd_configのPermitRootLogin [22]を使ってrootでのリモートログインを制限可能である.これにより,SSHサーバに対する操作の権限を制限できる.また,sshrサーバが有する秘密鍵が外部に流出した場合に備えて,接続元IPを制限することが有効である.たとえば,sshdのauthorized_keysファイルでは公開鍵ごとにオプションを記述できる.このオプションの中の接続元IPの制限機能[21]を用いて,sshrサーバのIPアドレスからの接続のみを許可することで,秘密鍵が外部に流出した際の被害を防ぐことができる.
提案するsshrは,プログラマブルに組み込み可能なフック関数を用いてプロキシサーバの動作を制御可能である.そのため,組み込むフック関数の実装やsshrを導入するシステムの構成等の選択性が高い.本章では,sshrを導入したシステムを構築する際の知見を具体例を挙げて説明する.
図3は提案手法を採用したシステム構成の例である.それぞれの構成要素の役割について説明する.クライアントは,用いるツールの制限がないため,OpenSSHのssh(1)コマンド[12]や,Tera Term [23]などGUIベースのSSHクライアントが想定される.サーバも用いるツールの制限がないため,代表的なSSHサーバであるsshd [16]などが想定される.sshrは,システム管理者が実装したフック関数を組み込んだsshrが動作し,フック関数により管理データからユーザの接続先サーバ等を取得する役割を担う.
DBは,sshrが参照するデータの管理を行うデータベースサーバである.データの管理にデータベースを用いることは必須ではなく,たとえばJSONやYAML形式のファイルを用いて管理することもできる.しかし,sshrを利用するユーザとサーバが増えた場合は,データベースを用いた管理が有効である.図4は,テーブルスキーマの例である.提案手法は,ユーザ名に紐づいたサーバに対してSSH接続できる仕組みであるため,データベースではユーザ名とサーバのホスト名の紐付けを管理する.また,sshrにてユーザの公開鍵認証を行うために,ユーザ名やホスト名に併せて公開鍵をデータベースで管理することが有効である.このテーブルの情報をsshrから直接参照することも可能であるが,Web APIを介してデータベースに格納されている情報を参照することも可能である.このようにシステム管理者がユーザやサーバの情報を自由なデータ形式で管理できることや,データの取得方法を自由に選択できることは,sshrが実行するフック関数をプログラマブルに拡張できる仕組みを採用することの利点の1つである.
図3では,1台のsshrサーバを想定しているが,sshrを利用するユーザやサーバが増えた場合は,複数台のsshrサーバを構築することが想定される.その場合,sshrサーバの前段にロードバランサを構築し,仮想IPアドレスを割り当て,ユーザからのSSH接続をすべて同じIPアドレスで受け付けることが有効である.これにより,sshrサーバの冗長化やスケールアウトが可能になる.さらに,sshrサーバのIPアドレスに変更が起きる場合や,sshrサーバを新規に導入した場合に対しても,ユーザに仮想IPアドレスさえ提供していれば変更を要求する必要がなくなる.
sshrは,システム管理者が自由に実装・導入可能なフック関数を3つ備えている.ここでは,図3のシステム構成を例に,それぞれのフック関数で想定される処理を解説する.図5は,フック関数の処理の例を示している.まず,接続先の決定に用いるFindUpstreamHookは,HTTPクライアントとしてWeb APIから情報を取得する処理を行う.図6は,Web API経由での接続先取得のためのサンプルコードである.Web APIから取得したJSON形式のデータを構造体にデコードし,ホスト名を取り出している.次に,ユーザの公開鍵検索に用いるFetchAuthorizedKeysHookは,前述のとおりユーザの公開鍵をデータベースで管理するため,FindUpstreamHookと同様にHTTPクライアントとしてWeb APIから情報を取得する処理を行う.最後にsshrから接続先サーバに対して認証を行うために利用する秘密鍵を検索するFetchPrivateKeyHookは,秘密鍵をデータベースで管理してネットワーク経由で取得することは,流出のリスクを高めるため,sshrサーバのローカルストレージに保管されている秘密鍵を参照する処理を行うことなどが想定される.
前節までに示したシステムを構築した場合,システムの変更に対してどのような方法で追従可能であるかを例を挙げて説明する.想定されるシステムの変更として,以下の3つを例に挙げる.
(1)の場合,データベースの情報を更新するだけで良い.sshrはリクエストのたびにユーザとそのユーザが用いるサーバの紐付け情報をデータベースから参照するため,データベースの情報を更新するだけで,ユーザの接続先サーバのIPアドレスやホスト名を切り替えることができる.(2)の場合,Web APIの処理を変更するだけで良い.このようにビジネスロジックをWeb APIに持たせることで,sshrの変更を行う必要がなくなる.(3)の場合,sshrのフック関数に変更を加える必要がある.システム管理者がGo言語で実装したフック関数をWeb APIの仕様変更に合わせて修正し,再度ビルドすることで得られた単一の実行ファイルをデプロイする.デプロイはCIツールで行うなど,開発プロセスに組み込むことで開発者の手を介さず自動で行うことが有効である.また,動作中のプログラムの処理を停止することなく,プログラムの変更を反映するために用いられるServer::Starter [24]と組み合わせることで,実行中のsshrサーバの処理を停止させることなく,デプロイした変更後の実行ファイルを適用できる.以上のように,提案手法は想定される様々なシステムの変更に対して,プロキシサーバ自体に変更を要しない,もしくは関数の修正のみで無停止で動作を切り替えられるため,システムの変更に対して高い拡張性を有する.
sshrを実環境のシステムへ適用する事例として,琉球大学の学内システムへの導入に向けた検証[25]が行われている.同大学では,学生にWebサイトを配信するためのサーバ環境を提供しており,学生がWebサイトの開発・保守のために自身のコンテンツが配置されたサーバにSSH接続を行う.一方,学内システムは定期的にシステム更新が行われ,学生が利用するサーバのIPアドレスやホスト名が変更されることがある.この際に,サーバを利用しているすべての学生に変更後のサーバ情報を知らせる必要があり,学生は変更に対応しなければならないことが運用上の課題として挙げられている.
運用上の課題となるシステムの変更は,4.3節で挙げたシステム変更例の(1)に該当する.4.3節で述べたとおり,学生が利用するサーバのIPアドレスやホスト名が変更された場合,sshrが参照するデータベース等の管理データを更新するだけでよい.すなわち,sshrを導入した場合,システム管理者は学生に変更後のサーバ情報を知らせる必要がなく,学生は変更に対応する必要がないため,挙げられている運用上の課題は解決できる.
これまで示したように,クライアント・サーバ間のSSH接続に対してsshrが介入することで接続先の決定処理や認証処理をプログラマブルに拡張でき,その結果,システムの変更に対して,ユーザに変更を要求することなく追従可能となる.一方,セッション確立時に実行されるフック関数により,通信のオーバーヘッドが発生する.このオーバーヘッドを測定することで,提案するsshrの導入がSSH接続の処理時間に与える影響を評価し,sshrが想定されうる実用的な環境に耐えうるか議論する.
評価は,sshrを介してクライアントからSSHサーバに対して,カーネルの名前やバージョン等を表示するunameコマンドを実行した場合,およびscp(secure copy)によりファイルを転送した場合のオーバーヘッドを測定する.unameコマンドは,ユーザがSSH経由でサーバ上でコマンドを実行することを想定した例として選定した.また,クライアント・サーバ間の認証には公開鍵認証のみを用いる.パスワード認証では,sshrはクライアントから受けた認証リクエストを解釈することなくSSHサーバにパケットを転送するのみであるが,公開鍵認証では,sshrがクライアントの認証を行うために公開鍵を検索するためのフック関数が実行される.これにより,公開鍵認証はパスワード認証に比べて認証にかかる時間が長いため,よりオーバーヘッドが大きくなる条件で評価を行った.
図7に用いた実験環境の概要図を示す.実験は,クライアントとサーバ間の通常のSSH接続と,提案手法であるsshrを介したSSH接続で比較を行う.表1は,各ロールの性能と用いたソフトウェアのバージョンを示している.各ロールのOSはすべてCentOS 7.6.1810 Kernel 3.10.0である.図8にunameコマンドの実行時間を測定するためのコマンド例を示す.それぞれ100回測定を行い,平均値を算出した.sshrが実行するフック関数は,接続先の決定処理(FindUpstreamHook)およびユーザの公開鍵検索の処理(FetchAuthorizedKeysHook)のいずれにおいても,別サーバ上のデータベースから参照するものとする.データベースには目的のレコード以外を含まないものとする.
表2は,unameコマンド実行の測定結果を示している.クライアント・サーバ間のSSH接続に対してsshrを介した場合,unameコマンド実行のオーバーヘッドは22ミリ秒であることが分かった.システムの処理時間とユーザの体感の関係がまとめられた[26]では,100ミリ秒以内の処理では,ユーザはシステムが瞬時に反応しているように感じると述べている.そのため,sshrを介することで発生するオーバーヘッドが100ミリ秒以内であれば,sshrを介さない通常のSSH接続と比較してユーザが遅延を感じないと考えられる.したがって,複数のユーザが複数のサーバ群の中から特定のサーバに対してSSHログインし,サーバの操作を行うようなシステム管理を想定した場合,sshrのオーバヘッドは実用に十分に耐えうるほど小さい.
図9は,scpで転送するファイルサイズを変化させた場合のファイル転送時間の測定結果を示している.20 MBのファイルを転送する場合,sshrを介することで発生するオーバーヘッドは48ミリ秒であった.この結果は,unameコマンド実行の結果よりオーバーヘッドが大きいことから,sshrを介することで発生するオーバーヘッドは,セッション確立時に実行されるフック関数以外の影響が含まれていることを示している.また,転送するファイルサイズが大きくなるにつれて,sshrを介することで発生するオーバーヘッドが大きくなることが分かった.これは,sshrを介する場合,クライアントから直接SSHサーバにパケットを送る場合と比べて,sshrサーバがパケットをフォワードをすることによるオーバーヘッドが影響していると考えられる.しかしながら,100 MBのファイル転送の場合に着目すると,転送時間の増加率はわずか0.9%程度であり,全体の転送時間に対してsshrを介することで発生するオーバーヘッドが占める割合は小さい,かつオーバーヘッドは100ミリ秒以内である.そのため,sshrを介して大量のファイル転送を行う場合などを除いて,sshrを介することで発生するオーバーヘッドは実用上無視できるほど小さいと考えられる.
さらに,sshrサーバは公開鍵認証方式を用いた場合,接続先サーバや公開鍵を管理データから取得するため,その取得時間がオーバヘッドに大きく影響を与える.そのため,データベースやWeb API等の応答時間が長い場合は,それに伴いsshrを介したSSHセッション確立にかかる時間も長くなるため,応答時間を短くする対策が必要である.
本稿では,ユーザに用いるクライアントツールの制限や変更を要求せず,システム管理者が自由に組み込み可能なフック関数を用いてシステム変化に追従できるsshrというSSHプロキシサーバを提案した.システム管理者は,sshrに対してユーザ名から接続先サーバを決定するためのフック関数を自由に実装・導入可能である.これにより,ユーザ名を用いて透過的に目的のサーバにSSH接続できるため,接続先サーバのIPアドレスやホスト名に変更があった場合でも,管理者は各ユーザへ変更後の情報を知らせる必要がなく,ユーザも変更に追従する必要がない.さらに,sshrは,公開鍵認証の際にユーザの公開鍵を検索するためのフック関数を組み込めるなど,SSH特有のユーザ認証もプログラマブルに拡張できる仕組みを採用しているため,システム管理者がユーザの公開鍵をデータベースなどの自由なデータ形式で管理できる.さらに実験から,クライアント・サーバ間にsshrを導入した場合のSSHセッション確立のオーバーヘッドは20ミリ秒程度であり,ユーザが特定のサーバにSSHログインする際に遅延を感じないほど短い時間[26]であることを示した.
今後の展望として,まずクライアントが同一ユーザを用いて複数のサーバの中から意図的に接続先を指定して接続要求を送ることができないという課題を解決したい.解決の手法として,sshrが同一ユーザに紐づいた複数のサーバリストをクライアント側に返し,クライアントが対話的に接続先を選択できる仕組みなどを検討していく.
さらに,今後はsshrを利用した実用的なシステムの構築を通して,sshrの有用性を評価していきたい.具体的には,ユーザの同時接続数が増えた場合のsshrの性能上限の評価や,sshrの導入前後でシステムの運用管理がどのように改善されたかを定量的に評価する予定である.
謝辞 sshrの開発にあたり,多大なるご支援とご助言を賜りましたGMOペパボ株式会社のホスティング事業部の皆様をはじめ多くの方々に深く感謝を申し上げます.
2012年九州大学大学院工学府材料物性工学専攻修士課程修了.2019年よりさくらインターネット株式会社さくらインターネット研究所研究員.インターネットの運用技術に対する機械学習の適用等に興味をもつ.IEEE,ACM各会員.
2015年京都大学大学院情報学研究科博士課単位取得認定退学.同年GMOペパボ株式会社入社を経て.2018年よりさくらインターネット株式会社さくらインターネット研究所上級研究員.京都大学博士(情報学).OSやサーバソフトウェア,インターネットの運用技術やセキュリティ等に興味をもつ.IEEE,ACM各会員.
会員種別ごとに入会方法やサービスが異なりますので、該当する会員項目を参照してください。