エラーメッセージ
gRPCサーバーをセットアップし、TLSを有効にして、準備は万端だと思った矢先、クライアントを実行すると次のような壁にぶつかることがあります。
rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate signed by unknown authority"
なぜこのエラーが発生するのか
このエラーはTLSハンドシェイク中に発生します。gRPCクライアントがサーバーの身元を検証しようとしていますが、サーバーの証明書を署名した認証局(CA)を認識できていません。CAがシステムのトラストストアに含まれていないか、コード内で明示的に定義されていない場合、安全のために接続は即座に遮断されます。
通常、以下の3つの状況でこのエラーに遭遇します:
- ローカル開発: OpenSSLで生成した自己署名証明書を使用している場合。
- プライベートCA: 組織内の内部CAを使用しており、それがDockerコンテナやOSのトラストストアに追加されていない場合。
- 不完全な証明書チェーン: サーバーが自身の証明書は提供しているが、ルートCAに到達するために必要な中間証明書の送信に失敗している場合。
ステップバイステップの修正方法
方法1:gRPCクライアントにルートCAを渡す(推奨)
内部マイクロサービスにおいて最も信頼性の高い修正方法は、CA証明書をクライアントに直接渡すことです。オペレーティングシステムのグローバルなストアに依存するのではなく、どの証明書を信頼すべきかをクライアントに明示的に伝えます。
1. CA証明書を用意する。 クライアントコードから利用可能な ca.crt ファイル(CAの公開鍵)が必要です。
2. Goクライアントでファイルを読み込む:
import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"log"
)
func main() {
// サーバー証明書を署名したCA証明書へのパス
caFile := "certs/ca.crt"
// ファイルからクライアントTLS認証情報を初期化
creds, err := credentials.NewClientTLSFromFile(caFile, "")
if err != nil {
log.Fatalf("認証情報の読み込みに失敗しました: %v", err)
}
// 特定の認証情報を使用して接続
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("接続に失敗しました: %v", err)
}
defer conn.Close()
// gRPC呼び出しを続行...
}
方法2:ホスト名の不一致への対処
信頼できるCAであっても、ホスト名が証明書と一致しない場合はハンドシェイクが失敗します。証明書が api.internal.com 用に発行されているのに localhost に接続しようとすると、Goはそれを拒否します。クライアント側でこの期待値を上書きできます:
// 第2引数で期待されるサーバー名を上書き(ServerNameOverride)
creds, err := credentials.NewClientTLSFromFile(caFile, "api.internal.com")
方法3:検証のスキップ(テスト目的のみ)
急ぎで接続の問題をデバッグする必要がある場合は、すべての証明書エラーを無視するようにクライアントに指示できます。中間者攻撃(MITM)に対して脆弱になるため、本番環境では絶対に使用しないでください。
import (
"crypto/tls"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
config := &tls.Config{
InsecureSkipVerify: true,
}
creds := credentials.NewTLS(config)
conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(creds))
修正の確認
コードを更新した後、grpcurl を使用して接続をテストします。これは、Goコードに戻る前にサーバーが正しく動作しているかを確認するのに最適な方法です:
grpcurl -cacert ca.crt localhost:50051 list
コマンドが成功すると、サービスの一覧が返されます。依然として x509 エラーが表示される場合は、証明書ファイル自体に問題がある可能性があります。
本番環境向けのヒント
証明書ファイルの小さなミスが、何時間ものフラストレーションにつながることがあります。CI/CDデプロイ中に証明書ファイルが途切れたり破損したりしても、エラーメッセージはその理由を教えてくれません。私は常に、ローカルマシンとサーバーの両方で ca.crt のSHA-256チェックサムを検証し、それらが同一であることを確認するようにしています。
素早いチェックには、ToolCraftのハッシュジェネレーターを使用しています。ブラウザ上でローカルに動作するため、証明書データが外部に漏れることはありません。ハッシュを比較するのは10秒で終わる作業ですが、多くの悩みを未然に防いでくれます。
最後に、/Users/dev/certs のようなファイルパスをハードコードするのは避けましょう。SSL_CERT_PATH などの環境変数を使用してください。これにより、ローカルのDockerコンテナで実行する場合でも、本番のKubernetesクラスターで実行する場合でも、サービスにポータビリティ(移植性)が生まれます。

