Skip to content

Commit 34e8f71

Browse files
attempt to detect improper TLS config (#843)
* attempt to detect improper TLS config when an io.ErrUnexpectedEOF is returned when reading a correlation ID, attempt to detect if the messages from the broker was a TLS Alert Message and decorate the returned error * update TLS info in README.md * simplify tls version check Co-authored-by: Achille <[email protected]>
1 parent b952e63 commit 34e8f71

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,8 @@ longer the case and import of the compression packages are now no-ops._
480480
## TLS Support
481481

482482
For a bare bones Conn type or in the Reader/Writer configs you can specify a dialer option for TLS support. If the TLS field is nil, it will not connect with TLS.
483+
*Note:* Connecting to a Kafka cluster with TLS enabled without configuring TLS on the Conn/Reader/Writer can manifest in opaque io.ErrUnexpectedEOF errors.
484+
483485

484486
### Connection
485487

@@ -512,6 +514,8 @@ r := kafka.NewReader(kafka.ReaderConfig{
512514

513515
### Writer
514516

517+
Using `kafka.NewWriter`
518+
515519
```go
516520
dialer := &kafka.Dialer{
517521
Timeout: 10 * time.Second,
@@ -527,6 +531,20 @@ w := kafka.NewWriter(kafka.WriterConfig{
527531
})
528532
```
529533

534+
Direct Writer creation
535+
536+
```go
537+
w := kafka.Writer{
538+
Addr: kafka.TCP("localhost:9093"),
539+
Topic: "topic-A",
540+
Balancer: &kafka.Hash{},
541+
Transport: &kafka.Transport{
542+
TLS: &tls.Config{},
543+
},
544+
}
545+
546+
```
547+
530548
## SASL Support
531549

532550
You can specify an option on the `Dialer` to use SASL authentication. The `Dialer` can be used directly to open a `Conn` or it can be passed to a `Reader` or `Writer` via their respective configs. If the `SASLMechanism` field is `nil`, it will not authenticate with SASL.

protocol/response.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package protocol
22

33
import (
4+
"crypto/tls"
5+
"encoding/binary"
6+
"errors"
47
"fmt"
58
"io"
69
)
@@ -35,6 +38,25 @@ func ReadResponse(r io.Reader, apiKey ApiKey, apiVersion int16) (correlationID i
3538

3639
d.remain = int(size)
3740
correlationID = d.readInt32()
41+
if err = d.err; err != nil {
42+
if errors.Is(err, io.ErrUnexpectedEOF) {
43+
// If a Writer/Reader is configured without TLS and connects
44+
// to a broker expecting TLS the only message we return to the
45+
// caller is io.ErrUnexpetedEOF which is opaque. This section
46+
// tries to determine if that's what has happened.
47+
// We first deconstruct the initial 4 bytes of the message
48+
// from the size which was read earlier.
49+
// Next, we examine those bytes to see if they looks like a TLS
50+
// error message. If they do we wrap the io.ErrUnexpectedEOF
51+
// with some context.
52+
if looksLikeUnexpectedTLS(size) {
53+
err = fmt.Errorf("%w: broker appears to be expecting TLS", io.ErrUnexpectedEOF)
54+
}
55+
return
56+
}
57+
err = dontExpectEOF(err)
58+
return
59+
}
3860

3961
res := &t.responses[apiVersion-minVersion]
4062

@@ -109,3 +131,21 @@ func WriteResponse(w io.Writer, apiVersion int16, correlationID int32, msg Messa
109131

110132
return err
111133
}
134+
135+
const (
136+
tlsAlertByte byte = 0x15
137+
)
138+
139+
// looksLikeUnexpectedTLS returns true if the size passed in resemble
140+
// the TLS alert message that is returned to a client which sends
141+
// an invalid ClientHello message.
142+
func looksLikeUnexpectedTLS(size int32) bool {
143+
var sizeBytes [4]byte
144+
binary.BigEndian.PutUint32(sizeBytes[:], uint32(size))
145+
146+
if sizeBytes[0] != tlsAlertByte {
147+
return false
148+
}
149+
version := int(sizeBytes[1])<<8 | int(sizeBytes[2])
150+
return version <= tls.VersionTLS13 && version >= tls.VersionTLS10
151+
}

protocol/response_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package protocol
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"io"
7+
"strings"
8+
"testing"
9+
)
10+
11+
func TestReadResponseUnexpectedTLSDetection(t *testing.T) {
12+
var buf bytes.Buffer
13+
14+
buf.Write([]byte{tlsAlertByte, 0x03, 0x03, 10, 0, 0, 0})
15+
16+
correlationID, _, err := ReadResponse(&buf, ApiVersions, 0)
17+
if !errors.Is(err, io.ErrUnexpectedEOF) {
18+
t.Fatalf("expected an io.ErrUnexpectedEOF from ReadResponse got %v", err)
19+
}
20+
21+
if !strings.Contains(err.Error(), "broker appears to be expecting TLS") {
22+
t.Fatalf("expected error messae to contain %s got %s", "broker appears to be expecting TLS", err.Error())
23+
}
24+
25+
if correlationID != 0 {
26+
t.Fatalf("expected correlationID of 0 got %d", correlationID)
27+
}
28+
}

0 commit comments

Comments
 (0)