Skip to content

Commit 603e8f8

Browse files
authored
feat: add SASLv1 support for Kerberos (#3279)
For some reason we were still pinning the old v0 SASL when using GSSAPI and this doesn't work if an ApiVersionsRequest is sent before the auth flow. Add support for sending the krb5 bytes in the v1 SASL SaslAuthenticate protocol wrapping. Note: this is just a draft for now as although it is tested and working against a GSSAPI enabled cluster, the underlying code is very much a copy-and-paste of the existing v0 auth flow, just wrappering the bytes in the SaslAuthenticate rather than sending them to the broker directly. This needs more work to tidyup the implementation --------- Signed-off-by: Dominic Evans <[email protected]>
1 parent ba20ded commit 603e8f8

File tree

5 files changed

+123
-56
lines changed

5 files changed

+123
-56
lines changed

broker.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ func (b *Broker) Open(conf *Config) error {
242242
conf.Net.SASL.Version = SASLHandshakeV1
243243
}
244244

245-
useSaslV0 := conf.Net.SASL.Version == SASLHandshakeV0 || conf.Net.SASL.Mechanism == SASLTypeGSSAPI
245+
useSaslV0 := conf.Net.SASL.Version == SASLHandshakeV0
246246
if conf.Net.SASL.Enable && useSaslV0 {
247247
b.connErr = b.authenticateViaSASLv0()
248248

@@ -1379,6 +1379,12 @@ func (b *Broker) authenticateViaSASLv1() error {
13791379
}
13801380

13811381
switch b.conf.Net.SASL.Mechanism {
1382+
case SASLTypeGSSAPI:
1383+
b.kerberosAuthenticator.Config = &b.conf.Net.SASL.GSSAPI
1384+
if b.kerberosAuthenticator.NewKerberosClientFunc == nil {
1385+
b.kerberosAuthenticator.NewKerberosClientFunc = NewKerberosClient
1386+
}
1387+
return b.kerberosAuthenticator.AuthorizeV2(b, authSendReceiver)
13821388
case SASLTypeOAuth:
13831389
provider := b.conf.Net.SASL.TokenProvider
13841390
return b.sendAndReceiveSASLOAuth(authSendReceiver, provider)

broker_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ func TestGSSAPIKerberosAuth_Authorize(t *testing.T) {
666666
},
667667
{
668668
name: "Kerberos client creation fails",
669-
error: errors.New("configuration file could not be opened: krb5.conf open krb5.conf: no such file or directory"),
669+
error: errors.New("configuration file could not be opened: testdata/krb5.conf open testdata/krb5.conf: no such file or directory"),
670670
},
671671
{
672672
name: "Bad server response, unmarshall key error",
@@ -701,10 +701,11 @@ func TestGSSAPIKerberosAuth_Authorize(t *testing.T) {
701701
broker.requestsInFlight = metrics.NilCounter{}
702702

703703
conf := NewTestConfig()
704+
conf.Net.SASL.Version = SASLHandshakeV0
704705
conf.Net.SASL.Mechanism = SASLTypeGSSAPI
705706
conf.Net.SASL.Enable = true
706707
conf.Net.SASL.GSSAPI.ServiceName = "kafka"
707-
conf.Net.SASL.GSSAPI.KerberosConfigPath = "krb5.conf"
708+
conf.Net.SASL.GSSAPI.KerberosConfigPath = "testdata/krb5.conf"
708709
conf.Net.SASL.GSSAPI.Realm = "EXAMPLE.COM"
709710
conf.Net.SASL.GSSAPI.Username = "kafka"
710711
conf.Net.SASL.GSSAPI.Password = "kafka"

config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,9 @@ func (c *Config) Validate() error {
655655
if c.Net.SASL.Mechanism == "" {
656656
c.Net.SASL.Mechanism = SASLTypePlaintext
657657
}
658-
658+
if c.Net.SASL.Version == SASLHandshakeV0 && c.ApiVersionsRequest {
659+
return ConfigurationError("ApiVersionsRequest must be disabled when SASL v0 is enabled")
660+
}
659661
switch c.Net.SASL.Mechanism {
660662
case SASLTypePlaintext:
661663
if c.Net.SASL.User == "" {

gssapi_kerberos.go

Lines changed: 108 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"math"
9+
"net"
910
"strings"
1011
"time"
1112

@@ -108,16 +109,14 @@ func (krbAuth *GSSAPIKerberosAuth) newAuthenticatorChecksum() []byte {
108109
return a
109110
}
110111

111-
/*
112-
*
113-
* Construct Kerberos AP_REQ package, conforming to RFC-4120
114-
* https://tools.ietf.org/html/rfc4120#page-84
115-
*
116-
*/
112+
// Construct Kerberos AP_REQ package, conforming to RFC-4120
113+
// https://tools.ietf.org/html/rfc4120#page-84
117114
func (krbAuth *GSSAPIKerberosAuth) createKrb5Token(
118-
domain string, cname types.PrincipalName,
115+
domain string,
116+
cname types.PrincipalName,
119117
ticket messages.Ticket,
120-
sessionKey types.EncryptionKey) ([]byte, error) {
118+
sessionKey types.EncryptionKey,
119+
) ([]byte, error) {
121120
auth, err := types.NewAuthenticator(domain, cname)
122121
if err != nil {
123122
return nil, err
@@ -144,16 +143,12 @@ func (krbAuth *GSSAPIKerberosAuth) createKrb5Token(
144143
return aprBytes, nil
145144
}
146145

147-
/*
148-
*
149-
* Append the GSS-API header to the payload, conforming to RFC-2743
150-
* Section 3.1, Mechanism-Independent Token Format
151-
*
152-
* https://tools.ietf.org/html/rfc2743#page-81
153-
*
154-
* GSSAPIHeader + <specific mechanism payload>
155-
*
156-
*/
146+
// Append the GSS-API header to the payload, conforming to RFC-2743
147+
// Section 3.1, Mechanism-Independent Token Format
148+
//
149+
// https://tools.ietf.org/html/rfc2743#page-81
150+
//
151+
// GSSAPIHeader + <specific mechanism payload>
157152
func (krbAuth *GSSAPIKerberosAuth) appendGSSAPIHeader(payload []byte) ([]byte, error) {
158153
oidBytes, err := asn1.Marshal(gssapi.OIDKRB5.OID())
159154
if err != nil {
@@ -166,12 +161,15 @@ func (krbAuth *GSSAPIKerberosAuth) appendGSSAPIHeader(payload []byte) ([]byte, e
166161
return GSSPackage, nil
167162
}
168163

169-
func (krbAuth *GSSAPIKerberosAuth) initSecContext(bytes []byte, kerberosClient KerberosClient) ([]byte, error) {
164+
func (krbAuth *GSSAPIKerberosAuth) initSecContext(
165+
client KerberosClient,
166+
bytes []byte,
167+
) ([]byte, error) {
170168
switch krbAuth.step {
171169
case GSS_API_INITIAL:
172170
aprBytes, err := krbAuth.createKrb5Token(
173-
kerberosClient.Domain(),
174-
kerberosClient.CName(),
171+
client.Domain(),
172+
client.CName(),
175173
krbAuth.ticket,
176174
krbAuth.encKey)
177175
if err != nil {
@@ -200,63 +198,123 @@ func (krbAuth *GSSAPIKerberosAuth) initSecContext(bytes []byte, kerberosClient K
200198
return nil, nil
201199
}
202200

203-
/* This does the handshake for authorization */
204-
func (krbAuth *GSSAPIKerberosAuth) Authorize(broker *Broker) error {
205-
kerberosClient, err := krbAuth.NewKerberosClientFunc(krbAuth.Config)
206-
if err != nil {
207-
Logger.Printf("Kerberos client error: %s", err)
208-
return err
209-
}
210-
211-
err = kerberosClient.Login()
212-
if err != nil {
213-
Logger.Printf("Kerberos client error: %s", err)
214-
return err
215-
}
216-
// Construct SPN using serviceName and host
217-
// default SPN format: <SERVICE>/<FQDN>
218-
219-
host := strings.SplitN(broker.addr, ":", 2)[0] // Strip port part
201+
func (krbAuth *GSSAPIKerberosAuth) spn(broker *Broker) string {
202+
host, _, _ := net.SplitHostPort(broker.addr)
220203
var spn string
221204
if krbAuth.Config.BuildSpn != nil {
222205
spn = krbAuth.Config.BuildSpn(broker.conf.Net.SASL.GSSAPI.ServiceName, host)
223206
} else {
224207
spn = fmt.Sprintf("%s/%s", broker.conf.Net.SASL.GSSAPI.ServiceName, host)
225208
}
209+
return spn
210+
}
226211

227-
ticket, encKey, err := kerberosClient.GetServiceTicket(spn)
212+
// Login will use the given KerberosClient to login and get a ticket for the given spn.
213+
func (krbAuth *GSSAPIKerberosAuth) Login(
214+
client KerberosClient,
215+
spn string,
216+
) (*messages.Ticket, error) {
217+
if err := client.Login(); err != nil {
218+
Logger.Printf("Kerberos client login error: %s", err)
219+
return nil, err
220+
}
221+
222+
ticket, encKey, err := client.GetServiceTicket(spn)
228223
if err != nil {
229-
Logger.Printf("Error getting Kerberos service ticket : %s", err)
230-
return err
224+
Logger.Printf("Kerberos service ticket error for %s: %s", spn, err)
225+
return nil, err
231226
}
232227
krbAuth.ticket = ticket
233228
krbAuth.encKey = encKey
234229
krbAuth.step = GSS_API_INITIAL
235-
var receivedBytes []byte = nil
236-
defer kerberosClient.Destroy()
230+
231+
return &ticket, nil
232+
}
233+
234+
// Authorize performs the kerberos auth handshake for authorization
235+
func (krbAuth *GSSAPIKerberosAuth) Authorize(broker *Broker) error {
236+
client, err := krbAuth.NewKerberosClientFunc(krbAuth.Config)
237+
if err != nil {
238+
Logger.Printf("Kerberos client initialization error: %s", err)
239+
return err
240+
}
241+
defer client.Destroy()
242+
243+
ticket, err := krbAuth.Login(client, krbAuth.spn(broker))
244+
if err != nil {
245+
return err
246+
}
247+
248+
principal := strings.Join(ticket.SName.NameString, "/") + "@" + ticket.Realm
249+
var receivedBytes []byte
250+
237251
for {
238-
packBytes, err := krbAuth.initSecContext(receivedBytes, kerberosClient)
252+
packBytes, err := krbAuth.initSecContext(client, receivedBytes)
239253
if err != nil {
240-
Logger.Printf("Error while performing GSSAPI Kerberos Authentication: %s\n", err)
254+
Logger.Printf("Kerberos init error as %s: %s", principal, err)
241255
return err
242256
}
257+
243258
requestTime := time.Now()
244259
bytesWritten, err := krbAuth.writePackage(broker, packBytes)
245260
if err != nil {
246-
Logger.Printf("Error while performing GSSAPI Kerberos Authentication: %s\n", err)
261+
Logger.Printf("Kerberos write error as %s: %s", principal, err)
247262
return err
248263
}
249264
broker.updateOutgoingCommunicationMetrics(bytesWritten)
250-
if krbAuth.step == GSS_API_VERIFY {
251-
bytesRead := 0
265+
266+
switch krbAuth.step {
267+
case GSS_API_VERIFY:
268+
var bytesRead int
252269
receivedBytes, bytesRead, err = krbAuth.readPackage(broker)
253270
requestLatency := time.Since(requestTime)
254271
broker.updateIncomingCommunicationMetrics(bytesRead, requestLatency)
255272
if err != nil {
256-
Logger.Printf("Error while performing GSSAPI Kerberos Authentication: %s\n", err)
273+
Logger.Printf("Kerberos read error as %s: %s", principal, err)
257274
return err
258275
}
259-
} else if krbAuth.step == GSS_API_FINISH {
276+
case GSS_API_FINISH:
277+
return nil
278+
}
279+
}
280+
}
281+
282+
// AuthorizeV2 performs the SASL v2 GSSAPI authentication with the Kafka broker.
283+
func (krbAuth *GSSAPIKerberosAuth) AuthorizeV2(
284+
broker *Broker,
285+
authSendReceiver func(authBytes []byte) (*SaslAuthenticateResponse, error),
286+
) error {
287+
client, err := krbAuth.NewKerberosClientFunc(krbAuth.Config)
288+
if err != nil {
289+
Logger.Printf("Kerberos client initialization error: %s", err)
290+
return err
291+
}
292+
defer client.Destroy()
293+
294+
ticket, err := krbAuth.Login(client, krbAuth.spn(broker))
295+
if err != nil {
296+
return err
297+
}
298+
299+
principal := strings.Join(ticket.SName.NameString, "/") + "@" + ticket.Realm
300+
var receivedBytes []byte
301+
302+
for {
303+
token, err := krbAuth.initSecContext(client, receivedBytes)
304+
if err != nil {
305+
Logger.Printf("SASL Kerberos init error as %s: %s", principal, err)
306+
return err
307+
}
308+
309+
authResponse, err := authSendReceiver(token)
310+
if err != nil {
311+
Logger.Printf("SASL Kerberos authenticate error as %s: %s", principal, err)
312+
return err
313+
}
314+
315+
receivedBytes = authResponse.SaslAuthBytes
316+
317+
if krbAuth.step == GSS_API_FINISH {
260318
return nil
261319
}
262320
}

kerberos_client_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const (
6060
)
6161

6262
func TestFaildToCreateKerberosConfig(t *testing.T) {
63-
expectedErr := errors.New("configuration file could not be opened: krb5.conf open krb5.conf: no such file or directory")
63+
expectedErr := errors.New("configuration file could not be opened: testdata/krb5.conf open testdata/krb5.conf: no such file or directory")
6464
clientConfig := NewTestConfig()
6565
clientConfig.Net.SASL.Mechanism = SASLTypeGSSAPI
6666
clientConfig.Net.SASL.Enable = true
@@ -69,7 +69,7 @@ func TestFaildToCreateKerberosConfig(t *testing.T) {
6969
clientConfig.Net.SASL.GSSAPI.Username = "client"
7070
clientConfig.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH
7171
clientConfig.Net.SASL.GSSAPI.Password = "qwerty"
72-
clientConfig.Net.SASL.GSSAPI.KerberosConfigPath = "krb5.conf"
72+
clientConfig.Net.SASL.GSSAPI.KerberosConfigPath = "testdata/krb5.conf"
7373
_, err := NewKerberosClient(&clientConfig.Net.SASL.GSSAPI)
7474
// Expect to create client with password
7575
if err.Error() != expectedErr.Error() {

0 commit comments

Comments
 (0)