Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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つです。

  1. transport.go ファイルから未使用の nil チェックを削除すること。これはコードの冗長性を排除し、よりクリーンで効率的なコードベースを目指すものです。
  2. 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エージェントを使用して秘密鍵を管理し、認証を行います。

ClientConfigClientAuth

exp/ssh パッケージにおいて、ClientConfig はSSHクライアントの接続設定を定義するための構造体です。これには接続先のユーザー名、認証情報、タイムアウトなどの設定が含まれます。 ClientAuth は、SSHクライアントがサーバーに対して自身を認証するためのインターフェースです。このインターフェースを実装することで、様々な認証方法(パスワード、公開鍵など)を ClientConfig に組み込むことができます。

ClientConnSession

ClientConn は、確立されたSSH接続を表します。この接続を通じて、複数の論理的なチャネル(セッション)を開くことができます。 Session は、SSH接続上で開かれる論理的なチャネルの一つで、通常はリモートコマンドの実行やシェルセッションの提供に使用されます。

Session.Run メソッド

Session オブジェクトの Run メソッドは、リモートSSHサーバー上で単一のコマンドを実行するために使用されます。コマンドの標準出力や標準エラーは、セッションの対応するストリームを通じてクライアントに返されます。

cipher.StreamXORKeyStream

cipher.Stream は、Go言語の crypto/cipher パッケージで定義されているインターフェースで、ストリーム暗号の操作を提供します。XORKeyStream メソッドは、入力バイトスライスと出力バイトスライスを、ストリーム暗号の鍵ストリームとXOR演算することで、暗号化または復号化を行います。これは、SSHのトランスポート層におけるデータの秘匿性を確保するために利用されます。

MAC (Message Authentication Code)

MACは、メッセージの完全性と認証を保証するための暗号技術です。メッセージと秘密鍵から固定長の値を生成し、受信側で同じ計算を行うことで、メッセージが改ざんされていないこと、および送信者が正当であることを確認します。

技術的詳細

このコミットは、src/pkg/exp/ssh/doc.gosrc/pkg/exp/ssh/transport.go の2つのファイルに影響を与えています。

src/pkg/exp/ssh/transport.go の変更

このファイルでは、SSHトランスポート層の処理に関連する変更が行われています。具体的には、readOnePacket メソッド内の r.ciphernil チェックが削除されました。

--- 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.ciphernil になる状態は想定されていないか、あるいは以前のコードで不要な防御的プログラミングが行われていた可能性があります。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 ではなく StdoutStderr から読み込むべきでした。SSHセッションの Stdin は、リモートコマンドへの入力ストリームであり、リモートコマンドの出力を読み取るためには StdoutStderr を使用する必要があります。この誤った例が削除されたことは、ドキュメントの正確性を向上させます。

新しい例では、bytes.NewBuffer() を作成し、それを session.Stdin に割り当てています。これは、リモートコマンドに標準入力を提供するシナリオを示唆しています。例えば、cat コマンドにデータをパイプで渡すような場合に利用できます。

しかし、その後の fmt.Println(bytes.String()) は、bytes.NewBuffer() で作成した b の内容(つまり、リモートコマンドへの入力として設定された内容)を出力することになります。リモートコマンド /usr/bin/whoami の出力を取得するためには、session.Stdoutbytes.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.
    

    この追加により、ClientConfigAuth フィールドを通じて ClientAuth の実装を渡すことで、リモートサーバーと認証を行う必要があることが明確に説明されました。 具体的なコード例として、ClientPassword インターフェースを実装する password 型が定義され、その Password メソッドがユーザー名とパスワードを返すように実装されています。 そして、ClientAuthPassword ヘルパー関数を使って password 型のインスタンスを ClientAuth 型にラップし、ClientConfigAuth スライスに含める方法が示されています。これにより、ユーザーはパスワード認証をどのように設定すべきかを具体的に理解できます。これは、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.Stdoutbytes.Buffer に設定し、そのバッファから読み取る必要があります。したがって、この更新された例は、Stdin の設定方法を示していますが、リモートコマンドの出力を取得する完全な例としては不十分であるか、あるいは意図的に Stdin の動作に焦点を当てたものと考えられます。ユーザーがリモートコマンドの出力を取得したい場合は、session.Stdout を適切に設定する必要があることに注意が必要です。

src/pkg/exp/ssh/transport.go

  • 削除された行:

    -	if r.cipher != nil {
    -		r.cipher.XORKeyStream(packet, packet[:length-1])
    -	}
    

    このコードブロックは、r.ciphernil でない場合にのみ XORKeyStream を呼び出すという条件分岐でした。

  • 追加された行:

    +	r.cipher.XORKeyStream(packet, packet[:length-1])
    

    この変更により、r.cipher.XORKeyStream の呼び出しから nil チェックが削除され、無条件に実行されるようになりました。これは、r.cipher が常に有効な暗号ストリームとして初期化されているという前提に基づいています。SSHのトランスポート層では、暗号化は常に有効であるべきであり、この nil チェックは冗長であったか、あるいはコードの進化に伴い不要になったと考えられます。これにより、コードの簡潔性が向上し、わずかながら実行効率も改善されます。これは、コードの品質と効率を向上させるための典型的なリファクタリングです。

関連リンク

参考にした情報源リンク

  • Go言語公式ドキュメント
  • SSHプロトコルに関する一般的な知識
  • Google検索結果 (Go exp/ssh package current status)