x/crypto/ssh: add AuthCallback to ClientConfig
要約
概要
x/crypto/sshパッケージのクライアント認証において、実行時に認証方法を動的に選択できるAuthCallbackフィールドをClientConfigに追加する提案です。これにより、サーバーから返されるメタデータや部分成功(partial success)の情報に基づいて、認証戦略を柔軟に変更できるようになります。
ステータス変更
(なし) → active
2026年1月28日にプロポーザルレビューグループにより、提案が正式にレビュー対象としてactiveステータスに移行しました。Teleportなどのエンタープライズ用途でのセキュリティ強化要望が背景にあり、特に@cthach氏からは「何百、何千もの組織のセキュリティ改善につながる」として早期実装を求める声が寄せられています。
技術的背景
現状の問題点
現在のx/crypto/sshパッケージでは、認証方法はClientConfig.Authに設定したAuthMethodの配列を順番に試行する静的な仕組みです。
// 現在の実装: 認証方法は事前に固定
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer), // 1番目に試行
ssh.Password("password"), // 2番目に試行
},
HostKeyCallback: ssh.FixedHostKey(hostKey),
}
この方式には以下の制限があります:
- 動的な決定が不可能: サーバーが返すメタデータ(サポートする認証方法、部分成功情報など)に基づいて認証方法を選択できない
- 認証の中断ができない: ランタイムコンテキストやポリシーに基づいて認証プロセスを中止する手段がない
- 情報へのアクセス制限:
ConnMetadataやNegotiatedAlgorithmsは認証完了後にしかアクセスできず、認証プロセス中には利用できない - 多段階認証への対応不足: RFC 4252で定義された部分成功(partial success)フラグを活用した柔軟な多段階認証を実装しにくい
提案された解決策
ClientConfigに新しいオプショナルなコールバックフィールドAuthCallbackを追加します:
type ClientConfig struct {
// ... 既存フィールド
// AuthCallback は各認証試行の前に呼び出されるオプショナルなフック。
// コールバックは接続メタデータ、ネゴシエートされたアルゴリズム、
// サーバーがサポートする認証方法、既に成功した方法(部分成功時)、
// 失敗した方法を検査できる。
//
// 戻り値:
// - 非nilエラー: 認証プロセスが即座に停止
// - 非nil AuthMethodとnil エラー: 返されたAuthMethodが次に試行される
// (ClientConfig.Authの方法の代わり)
// - nil AuthMethodとnil エラー: ClientConfig.Authで定義された方法が試行される
AuthCallback func(
conn ConnMetadata,
algorithms NegotiatedAlgorithms,
supportedAuthMethods []string, // サーバーがサポートする方法
succeededAuthMethods []string, // 既に成功した方法(部分成功時)
failedAuthMethods []string, // 既に失敗した方法
) (AuthMethod, error)
}
呼び出しタイミング: 初回のnone認証の後、サーバーのサポートする認証方法が判明した時点で呼び出され、以降の各認証試行の前にも呼び出されます。
後方互換性: AuthCallbackが設定されていない場合、既存の動作と完全に互換性があります。
これによって何ができるようになるか
1. サーバーメタデータに基づく動的な認証方法選択
サーバーが返す情報に基づいて、最適な認証方法を実行時に決定できます。例えば、サーバーが特定の認証方法をサポートしている場合のみ、その方法を試行するなど。
2. 多段階認証の高度な制御
RFC 4252の部分成功メカニズムを活用し、既に成功した認証ステップに基づいて次の認証方法を動的に選択できます。これはTeleportのような高度なSSHプロキシシステムでの要件です。
3. 認証プロセスの中断
ポリシー違反や特定の条件下で、認証プロセスを早期に中断できます。
4. アウトオブバンド認証との統合
サーバーから返されるカスタム認証方法名(例: "web-auth")を検知し、ブラウザベースの認証フローへリダイレクトするなど、従来の枠を超えた認証フローを実装できます。
コード例
Before: 従来の静的な認証
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
ssh.PublicKeys(keySigner),
ssh.Password("password"),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// 問題:
// - サーバーがどの認証方法をサポートしているか分からないまま試行
// - 部分成功の情報を活用できない
// - 動的に認証戦略を変更できない
After: AuthCallbackを使った動的な認証
config := &ssh.ClientConfig{
User: "username",
Auth: []ssh.AuthMethod{
// デフォルトの認証方法(AuthCallbackが何も返さない場合)
ssh.PublicKeys(keySigner),
},
AuthCallback: func(
conn ssh.ConnMetadata,
algorithms ssh.NegotiatedAlgorithms,
supported, succeeded, failed []string,
) (ssh.AuthMethod, error) {
// サーバーがサポートする方法に応じて動的に選択
if containsMethod(supported, "publickey") && containsMethod(succeeded, "password") {
// パスワード認証が既に成功している場合のみ公開鍵認証を試行
return ssh.PublicKeys(keySigner), nil
}
if containsMethod(supported, "keyboard-interactive") {
// サーバーがキーボードインタラクティブをサポートしている場合
return ssh.KeyboardInteractive(challengeHandler), nil
}
// 特定の条件で認証を中断
if isAuthenticationBlocked(conn) {
return nil, fmt.Errorf("authentication blocked by policy")
}
// デフォルトの方法を使用
return nil, nil
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
議論のハイライト
- 提案者: @drakkan氏(x/crypto/sshのメンバー)が2025年11月2日に提案し、同日CL 717140で実装を提出
- 実装の影響範囲: 完全に後方互換性があり、既存コードへの影響はゼロ。
AuthCallbackは純粋にオプトイン機能 - 実用的需要: Teleport(エンタープライズSSHアクセス管理システム)での実装要望が明確に示され、セキュリティ強化の緊急性が強調されている
- 関連提案との統合: サーバーサイドの動的認証方法選択を扱うIssue #64974と対になる提案であり、クライアント・サーバー双方向での柔軟な認証制御を実現
- 技術的根拠: RFC 4252の部分成功メカニズムと、Issue #23461で議論された多段階認証の課題に対する解決策
- レビュー状況: 2026年1月28日に@aclements氏により正式にactiveステータスへ移行し、週次プロポーザルレビューミーティングでの審議対象となった
Sources:
- ssh package - golang.org/x/crypto/ssh - Go Packages
- proposal: x/crypto/ssh: dynamic auth method selection in ServerConfig · Issue #64974 · golang/go
- x/crypto/ssh: Client Auth: handle partial success correctly · Issue #23461 · golang/go
- RFC 4252 - The Secure Shell (SSH) Authentication Protocol
- Teleport GitHub Repository
- client package - github.com/gravitational/teleport/api/client