私はかつて、ssh-chatというプログラムを書きました。
ssh http://t.co/E7Ilc0B0BC pic.twitter.com/CqYBR1WYO4
— Andrey ???? Petrov (@shazow) December 13, 2014
アイデアは単純なもので、ターミナルを開いてこのようにタイプするだけのことです。
$ ssh chat.shazow.net
たいていの人はこの後に続けてlsコマンドをタイプするのでしょうが、ちょっと待って。よく見てください。そこにあるのはシェルではなく、なんとチャットルームですよ!
詳しいことはわからないけど、何かすごいことが起こっているようですね。
SSHはユーザー名を認識する
sshでサーバーに接続するときに、sshクライアントはいくつかの環境変数をサーバーへの入力として渡します。その中のひとつが環境変数$USERです。もちろんこれを上書きすることもできます。接続するときに、ユーザー名を指定すればいいのです。
$ ssh neo@chat.shazow.net
ほら、ごらんなさい。あなたこそが救世主。特別な存在なのです。これ以外に何ができるのでしょう?デフォルトで、環境変数$TERMの内容も受け取ってくれます。
$ TERM=inator ssh chat.shazow.net
ssh-chatがもう少し賢ければ、このカスタム端末がカラー対応していないことを察して、フォーマット文字の送信をスキップしてくれたことでしょう。
オプションフラグSendEnvを使えばその他の環境変数も送れますが、ここではそこまで立ち入らないことにします。
SSHは鍵ペアを検証する
SSHがサポートする認証方式にはnone、password、keyboard-interactive、publickeyがあります。それぞれ特徴がありますが、中でもいちばん便利なのが最後の方式です。
SSHクライアントがサーバーと接続するときには、お互いがサポートする認証方式のネゴシエーションを(通常は、先ほど挙げたのとは逆順で)行います。identityフラグを指定していたり、鍵を~/.ssh/ディレクトリに置いていたりする場合は、クライアント側からは公開鍵認証を提案するでしょう。サーバーが公開鍵を認識すれば(つまり、authorized_keysに記載されていれば)、セキュアなハンドシェイクが始まって、あなたがその公開鍵に対応する秘密鍵を持っていることを確認します。この流れにおいて、クライアントとサーバーの間では一時的なセッション鍵を使って通信を暗号化することに合意しています。
これは何を意味するのでしょう?要するに、SSHにはプロトコルレベルで認証の仕組みが組み込まれているということです。あなたがssh-chatにjoinしたときには、サーバー側ではあなたが何者なのかを知っているだけではなく、恒久的かつ確実にそのコネクションを特定できるのです。ユーザー側で何かをしてもらう必要はありません。登録フォームもいらなければメールのリンクをクリックしてもらう必要もないし、すてきなモバイルアプリだっていらないのです。
ssh-chatの将来のバージョンでは、ssh-chat専用のアカウントを作ってユーザーの鍵ペアで認証できるようになるでしょう。この専用アカウントとシェルアカウントとの紐付けや常駐機能、そしてPushoverやメールによるプッシュ通知といった機能も用意できるかもしれません。
SSH接続は暗号化されている
サーバー側でも、クライアントと同じような鍵ペアを使っています。SSHで新しいホストに接続すると、「鍵のフィンガープリント」を確かめてくださいというメッセージが表示されます。このフィンガープリントは、サーバーの公開鍵のハッシュを十六進形式で表したものです。
たとえば、chat.shazow.netに接続しようとしたときに今までとは違うフィンガープリントハッシュが表示されたとしたら、それは中間者攻撃を受けていることを意味します。
どこかの極秘セキュリティ機関がSSHサーバーを用意して、あなたがふだん接続するSSHサーバーの前に(sshmitmなどを使って)プロキシーとして割り込ませ、通信内容をすべて記録することだってありえます。幸いなことに、元のサーバーの秘密鍵がプロキシーに漏れていない限り、鍵のフィンガープリントは一致しません。
一度受け入れたフィンガープリントは~/.ssh/known_hostsに追記され、接続先のホストにピン留めされます。その後の接続でもしホスト側のキーが変わっていたら、こんな恐ろしいメッセージが表示されるでしょう。
接続しようとしているホストが提示した公開鍵が、以前に同じホストに接続したときのものとは異なります。(同じIPアドレスで新しくVPSを起動したり、サーバー側でSSH鍵ペアを作り直したなどの)心当たりがない場合は、注意しましょう。別のネットワーク経由でこのホストに接続して、同じ結果になるかどうかを確認しましょう。同じにならない場合は、サーバー側のコネクションではなくローカルのコネクションを誰かに乗っ取られています。
SSHは多重接続に対応している
クライアントはサーバーに接続すると、チャネルをオープンします。このチャネルに対して、いろいろな機能をリクエストします。クライアントからは、pty-req(擬似端末)やexec(コマンドの実行)などのリクエストを送信できますし、tcpip-forward(ポートフォワーディング)だってできます。それ以外にもいろいろなチャネルがありますし、クライアントとサーバーを実装した独自のチャネルも作れます。そのうちchatチャネルができるかもしれませんね。
いちばんよいところは、これらを並列に実行できるということです。ポートフォワーディングをしながら、シェルを開いてバックグラウンドでコマンドを実行できるのです。
パイプをオープンすれば、moreコマンドの結果を流し込めます。クライアントがpty-reqをオープンしたら、たとえばターミナルのサイズを変更するたびにwindow-changeイベントを送信します。
SSHはどこにでもある
「それってモバイルでも使える?」ご冗談を。当たり前じゃないですか!思いつく限りのあらゆるプラットフォーム上にSSHクライアントが用意されています。iOSやAndroidはもちろん、Windowsにだってさえ!OSXや各種Linuxディストリビューションには、SSHクライアントが標準で含まれています。ブラウザをSSHクライアントにするエクステンションだってあるのです。
セキュアなプロトコルとしてSSHよりも広まっているものといえば、HTTPSくらいでしょう。
SSHってすごいですねえ……
さて、ここまでを振り返ってみましょう。SSHはバイナリプロトコルです。強制的に暗号化されます。鍵のピン留めに対応しています。多重接続に対応しています。圧縮に対応しています(そう、実は圧縮もできるのです)。
これらの機能って、HTTP/2を作るきっかけになったものじゃありませんでしたっけ?
確かに、SSHには足りないところもあります。バーチャルホストの概念がなくて、ひとつのIPアドレス上でホストネームごとに別々のエンドポイントを提供することができません。
しかしその一方で、SSHにはHTTP/2よりも優れた機能もあります。クライアント認証の仕組みが組み込まれているので、余計なパスワードを覚えさえる必要がないのです。
その他諸々
- SSHサーバーの仕様とクライアントの仕様は対称的なものです。プロトコルの仕様では、クライアントがサーバーに問い合わせることのできるほとんどのことは、サーバーからクライアントへも問い合わせられます。コマンドの実行機能もそれに含まれますが、主要なSSHクライアントはコマンド実行機能を実装していません(仕様でもそれを推奨しています)。
- すべてのキーストロークはTCPコネクション上で送信されます。そのため、タイプした内容が反映されるまでに遅延を感じるかもしれません。
- OpenSSHクライアントの挙動を詳しく知りたければ、-vオプションを指定すれば詳細なデバッグ出力が得られます。もうひとつ-vをつければキーストローク単位のデバッグができますし、さらにもうひとつ-vを指定すればより詳しい出力が得られます。
- MOSHというプロトコルがあります。これは、起動時にだけSSHを使って、あとはクライアント側での予測レンダリングとUDP状態同期プロトコルを利用するものです。これによって、レイテンシーが及ぼす影響を抑えます。サードパーティのMOSH実装がもっと増えればいいのにと思います。
- SSHはポートフォワーディングに対応しており、SOCKSプロキシインターフェイスもサポートしているので、sshuttleなどを使ってSSH上でVPNを構築することもできるでしょう。
- SSHでは、認証局を使った認証もできます。これはTLSで用いられるx.509証明書のようなものです。また、SSHクライアントの多くは、SSHFP DNSエントリを使ったサーバーのフィンガープリントの検証に対応しています。
SSHを使ったもっと挑戦的なアイデア
SSHを使ったチャットも楽しいけど、これはSSHの可能性のほんの一部を使っただけにすぎません。
マルチユーザーダンジョン(MUD)
いつの日か、mud.shazow.netにSSHでログインすればちょっとしたRPGの世界を冒険できるようになるでしょう。今はまだないけど、きっといつかはそうなるでしょう。
分散ハッシュテーブル
技術的に高度ではありますが、可能性はありますよ…… https://twitter.com/shazow/status/549348566972370944
プログラマティックデータストリーム
いっそのこと、ZeroMQみたいなソケットをセキュアな暗号化環境で扱うというのはどうでしょう。Jeff LindsayのDuplexについて調べてみましょう。まだ概念実証のレベルですが、クールなデモがたくさん用意されています。
RPC API
SSHに組み込まれている認証や暗号化の仕組みは、APIなどにとっても実に便利です。複雑なOAuth2ハンドシェイクやHMACとシグネチャなどが不要になります。
ssh api.example.com multiply a=4 b=5
SSH経由での接続をHTTPと同じくらい簡単にできるようにするライブラリが、そのうち出てくるでしょう。そんなライブラリがあれば、ごく普通にREST APIにアクセスするコードの裏側ではSSHを使っていることになるでしょう。
いずれにせよ、APIドキュメントにcurlでの実行例を書くような時代は終わるのです。
ファイルサーバー
RPC APIが手に入ったら、静的なファイルも操作したくなるものでしょう。
ssh static.example.com get /images/header.png
SSHは持続的接続にも対応していたことを思い出しましょう。ブラウザからSSHチャネルgetに接続して、getリクエストを並列に送信すればいいのです。ETAGなどだって実装できるでしょう。
そして、HTTP
ここまでくれば、HTTP/1やHTTP/2をSSH上で実装できないわけがないですよね。headerチャネルを追加してバーチャルホスト対応のための>Hostを指定したり、Cookieヘッダを送信したり……。他のHTTPメソッドも追加したいですって?もちろんできますよ。たとえばpostのようなチャネルを作ればいいだけです。もう少し丁寧に作るならhttp-postみたいな名前にしてもいいでしょうね。
なぜ何もかもSSHでやってしまわないのですか?
うん、いい質問ですね。
John Zila、Tracy Osborn、Cory Benfieldに感謝します。