错误信息
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 没能把它识别为 HTTP 的情况下)、明文 gRPC 服务器,或者数据库、邮件协议的问候消息。对于相反的情况,即客户端用明文访问 TLS 端口,Go 服务器记录的日志内容不同。
原因
TLS 客户端连接了明文端口
常见用 tls.Dial 或 https:// 连接了未启用 TLS 的服务端口,这类端口通常属于 sidecar 或内部监听器。
使用 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"}) }