エラーメッセージ
tls: first record does not look like a TLS handshake
gRPC やデータベースドライバーなどのライブラリは、このエラーを独自のメッセージで包むため、ログは中核となるテキストで検索してください。応答が HTTP の場合、net/http は代わりに「http: server gave HTTP response to HTTPS client」と報告します。
Go crypto/tls — the peer's first bytes are not a TLS record (tls.Dial, gRPC, database drivers, http.Client)
このエラーの意味
Go の TLS クライアントが ClientHello を送信しましたが、最初に返ってきたレコードが TLS ハンドシェイクのレコードの構造になっていないため、すぐに処理を中止しました。相手はそのポートで別のプロトコルを使って通信しています。たとえば、平文の HTTP(Go がそれと認識できない場合)、平文の gRPC サーバー、データベースやメールのプロトコルの最初の応答(グリーティング)です。逆の状況、つまりクライアントが TLS のポートに平文で話しかける場合は、Go のサーバーは別の形でログに記録します。
発生する理由
平文のポートに TLS クライアントで接続している
よくあるTLS を使っていないサービスのポート(多くはサイドカーや内部のリスナー)に、tls.Dial や https:// で接続しているケースです。
TLS の認証情報を設定した gRPC クライアントで平文のサーバーに接続している
よくある開発用サーバーは TLS なしで動いていることが多い一方で、クライアントは TLS の認証情報を使って構築されています(逆の場合は、サーバー側で失敗します)。
STARTTLS を使うプロトコルに暗黙的 TLS で接続している
ときどき587 番ポートの SMTP、PostgreSQL、MySQL、TLS を別のポートで提供している Redis が該当します。いずれも、サーバーはまず平文で通信を開始します。
修正方法
- 1.
ポートが使っているプロトコルを確認する
openssl で TLS ハンドシェイクを試すか、バナーをざっと確認すれば、そのポートがそもそも TLS なのかどうかがわかります。
bash # Does the port speak TLS? openssl s_client -connect api.example.com:8080 -servername api.example.com -brief </dev/null timeout 3 nc api.example.com 8080 </dev/null | head -1 # a text banner = plain protocol - 2.
gRPC:サーバーが想定する認証情報を使う
TLS サーバーには TLS の認証情報を使ってください。insecure の認証情報は、信頼できるネットワーク上の平文のサーバーに対してのみ使ってください。
go // gRPC: match the server. Plaintext server -> insecure credentials (only on trusted networks); // TLS server -> TLS credentials. conn, err := grpc.NewClient("api.example.com:9090", grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ServerName: "api.example.com"}))) - 3.
プロトコルが要求する場合は STARTTLS を使う
net/smtp の StartTLS(およびデータベースドライバーにある同等の機能、たとえば PostgreSQL の sslmode)は、同じ接続のまま TLS にアップグレードします。
go // SMTP on 587 starts in plain text: use STARTTLS, not tls.Dial c, err := smtp.Dial("mail.example.com:587") if err == nil { err = c.StartTLS(&tls.Config{ServerName: "mail.example.com"}) }