SSH.NET を使って C# で多段 SSH 経由のリモートデスクトップ接続を可能にする
こんにちは、kenzauros です。
多段 SSH 経由のリモートデスクトップを簡略化したくて、最近、自前のソフト を作っていたのですが、内部的に SSH 接続の確立に役立ってくれたのが、 SSH.NET です。
sshnet/SSH.NET: SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism.
非常に便利なライブラリですが、意外と使用例が少なく、試行錯誤しながら使うことになったので、同じような方のために簡単な使い方をメモしておきます。
ちなみにここで書いたことは作ってみたアプリでできますので、よければお試しください。
前提条件
今回は bastion1, bastion2 の二つの SSH ホストを経由して、 bastion2 と同じネットワークにある Windows 機 remote1 のリモートデスクトップ (3389) へ接続できる環境を構築します。
今回はサンプルなので、各ホストの IP アドレスとポートは下記のように定数で定義したものとします。
const string LOCAL_HOST = "127.0.0.1";
const string FIRST_HOP_HOST = "192.168.10.112"; // bastion1
const int FIRST_HOP_PORT = 22;
const string SECOND_HOP_HOST = "192.168.11.114"; // bastion2
const int SECOND_HOP_PORT = 22;
const string REMOTE_IP = "192.168.11.10"; // remote1
const int REMOTE_PORT = 3389; // RDP
const string SSH_USERNAME1 = "hogehoge";
const string SSH_PASSWORD1 = "hogehoge";
bastion1, bastion2 はそれぞれパスワード認証です。
一段目の SSH 認証
認証は多要素認証にも対応していて、パスワードの他、鍵認証やそれらを組み合わせた認証もできます。
とりあえずパスワード認証の場合は PasswordConnectionInfo
を使って接続情報 (ConnectionInfo
) を作成します。
var connectionInfo = new PasswordConnectionInfo(FIRST_HOP_HOST, FIRST_HOP_PORT, SSH_USERNAME1, SSH_PASSWORD1);
こんな感じです。わかりやすいですね。
一段目の SSH 接続
これは極めて簡単で、 SShClient
クラスに認証情報を渡してインスタンス化し、 Connect
メソッドを呼ぶだけです。
var client = new SshClient(connectionInfo);
client.Connect();
ちなみに認証に失敗した場合は SshAuthenticationException
が発生するので、この例外をキャッチして、再認証(パスワード入力など)を走らせるとよいでしょう。
(失敗理由は英語ですが Message
プロパティに含まれています)
二段目の SSH 接続用のポートフォワード
次に二段目の SSH に接続するため、 bastion2 に対するポートフォワードを設定します。
この場合のポートフォワードは “Local” と呼ばれるタイプのものなので、 ForwardedPortLocal
クラスを用います。
var forward = new ForwardedPortLocal(LOCAL_HOST, SECOND_HOP_HOST, SECOND_HOP_PORT);
client.AddForwardedPort(forward);
forward.Start();
client.AddForwardedPort
で一段目の SSH にポートフォワードを追加したあと、 forward.Start()
で開始します。
当たり前ですが、この順番が逆だと例外が発生します。別メソッドでやっていると逆になってしまったりするので注意しましょう。
二段目の SSH 接続
二段目の認証情報を作成します。この場合の接続先は今作成したポートフォワードのローカルポートなので、 forward.BoundHost
と forward.BoundPort
になります。
var connectionInfo2 = new PasswordConnectionInfo(forward.BoundHost, (int)forward.BoundPort, SSH_USERNAME1, SSH_PASSWORD1);
var client2 = new SshClient(connectionInfo2);
client2.Connect();
なぜか ForwardedPort
のほうのポートは uint
で ConnectionInfo
のほうのポートは int
なのでキャストしています。
最後のポートフォワード
最後にリモートデスクトップ先である remote1 に対してポートフォワードを設定します。
先のポートフォワードとほぼ同じなので難しいところはありません。
var forward2 = new ForwardedPortLocal(LOCAL_HOST, REMOTE_IP, REMOTE_PORT);
client2.AddForwardedPort(forward2);
forward2.Start();
Console.WriteLine(forward2.BoundPort);
ここまで実行できた後 forward2.BoundHost
が 50000 だとすると、リモートデスクトップを開き localhost:50000
で接続できるはずです。
おまけ : 鍵認証
鍵認証の場合は PrivateKeyFile
と PrivateKeyAuthenticationMethod
を使います。
(いくつかやり方はありますが、この方法が汎用的だと思います。)
var pkeyfile = new PrivateKeyFile(鍵ファイルのパス, PASSPHRASE);
var auth = new PrivateKeyAuthenticationMethod(USERNAME, pkeyfile);
var connectionInfo = new ConnectionInfo(host, port, USERNAME, auth);
var client = new SshClient(connectionInfo);
client.Connect();
ちなみに鍵ファイルは OpenSSH 形式に対応していますので、 PuTTy 形式 (拡張子 .ppk) だと “Invalid key file” と怒られます。
PuTTy 形式の鍵しかない場合は puttygen.exe などを利用して ppk から OpenSSH 形式に変換してください。
PuTTY 下のほうの “Alternative binary files” に puttygen.exe のダウンロードリンクがある