|
@@ -13,7 +13,7 @@ import (
|
|
|
"sync/atomic"
|
|
"sync/atomic"
|
|
|
"time"
|
|
"time"
|
|
|
|
|
|
|
|
- "github.com/rcrowley/go-metrics"
|
|
|
|
|
|
|
+ metrics "github.com/rcrowley/go-metrics"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
// Broker represents a single Kafka broker connection. All operations on this object are entirely concurrency-safe.
|
|
// Broker represents a single Kafka broker connection. All operations on this object are entirely concurrency-safe.
|
|
@@ -905,8 +905,10 @@ func (b *Broker) sendAndReceiveSASLHandshake(saslType SASLMechanism, version int
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Kafka 0.10.0 plans to support SASL Plain and Kerberos as per PR #812 (KIP-43)/(JIRA KAFKA-3149)
|
|
|
|
|
-// Some hosted kafka services such as IBM Message Hub already offer SASL/PLAIN auth with Kafka 0.9
|
|
|
|
|
|
|
+// Kafka 0.10.x supported SASL PLAIN/Kerberos via KAFKA-3149 (KIP-43).
|
|
|
|
|
+// Kafka 1.x.x onward added a SaslAuthenticate request/response message which
|
|
|
|
|
+// wraps the SASL flow in the Kafka protocol, which allows for returning
|
|
|
|
|
+// meaningful errors on authentication failure.
|
|
|
//
|
|
//
|
|
|
// In SASL Plain, Kafka expects the auth header to be in the following format
|
|
// In SASL Plain, Kafka expects the auth header to be in the following format
|
|
|
// Message format (from https://tools.ietf.org/html/rfc4616):
|
|
// Message format (from https://tools.ietf.org/html/rfc4616):
|
|
@@ -920,18 +922,37 @@ func (b *Broker) sendAndReceiveSASLHandshake(saslType SASLMechanism, version int
|
|
|
// SAFE = UTF1 / UTF2 / UTF3 / UTF4
|
|
// SAFE = UTF1 / UTF2 / UTF3 / UTF4
|
|
|
// ;; any UTF-8 encoded Unicode character except NUL
|
|
// ;; any UTF-8 encoded Unicode character except NUL
|
|
|
//
|
|
//
|
|
|
|
|
+// With SASL v0 handshake and auth then:
|
|
|
// When credentials are valid, Kafka returns a 4 byte array of null characters.
|
|
// When credentials are valid, Kafka returns a 4 byte array of null characters.
|
|
|
-// When credentials are invalid, Kafka closes the connection. This does not seem to be the ideal way
|
|
|
|
|
-// of responding to bad credentials but thats how its being done today.
|
|
|
|
|
|
|
+// When credentials are invalid, Kafka closes the connection.
|
|
|
|
|
+//
|
|
|
|
|
+// With SASL v1 handshake and auth then:
|
|
|
|
|
+// When credentials are invalid, Kafka replies with a SaslAuthenticate response
|
|
|
|
|
+// containing an error code and message detailing the authentication failure.
|
|
|
func (b *Broker) sendAndReceiveSASLPlainAuth() error {
|
|
func (b *Broker) sendAndReceiveSASLPlainAuth() error {
|
|
|
|
|
+ // default to V0 to allow for backward compatability when SASL is enabled
|
|
|
|
|
+ // but not the handshake
|
|
|
|
|
+ saslHandshake := SASLHandshakeV0
|
|
|
if b.conf.Net.SASL.Handshake {
|
|
if b.conf.Net.SASL.Handshake {
|
|
|
- handshakeErr := b.sendAndReceiveSASLHandshake(SASLTypePlaintext, SASLHandshakeV0)
|
|
|
|
|
|
|
+ if b.conf.Version.IsAtLeast(V1_0_0_0) {
|
|
|
|
|
+ saslHandshake = SASLHandshakeV1
|
|
|
|
|
+ }
|
|
|
|
|
+ handshakeErr := b.sendAndReceiveSASLHandshake(SASLTypePlaintext, saslHandshake)
|
|
|
if handshakeErr != nil {
|
|
if handshakeErr != nil {
|
|
|
Logger.Printf("Error while performing SASL handshake %s\n", b.addr)
|
|
Logger.Printf("Error while performing SASL handshake %s\n", b.addr)
|
|
|
return handshakeErr
|
|
return handshakeErr
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if saslHandshake == SASLHandshakeV1 {
|
|
|
|
|
+ return b.sendAndReceiveV1SASLPlainAuth()
|
|
|
|
|
+ }
|
|
|
|
|
+ return b.sendAndReceiveV0SASLPlainAuth()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// sendAndReceiveV0SASLPlainAuth flows the v0 sasl auth NOT wrapped in the kafka protocol
|
|
|
|
|
+func (b *Broker) sendAndReceiveV0SASLPlainAuth() error {
|
|
|
|
|
+
|
|
|
length := 1 + len(b.conf.Net.SASL.User) + 1 + len(b.conf.Net.SASL.Password)
|
|
length := 1 + len(b.conf.Net.SASL.User) + 1 + len(b.conf.Net.SASL.Password)
|
|
|
authBytes := make([]byte, length+4) //4 byte length header + auth data
|
|
authBytes := make([]byte, length+4) //4 byte length header + auth data
|
|
|
binary.BigEndian.PutUint32(authBytes, uint32(length))
|
|
binary.BigEndian.PutUint32(authBytes, uint32(length))
|
|
@@ -965,6 +986,35 @@ func (b *Broker) sendAndReceiveSASLPlainAuth() error {
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// sendAndReceiveV1SASLPlainAuth flows the v1 sasl authentication using the kafka protocol
|
|
|
|
|
+func (b *Broker) sendAndReceiveV1SASLPlainAuth() error {
|
|
|
|
|
+ correlationID := b.correlationID
|
|
|
|
|
+
|
|
|
|
|
+ requestTime := time.Now()
|
|
|
|
|
+
|
|
|
|
|
+ bytesWritten, err := b.sendSASLPlainAuthClientResponse(correlationID)
|
|
|
|
|
+
|
|
|
|
|
+ b.updateOutgoingCommunicationMetrics(bytesWritten)
|
|
|
|
|
+
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ Logger.Printf("Failed to write SASL auth header to broker %s: %s\n", b.addr, err.Error())
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ b.correlationID++
|
|
|
|
|
+
|
|
|
|
|
+ bytesRead, err := b.receiveSASLServerResponse(correlationID)
|
|
|
|
|
+ b.updateIncomingCommunicationMetrics(bytesRead, time.Since(requestTime))
|
|
|
|
|
+
|
|
|
|
|
+ // With v1 sasl we get an error message set in the response we can return
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ Logger.Printf("Error returned from broker during SASL flow %s: %s\n", b.addr, err.Error())
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// sendAndReceiveSASLOAuth performs the authentication flow as described by KIP-255
|
|
// sendAndReceiveSASLOAuth performs the authentication flow as described by KIP-255
|
|
|
// https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=75968876
|
|
// https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=75968876
|
|
|
func (b *Broker) sendAndReceiveSASLOAuth(provider AccessTokenProvider) error {
|
|
func (b *Broker) sendAndReceiveSASLOAuth(provider AccessTokenProvider) error {
|
|
@@ -988,7 +1038,7 @@ func (b *Broker) sendAndReceiveSASLOAuth(provider AccessTokenProvider) error {
|
|
|
b.updateOutgoingCommunicationMetrics(bytesWritten)
|
|
b.updateOutgoingCommunicationMetrics(bytesWritten)
|
|
|
b.correlationID++
|
|
b.correlationID++
|
|
|
|
|
|
|
|
- bytesRead, err := b.receiveSASLOAuthBearerServerResponse(correlationID)
|
|
|
|
|
|
|
+ bytesRead, err := b.receiveSASLServerResponse(correlationID)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return err
|
|
return err
|
|
|
}
|
|
}
|
|
@@ -1123,6 +1173,23 @@ func mapToString(extensions map[string]string, keyValSep string, elemSep string)
|
|
|
return strings.Join(buf, elemSep)
|
|
return strings.Join(buf, elemSep)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+func (b *Broker) sendSASLPlainAuthClientResponse(correlationID int32) (int, error) {
|
|
|
|
|
+ authBytes := []byte("\x00" + b.conf.Net.SASL.User + "\x00" + b.conf.Net.SASL.Password)
|
|
|
|
|
+ rb := &SaslAuthenticateRequest{authBytes}
|
|
|
|
|
+ req := &request{correlationID: correlationID, clientID: b.conf.ClientID, body: rb}
|
|
|
|
|
+ buf, err := encode(req, b.conf.MetricRegistry)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return 0, err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ err = b.conn.SetWriteDeadline(time.Now().Add(b.conf.Net.WriteTimeout))
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ Logger.Printf("Failed to set write deadline when doing SASL auth with broker %s: %s\n", b.addr, err.Error())
|
|
|
|
|
+ return 0, err
|
|
|
|
|
+ }
|
|
|
|
|
+ return b.conn.Write(buf)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func (b *Broker) sendSASLOAuthBearerClientResponse(token *AccessToken, correlationID int32) (int, error) {
|
|
func (b *Broker) sendSASLOAuthBearerClientResponse(token *AccessToken, correlationID int32) (int, error) {
|
|
|
initialResp, err := buildClientInitialResponse(token)
|
|
initialResp, err := buildClientInitialResponse(token)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
@@ -1145,7 +1212,7 @@ func (b *Broker) sendSASLOAuthBearerClientResponse(token *AccessToken, correlati
|
|
|
return b.conn.Write(buf)
|
|
return b.conn.Write(buf)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func (b *Broker) receiveSASLOAuthBearerServerResponse(correlationID int32) (int, error) {
|
|
|
|
|
|
|
+func (b *Broker) receiveSASLServerResponse(correlationID int32) (int, error) {
|
|
|
|
|
|
|
|
buf := make([]byte, responseLengthSize+correlationIDSize)
|
|
buf := make([]byte, responseLengthSize+correlationIDSize)
|
|
|
|
|
|