
SwiftNIO SSHは、SwiftNIOを使用したSSHのプログラム的な実装です。
このプロジェクトは、SwiftNIO を使用したSSHサポートを含んでいます。
SwiftNIO SSHは、SSHのプログラムによる実装です。つまり、プログラマがSSHを話すエンドポイントを実装できるようにするAPIのコレクションです。重要なのは、これはopensshというよりlibssh2に近いということです。SwiftNIO SSHは本番環境ですぐ使えるSSHクライアントやサーバを提供するのではなく、そのようなクライアントとサーバを構築するための構成要素を提供します。
プログラムによるSSH実装を提供する理由はいくつかあります。1つは、SSHがユーザーとの対話性に対して独自の関係を持っていることです。技術的なユーザーは、リモートマシンでコマンドを実行するためであれ、対話型シェルを実行するためであれ、SSHと対話的にやり取りすることに非常に慣れています。これらの要求にプログラム的に応答できることで、興味深い代替的な対話モードが可能になります。以前の例としては、実行中のPythonサーバ内で対話型Pythonインタープリタを提供するためにconchと呼ばれるプログラムによるSSH実装を使用するTwistedのManholeや、通常のSSHシェル機能の代わりにチャットルームを提供するSSHサーバであるssh-chatを挙げることができます。TCPフォワーディングについても革新的な用途が考えられます。
プログラムによるSSHを提供するもう1つの良い理由は、サービスがコマンドの実行を伴う形で他のサービスとやり取りする必要があることが珍しくないからです。Processはローカルでのユースケースを解決しますが、呼び出す必要があるコマンドがリモートにある場合もあります。Processはこの呼び出しを実行するためにsshクライアントをサブプロセスとして起動できますが、SSHを直接呼び出す方がはるかに簡単な場合があります。これはlibssh2のターゲットユースケースです。SwiftNIO SSHはlibssh2のネットワーキングおよび暗号化レイヤに相当するものを提供し、意欲的なユーザーがSwiftサービス内から直接SSHセッションを操作できるようにします。
SwiftNIO SSHの最新バージョンはSwift 5.9以降をサポートしています。SwiftNIO SSHのリリースがサポートする最小Swiftバージョンは以下のとおりです:
SwiftNIO SSHは以下の機能セットでSSHv2をサポートしています:
SwiftNIO SSHは、SwiftNIOのChannelHandlerであるNIOSSHHandlerを提供します。このハンドラはSSHプロトコルの大部分を直接実装しています。ユーザーはSSHメッセージを直接生成する必要はありません。代わりに、子チャネルとデリゲートを通じてNIOSSHHandlerとやり取りします。
SSHは多重化プロトコルです。各SSH接続は、適切にもチャネルと呼ばれる複数の双方向通信チャネルに分割されます。SwiftNIO SSHは「子チャネル」抽象化を使用してこの構造を反映します。ピアが新しいSSHチャネルを作成すると、SwiftNIO SSHはそのSSHチャネル上のすべてのトラフィックを表す新しいNIO Channelを作成します。この子Channel内では、すべてのイベントは互いに対して厳密に順序付けられます。ただし、異なるChannelのイベントは実装によって自由にインターリーブされる場合があります。
したがって、アクティブなSSH接続は次のようになります:
┌ ─ NIO Channel ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
│ ┌────────────────────────────────┐ │
│ │
│ │ │ │
│ │
│ │ │ │
│ NIOSSHHandler │───────────────────────┐
│ │ │ │ │
│ │ │
│ │ │ │ │
│ │ │
│ └────────────────────────────────┘ │ │
│
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │
│
│
│
│
▼
┌── SSH Child Channel ─────────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────┐ ┌────────────────────────────────┐ ├───┐
│ │ │ │ │ │ │
│ │ │ │ │ │ ├───┐
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ User Handler │ │ User Handler │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │
│ └────────────────────────────────┘ └────────────────────────────────┘ │ │ │
│ │ │ │
└───┬──────────────────────────────────────────────────────────────────────────────┘ │ │
│ │ │
└───┬──────────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────┘
SSHチャネルはチャネルタイプとともに呼び出されます。NIOSSHはsession、directTCPIP、forwardedTCPIPの3つをサポートします。最も一般的なチャネルタイプはsessionです。sessionは、特定の名前付きプログラムであれシェルであれ、プログラムの呼び出しを表すために使用されます。他の2つのチャネルタイプはTCPポートフォワーディングに関連しており、後ほど説明します。
SSHチャネルは単一のデータ型SSHChannelDataで動作します。この構造体は、SSHが通常のチャネルデータと「拡張」チャネルデータの両方をサポートしていることをカプセル化します。通常のチャネルデータ(SSHChannelData.DataType.channel)は、大部分のコアデータに使用されます。sessionチャネルでは、.channelデータ型が標準入力と標準出力に使用され、.stdErrデータ型が(当然ながら)標準エラーに使用されます。TCPフォワーディングチャネルでは、.channelデータ型のみが使用され、フォワーディングされたデータを表します。
sessionチャネルはコマンドの呼び出しを表します。チャネルがどのように動作するかは、いくつかのインバウンドユーザーイベントで伝えられます。以下のイベントが重要です:
SSHChannelRequestEvent.PseudoTerminalRequest: 擬似端末の割り当てを要求します。SSHChannelRequestEvent.EnvironmentRequest: コマンド呼び出しのための単一の環境変数を要求します。常にコマンド自体より前に送信されます。SSHChannelRequestEvent.ShellRequest: 呼び出されるコマンドが認証されたユーザーのシェルであることを要求します。SSHChannelRequestEvent.ExecRequest: 特定のコマンドの呼び出しを要求します。SSHChannelRequestEvent.ExitStatus: リモートコマンドが終了したことを通知し、終了コードを伝えるために使用されます。SSHChannelRequestEvent.ExitSignal: リモートコマンドがシグナルに応答して終了したこと、およびそのシグナルが何であったかを示すために使用されます。SSHChannelRequestEvent.SignalRequest: リモートコマンドにシグナルを送信するために使用されます。SSHChannelRequestEvent.LocalFlowControlRequest: クライアントがCtrl-Q/Ctrl-Sフロー制御を自身で実行できるかどうかを示すために使用されます。SSHChannelRequestEvent.WindowChangeRequest: クライアントのターミナルウィンドウサイズの変更を、割り当てられた擬似端末に伝えるために使用されます。SSHChannelRequestEvent.SubsystemRequest: 特定のサブシステムの呼び出しを要求するために使用されます。この意味は個々のユースケースに固有です。これらのイベントはポートフォワーディングメッセージでは使用されません。.sessionタイプのチャネルをサポートするSSH実装は、これらのほとんどまたはすべてをさまざまな方法で処理できるように準備する必要があります。
これらの各イベントにはwantReplyフィールドもあります。これは、リクエストが成功または失敗を示す応答を必要とするかどうかを示します。必要とする場合、次の2つのイベントが使用されます:
ChannelSuccessEvent、成功を伝えるために使用されます。ChannelFailureEvent、失敗を伝えるために使用されます。SSHネットワークプロトコルは、子チャネルでハーフクロージャを広範囲に使用します。NIOのChannelは通常、デフォルトでハーフクロージャサポートが無効になっており、SwiftNIO SSHもその子チャネルでこのデフォルト設定を尊重します。ただし、この設定をデフォルト値のままにすると、SSH子チャネルは極めて予期しない動作をします。そのため、すべての子チャネルでハーフクロージャサポートを有効にすることを強くお勧めします:
channel.setOption(ChannelOptions.allowRemoteHalfClosure, true)
これにより、標準のNIOハーフクロージャサポートが使用されます。リモートピアがEOFを送信すると、インバウンドユーザーイベントChannelEvent.inputClosedで通知されます。自分でEOFを送信するには、close(mode: .output)を呼び出します。
ユーザー認証はSSHの重要な部分です。これを管理するために、SwiftNIO SSHは2つのデリゲートプロトコル、NIOSSHClientUserAuthenticationDelegateとNIOSSHServerUserAuthenticationDelegateを使用します。クライアントとサーバは、ユーザー認証を管理するためにこれらのデリゲートプロトコルの実装を提供する必要があります。
クライアントプロトコルは簡単です。SwiftNIO SSHはデリゲートのnextAuthenticationType(availableMethods:nextChallengePromise:)メソッドを呼び出します。availableMethodsは、サーバが受け入れ可能と提案した認証方法を伝えるNIOSSHAvailableUserAuthenticationMethodsのインスタンスになります。デリゲートはその後、新しい認証リクエスト、またはクライアントが試す手段を使い果たしたことを示すnilでnextChallengePromiseを完了できます。
サーバプロトコルはより複雑です。デリゲートは、デリゲートがサポートする認証方法を伝えるsupportedAuthenticationMethodsプロパティを提供する必要があります。その後、クライアントがユーザー認証リクエストを送信するたびに、requestReceived(request:responsePromise:)メソッドが呼び出されます。クライアントは認証リクエストを並行して発行できるため、これは並行して複数回呼び出される場合があります。responsePromiseは認証の結果で成功として完了させる必要があります。結果は3つあります:.successと.failureは単純ですが、原則としてサーバは.partialSuccess(remainingMethods:)を使用して複数のチャレンジを要求できます。
直接ポートフォワーディングは、クライアントからサーバへのポートフォワーディングです。このモードでは、従来クライアントがローカルポートで待ち受け、インバウンド接続をサーバに転送します。クライアントは、これらの接続を特定のホストとポートへのアウトバウンド接続として転送するようサーバに要求します。
クライアントは.directTCPIPチャネルタイプを使用してこれらのチャネルを直接開くことができます。
リモートポートフォワーディングはあまり一般的ではない状況で、クライアントがサーバに特定のアドレスとポートで待ち受けるよう要求し、すべてのインバウンド接続をクライアントに転送します。クライアントはこの動作を要求する必要があるため、グローバルリクエストを使用して行います。
グローバルリクエストはNIOSSHHandler.sendGlobalRequestを使用して開始され、GlobalRequestDelegateを通じて受信および処理されます。現在サポートされているグローバルリクエストは2つあります:
GlobalRequest.TCPForwardingRequest.listen(host:port:): サーバが指定されたホストとポートで待ち受けるためのリクエスト。GlobalRequest.TCPForwardingRequest.cancel(host:port:): 指定されたホストとポートでの待ち受けをキャンセルするためのリクエスト。サーバはGlobalRequestDelegateを使用してこれらのリクエストの通知を受け、応答できます。ここで実装するメソッドはtcpForwardingRequest(_:handler:promise:)です。このデリゲートメソッドは、グローバルリクエストが受信されるたびに呼び出されます。リクエストへの応答はpromiseに渡されます。
転送されたチャネルは、その後.forwardedTCPIPチャネルタイプを使用してサーバからクライアントに送信されます。
| SwiftNIO SSH | Minimum Swift Version |
|---|
0.0.0 ..< 0.3.0 | 5.1 |
0.3.0 ..< 0.4.0 | 5.2 |
0.4.0 ..< 0.5.0 | 5.4 |
0.5.0 ..< 0.6.2 | 5.5.2 |
0.6.2 ..< 0.9.0 | 5.6 |
0.9.0 ..< 0.9.2 | 5.8 |
0.9.2 ..< 0.10.0 | 5.9 |
0.10.0 ... 0.12.0 | 5.10 |
0.12.0 ..< 0.13.0 | 6.0 |
0.13.0 ..< | 6.1 |