Сообщение об ошибке
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)
Что это означает
TLS-клиент Go отправил ClientHello, а первая полученная в ответ запись не имеет структуры записи TLS-рукопожатия, поэтому клиент сразу сдаётся. Другая сторона говорит на этом порту по другому протоколу: незашифрованный HTTP (когда Go не может распознать его как HTTP), gRPC-сервер без шифрования, приветствие базы данных или почтового протокола. Обратную ситуацию, когда клиент обращается к TLS-порту без шифрования, серверы на Go записывают в журнал иначе.
Почему это происходит
TLS-клиент обращается к порту без шифрования
частоПодключение через tls.Dial или по https:// к порту сервиса без TLS — часто к sidecar-контейнеру или внутреннему слушателю.
Клиент gRPC с учётными данными TLS обращается к серверу без шифрования
частоСерверы для разработки часто работают без TLS, а клиент собран с учётными данными TLS (или наоборот, и тогда ошибка возникает на сервере).
Неявный TLS с протоколом, использующим STARTTLS
иногдаSMTP на порту 587, PostgreSQL, MySQL, Redis с TLS на другом порту: сначала сервер отправляет приветствие без шифрования.
Как исправить
- 1.
Проверьте, по какому протоколу работает порт
TLS-рукопожатие через openssl или беглый взгляд на баннер покажет, работает ли порт по 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 там, где этого требует протокол
StartTLS из net/smtp (и аналоги в драйверах баз данных, например sslmode для PostgreSQL) переводит соединение на 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"}) }