[インデックス 11011] ファイルの概要
コミット
Author: Dave Cheney dave@cheney.net Date: Tue Dec 27 09:49:19 2011 -0500
exp/ssh: various small fixes
transport.go:
* remove unused nil check.
doc.go:
* improve documentation about supported auth
methods and update Run example.
Thanks Jacek Masiulaniec for both reports.
R=jacek.masiulaniec, agl
CC=golang-dev
https://golang.org/cl/5501075
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7f20bcbbcb04f8239d894045afc9482018dc2bab
元コミット内容
exp/ssh: various small fixes
transport.go:
* remove unused nil check.
doc.go:
* improve documentation about supported auth
methods and update Run example.
Thanks Jacek Masiulaniec for both reports.
R=jacek.masiulaniec, agl
CC=golang-dev
https://golang.org/cl/5501075
変更の背景
このコミットは、Go言語の実験的なSSHパッケージ exp/ssh
におけるいくつかの小さな修正とドキュメントの改善を目的としています。コミットメッセージによると、主な変更点は以下の2つです。
transport.go
ファイルから未使用のnil
チェックを削除すること。これはコードの冗長性を排除し、よりクリーンで効率的なコードベースを目指すものです。doc.go
ファイルのドキュメントを改善すること。具体的には、サポートされている認証方法に関する説明をより明確にし、Session.Run
メソッドの使用例を更新しています。
これらの変更は、Jacek Masiulaniec氏からの報告(おそらくバグ報告や改善提案)に基づいて行われたものであり、パッケージの安定性と使いやすさを向上させるためのものです。特にドキュメントの改善は、ユーザーが exp/ssh
パッケージをより正確かつ容易に利用できるようにするために重要であり、APIの正しい利用方法を促進します。
前提知識の解説
Go言語の exp
パッケージ
Go言語の標準ライブラリには、安定版のパッケージとは別に exp
(experimental) というプレフィックスを持つ実験的なパッケージ群が存在します。これらは将来的に標準ライブラリに取り込まれる可能性のある機能や、まだAPIが安定していない機能を提供します。exp/ssh
もその一つで、GoでSSHクライアントやサーバーを実装するための機能を提供します。現在では golang.org/x/crypto/ssh
として独立したモジュールとして提供されており、広く利用されています。
SSH (Secure Shell)
SSHは、ネットワークを介してコンピュータを安全に操作するためのプロトコルです。主にリモートログインやファイル転送(SCP, SFTP)に利用されます。SSHは、クライアントとサーバー間で暗号化された通信チャネルを確立し、データの盗聴や改ざんを防ぎます。
SSH認証方法
SSHクライアントがSSHサーバーに接続する際、クライアントは自身を認証する必要があります。一般的な認証方法には以下があります。
- パスワード認証: ユーザー名とパスワードを使用して認証します。
- 公開鍵認証: クライアントが秘密鍵を保持し、サーバーが対応する公開鍵を保持することで認証します。より安全な方法とされています。
- エージェント認証: SSHエージェントを使用して秘密鍵を管理し、認証を行います。
ClientConfig
と ClientAuth
exp/ssh
パッケージにおいて、ClientConfig
はSSHクライアントの接続設定を定義するための構造体です。これには接続先のユーザー名、認証情報、タイムアウトなどの設定が含まれます。
ClientAuth
は、SSHクライアントがサーバーに対して自身を認証するためのインターフェースです。このインターフェースを実装することで、様々な認証方法(パスワード、公開鍵など)を ClientConfig
に組み込むことができます。
ClientConn
と Session
ClientConn
は、確立されたSSH接続を表します。この接続を通じて、複数の論理的なチャネル(セッション)を開くことができます。
Session
は、SSH接続上で開かれる論理的なチャネルの一つで、通常はリモートコマンドの実行やシェルセッションの提供に使用されます。
Session.Run
メソッド
Session
オブジェクトの Run
メソッドは、リモートSSHサーバー上で単一のコマンドを実行するために使用されます。コマンドの標準出力や標準エラーは、セッションの対応するストリームを通じてクライアントに返されます。
cipher.Stream
と XORKeyStream
cipher.Stream
は、Go言語の crypto/cipher
パッケージで定義されているインターフェースで、ストリーム暗号の操作を提供します。XORKeyStream
メソッドは、入力バイトスライスと出力バイトスライスを、ストリーム暗号の鍵ストリームとXOR演算することで、暗号化または復号化を行います。これは、SSHのトランスポート層におけるデータの秘匿性を確保するために利用されます。
MAC (Message Authentication Code)
MACは、メッセージの完全性と認証を保証するための暗号技術です。メッセージと秘密鍵から固定長の値を生成し、受信側で同じ計算を行うことで、メッセージが改ざんされていないこと、および送信者が正当であることを確認します。
技術的詳細
このコミットは、src/pkg/exp/ssh/doc.go
と src/pkg/exp/ssh/transport.go
の2つのファイルに影響を与えています。
src/pkg/exp/ssh/transport.go
の変更
このファイルでは、SSHトランスポート層の処理に関連する変更が行われています。具体的には、readOnePacket
メソッド内の r.cipher
の nil
チェックが削除されました。
--- a/src/pkg/exp/ssh/transport.go
+++ b/src/pkg/exp/ssh/transport.go
@@ -117,9 +117,7 @@ func (r *reader) readOnePacket() ([]byte, error) {
\treturn nil, err
}\n \tmac := packet[length-1:]
-\tif r.cipher != nil {\n-\t\tr.cipher.XORKeyStream(packet, packet[:length-1])\n-\t}\n+\tr.cipher.XORKeyStream(packet, packet[:length-1])
\n \tif r.mac != nil {\n \t\tr.mac.Write(packet[:length-1])
変更前は if r.cipher != nil
という条件分岐がありましたが、変更後はこの条件が削除され、r.cipher.XORKeyStream(packet, packet[:length-1])
が無条件に実行されるようになりました。
この変更の背景には、r.cipher
が常に有効な cipher.Stream
インターフェースの実装を持つことが保証されている、という前提があると考えられます。SSHのトランスポート層では、暗号化は常に有効であるべきであり、r.cipher
が nil
になる状態は想定されていないか、あるいは以前のコードで不要な防御的プログラミングが行われていた可能性があります。nil
チェックを削除することで、コードがより簡潔になり、実行時のオーバーヘッドもわずかながら削減されます。これは、コードの品質と効率を向上させるための典型的なリファクタリングです。
src/pkg/exp/ssh/doc.go
の変更
このファイルでは、パッケージのドキュメントと使用例が大幅に改善されています。
認証方法に関するドキュメントの改善
変更前は「Currently only the "password" authentication method is supported.」と記載されていましたが、変更後は ClientAuth
インターフェースを介して複数の認証方法をサポートできることが明確に説明されています。
--- a/src/pkg/exp/ssh/doc.go
+++ b/src/pkg/exp/ssh/doc.go
@@ -78,8 +78,26 @@ present a simple terminal interface.\n \treturn\n \t}()\n \n-An SSH client is represented with a ClientConn. Currently only the "password"\n-authentication method is supported. \n+To authenticate with the remote server you must pass at least one implementation of \n+ClientAuth via the Auth field in ClientConfig.\n+\n+\t// password implements the ClientPassword interface\n+\ttype password string\n+\n+\tfunc (p password) Password(user string) (string, error) {\n+\t\treturn string(p), nil
+\t}\n+\n+\tconfig := &ssh.ClientConfig {\n+\t\tUser: "username",\n+\t\tAuth: []ClientAuth {\n+\t\t\t// ClientAuthPassword wraps a ClientPassword implementation\n+\t\t\t// in a type that implements ClientAuth.\n+\t\t\tClientAuthPassword(password("yourpassword")),\n+\t\t}\n+\t}\n+\n+An SSH client is represented with a ClientConn. \n```
新しいドキュメントでは、`ClientConfig` の `Auth` フィールドを通じて `ClientAuth` の実装を渡す必要があることが強調されています。また、`ClientPassword` インターフェースを実装するカスタム型 `password` を定義し、それを `ClientAuthPassword` でラップして `ClientConfig` に渡す具体的なコード例が追加されています。これにより、ユーザーはパスワード認証をどのように設定すればよいかをより明確に理解できます。これは、APIの柔軟性と拡張性を示す良い例であり、ユーザーがカスタム認証ロジックを組み込む際の指針となります。
#### `Session.Run` メソッドの使用例の更新
`Session.Run` メソッドの例も更新され、より実用的なコードが示されています。
```diff
--- a/src/pkg/exp/ssh/doc.go
+++ b/src/pkg/exp/ssh/doc.go
@@ -94,12 +112,12 @@ Each ClientConn can support multiple interactive sessions, represented by a Sess\n Once a Session is created, you can execute a single command on the remote side \n using the Run method.\n \n+\tb := bytes.NewBuffer()\n+\tsession.Stdin = b\n \tif err := session.Run("/usr/bin/whoami"); err != nil {\n \t\tpanic("Failed to exec: " + err.String())\n \t}\n-\treader := bufio.NewReader(session.Stdin)\n-\tline, _, _ := reader.ReadLine()\n-\tfmt.Println(line)\n+\tfmt.Println(bytes.String())\n \tsession.Close()\n */\n package ssh
変更前は session.Stdin
から bufio.NewReader
を使って読み込み、ReadLine
で1行ずつ処理していましたが、これは Stdin
ではなく Stdout
や Stderr
から読み込むべきでした。SSHセッションの Stdin
は、リモートコマンドへの入力ストリームであり、リモートコマンドの出力を読み取るためには Stdout
や Stderr
を使用する必要があります。この誤った例が削除されたことは、ドキュメントの正確性を向上させます。
新しい例では、bytes.NewBuffer()
を作成し、それを session.Stdin
に割り当てています。これは、リモートコマンドに標準入力を提供するシナリオを示唆しています。例えば、cat
コマンドにデータをパイプで渡すような場合に利用できます。
しかし、その後の fmt.Println(bytes.String())
は、bytes.NewBuffer()
で作成した b
の内容(つまり、リモートコマンドへの入力として設定された内容)を出力することになります。リモートコマンド /usr/bin/whoami
の出力を取得するためには、session.Stdout
を bytes.Buffer
に設定し、そのバッファから読み取る必要があります。したがって、この更新された例は、Stdin
の設定方法を示していますが、リモートコマンドの出力を取得する完全な例としては不十分であるか、あるいは意図的に Stdin
の動作に焦点を当てたものと考えられます。ユーザーがリモートコマンドの出力を取得したい場合は、session.Stdout
を適切に設定する必要があることに注意が必要です。
コアとなるコードの変更箇所
diff --git a/src/pkg/exp/ssh/doc.go b/src/pkg/exp/ssh/doc.go
index 480f877191..4ea402c5d2 100644
--- a/src/pkg/exp/ssh/doc.go
+++ b/src/pkg/exp/ssh/doc.go
@@ -78,8 +78,26 @@ present a simple terminal interface.\n \treturn\n \t}()\n \n-An SSH client is represented with a ClientConn. Currently only the "password"\n-authentication method is supported. \n+To authenticate with the remote server you must pass at least one implementation of \n+ClientAuth via the Auth field in ClientConfig.\n+\n+\t// password implements the ClientPassword interface\n+\ttype password string\n+\n+\tfunc (p password) Password(user string) (string, error) {\n+\t\treturn string(p), nil
+\t}\n+\n+\tconfig := &ssh.ClientConfig {\n+\t\tUser: "username",\n+\t\tAuth: []ClientAuth {\n+\t\t\t// ClientAuthPassword wraps a ClientPassword implementation\n+\t\t\t// in a type that implements ClientAuth.\n+\t\t\tClientAuthPassword(password("yourpassword")),\n+\t\t}\n+\t}\n+\n+An SSH client is represented with a ClientConn. \n \n \tconfig := &ClientConfig{\n \t\tUser: "username",\n@@ -94,12 +112,12 @@ Each ClientConn can support multiple interactive sessions, represented by a Sess\n Once a Session is created, you can execute a single command on the remote side \n using the Run method.\n \n+\tb := bytes.NewBuffer()\n+\tsession.Stdin = b\n \tif err := session.Run("/usr/bin/whoami"); err != nil {\n \t\tpanic("Failed to exec: " + err.String())\n \t}\n-\treader := bufio.NewReader(session.Stdin)\n-\tline, _, _ := reader.ReadLine()\n-\tfmt.Println(line)\n+\tfmt.Println(bytes.String())\n \tsession.Close()\n */\n package ssh
diff --git a/src/pkg/exp/ssh/transport.go b/src/pkg/exp/ssh/transport.go
index bcd073e7ce..2e7c955a12 100644
--- a/src/pkg/exp/ssh/transport.go
+++ b/src/pkg/exp/ssh/transport.go
@@ -117,9 +117,7 @@ func (r *reader) readOnePacket() ([]byte, error) {\n \treturn nil, err\n }\n \tmac := packet[length-1:]\n-\tif r.cipher != nil {\n-\t\tr.cipher.XORKeyStream(packet, packet[:length-1])\n-\t}\n+\tr.cipher.XORKeyStream(packet, packet[:length-1])\n \n \tif r.mac != nil {\n \t\tr.mac.Write(packet[:length-1])
コアとなるコードの解説
src/pkg/exp/ssh/doc.go
認証方法に関する説明の変更
-
削除された行:
-An SSH client is represented with a ClientConn. Currently only the "password" -authentication method is supported.
この行は、以前はパスワード認証のみがサポートされていると誤解を招く可能性のある記述でした。実際のところ、
exp/ssh
パッケージはClientAuth
インターフェースを通じてより多様な認証方法をサポートする設計になっていたため、この記述は実態と合致していませんでした。 -
追加された行:
+To authenticate with the remote server you must pass at least one implementation of +ClientAuth via the Auth field in ClientConfig. + + // password implements the ClientPassword interface + type password string + + func (p password) Password(user string) (string, error) { + return string(p), nil + } + + config := &ssh.ClientConfig { + User: "username", + Auth: []ClientAuth { + // ClientAuthPassword wraps a ClientPassword implementation + // in a type that implements ClientAuth. + ClientAuthPassword(password("yourpassword")),\n+\t\t}\n+\t}\n+\n+An SSH client is represented with a ClientConn.
この追加により、
ClientConfig
のAuth
フィールドを通じてClientAuth
の実装を渡すことで、リモートサーバーと認証を行う必要があることが明確に説明されました。 具体的なコード例として、ClientPassword
インターフェースを実装するpassword
型が定義され、そのPassword
メソッドがユーザー名とパスワードを返すように実装されています。 そして、ClientAuthPassword
ヘルパー関数を使ってpassword
型のインスタンスをClientAuth
型にラップし、ClientConfig
のAuth
スライスに含める方法が示されています。これにより、ユーザーはパスワード認証をどのように設定すべきかを具体的に理解できます。これは、APIの柔軟性と拡張性を示す良い例であり、ユーザーがカスタム認証ロジックを組み込む際の指針となります。
Session.Run
メソッドの使用例の変更
-
削除された行:
- reader := bufio.NewReader(session.Stdin) - line, _, _ := reader.ReadLine() - fmt.Println(line)
このコードは、リモートコマンドの出力を
session.Stdin
から読み取ろうとしていましたが、これは誤りです。session.Stdin
はリモートコマンドへの入力ストリームであり、出力はsession.Stdout
またはsession.Stderr
から読み取る必要があります。この誤った例が削除されたことは、ドキュメントの正確性を向上させます。 -
追加された行:
+ b := bytes.NewBuffer() + session.Stdin = b
この行では、
bytes.Buffer
を作成し、それをsession.Stdin
に割り当てています。これは、リモートコマンドに標準入力を提供するシナリオを示唆しています。例えば、cat
コマンドにデータをパイプで渡すような場合に利用できます。 -
変更された行:
- fmt.Println(line) + fmt.Println(bytes.String())
この変更は、
session.Run
の後にbytes.String()
を出力しようとしていますが、これはbytes.NewBuffer()
で作成したb
の内容(つまり、リモートコマンドへの入力として設定された内容)を出力することになります。リモートコマンド/usr/bin/whoami
の出力を取得するためには、session.Stdout
をbytes.Buffer
に設定し、そのバッファから読み取る必要があります。したがって、この更新された例は、Stdin
の設定方法を示していますが、リモートコマンドの出力を取得する完全な例としては不十分であるか、あるいは意図的にStdin
の動作に焦点を当てたものと考えられます。ユーザーがリモートコマンドの出力を取得したい場合は、session.Stdout
を適切に設定する必要があることに注意が必要です。
src/pkg/exp/ssh/transport.go
-
削除された行:
- if r.cipher != nil { - r.cipher.XORKeyStream(packet, packet[:length-1]) - }
このコードブロックは、
r.cipher
がnil
でない場合にのみXORKeyStream
を呼び出すという条件分岐でした。 -
追加された行:
+ r.cipher.XORKeyStream(packet, packet[:length-1])
この変更により、
r.cipher.XORKeyStream
の呼び出しからnil
チェックが削除され、無条件に実行されるようになりました。これは、r.cipher
が常に有効な暗号ストリームとして初期化されているという前提に基づいています。SSHのトランスポート層では、暗号化は常に有効であるべきであり、このnil
チェックは冗長であったか、あるいはコードの進化に伴い不要になったと考えられます。これにより、コードの簡潔性が向上し、わずかながら実行効率も改善されます。これは、コードの品質と効率を向上させるための典型的なリファクタリングです。
関連リンク
- Go言語の
exp
パッケージに関する情報: https://pkg.go.dev/golang.org/x/exp - 現在の
golang.org/x/crypto/ssh
パッケージのドキュメント: https://pkg.go.dev/golang.org/x/crypto/ssh - SSHプロトコルに関するRFC:
- RFC 4251: The Secure Shell (SSH) Protocol Architecture (https://datatracker.ietf.org/doc/html/rfc4251)
- RFC 4252: The Secure Shell (SSH) Authentication Protocol (https://datatracker.ietf.org/doc/html/rfc4252)
参考にした情報源リンク
- Go言語公式ドキュメント
- SSHプロトコルに関する一般的な知識
- Google検索結果 (Go exp/ssh package current status)