Skip to content

Commit 6dfca8b

Browse files
committed
feat: experimental SASLv1 support for Kerberos
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. Signed-off-by: Dominic Evans <[email protected]>
1 parent 72c7448 commit 6dfca8b

File tree

4 files changed

+99
-31
lines changed

4 files changed

+99
-31
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

@@ -1370,6 +1370,12 @@ func (b *Broker) authenticateViaSASLv1() error {
13701370
}
13711371

13721372
switch b.conf.Net.SASL.Mechanism {
1373+
case SASLTypeGSSAPI:
1374+
b.kerberosAuthenticator.Config = &b.conf.Net.SASL.GSSAPI
1375+
if b.kerberosAuthenticator.NewKerberosClientFunc == nil {
1376+
b.kerberosAuthenticator.NewKerberosClientFunc = NewKerberosClient
1377+
}
1378+
return b.kerberosAuthenticator.AuthorizeV2(b, authSendReceiver)
13731379
case SASLTypeOAuth:
13741380
provider := b.conf.Net.SASL.TokenProvider
13751381
return b.sendAndReceiveSASLOAuth(authSendReceiver, provider)

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: 87 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ func (krbAuth *GSSAPIKerberosAuth) newAuthenticatorChecksum() []byte {
117117
func (krbAuth *GSSAPIKerberosAuth) createKrb5Token(
118118
domain string, cname types.PrincipalName,
119119
ticket messages.Ticket,
120-
sessionKey types.EncryptionKey) ([]byte, error) {
120+
sessionKey types.EncryptionKey,
121+
) ([]byte, error) {
121122
auth, err := types.NewAuthenticator(domain, cname)
122123
if err != nil {
123124
return nil, err
@@ -200,63 +201,122 @@ func (krbAuth *GSSAPIKerberosAuth) initSecContext(bytes []byte, kerberosClient K
200201
return nil, nil
201202
}
202203

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
204+
func (krbAuth *GSSAPIKerberosAuth) spn(broker *Broker) string {
205+
host := strings.SplitN(broker.addr, ":", 2)[0]
220206
var spn string
221207
if krbAuth.Config.BuildSpn != nil {
222208
spn = krbAuth.Config.BuildSpn(broker.conf.Net.SASL.GSSAPI.ServiceName, host)
223209
} else {
224210
spn = fmt.Sprintf("%s/%s", broker.conf.Net.SASL.GSSAPI.ServiceName, host)
225211
}
212+
return spn
213+
}
214+
215+
// Login will use the given KerberosClient to login and get a ticket for the given spn.
216+
func (krbAuth *GSSAPIKerberosAuth) Login(kerberosClient KerberosClient, spn string) (*messages.Ticket, error) {
217+
if err := kerberosClient.Login(); err != nil {
218+
Logger.Printf("Kerberos client login error: %s", err)
219+
return nil, err
220+
}
226221

227222
ticket, encKey, err := kerberosClient.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
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+
kerberosClient, err := krbAuth.NewKerberosClientFunc(krbAuth.Config)
237+
if err != nil {
238+
Logger.Printf("Kerberos client initialization error: %s", err)
239+
return err
240+
}
236241
defer kerberosClient.Destroy()
242+
243+
ticket, err := krbAuth.Login(kerberosClient, 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 {
238252
packBytes, err := krbAuth.initSecContext(receivedBytes, kerberosClient)
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(broker *Broker, authSendReceiver func(authBytes []byte) (*SaslAuthenticateResponse, error)) error {
284+
kerberosClient, err := krbAuth.NewKerberosClientFunc(krbAuth.Config)
285+
if err != nil {
286+
Logger.Printf("Kerberos client initialization error: %s", err)
287+
return err
288+
}
289+
defer kerberosClient.Destroy()
290+
291+
ticket, err := krbAuth.Login(kerberosClient, krbAuth.spn(broker))
292+
if err != nil {
293+
return err
294+
}
295+
296+
principal := strings.Join(ticket.SName.NameString, "/") + "@" + ticket.Realm
297+
var receivedBytes []byte
298+
299+
for {
300+
token, err := krbAuth.initSecContext(receivedBytes, kerberosClient)
301+
if err != nil {
302+
Logger.Printf("SASL Kerberos init error as %s: %s", principal, err)
303+
return err
304+
}
305+
306+
authResponse, err := authSendReceiver(token)
307+
if err != nil {
308+
Logger.Printf("SASL Kerberos authenticate error as %s: %s", principal, err)
309+
return err
310+
}
311+
312+
if authResponse.Err != ErrNoError {
313+
Logger.Printf("SASL Kerberos authentication failed as %s: %s", principal, authResponse.Err)
314+
return authResponse.Err
315+
}
316+
317+
receivedBytes = authResponse.SaslAuthBytes
318+
319+
if krbAuth.step == GSS_API_FINISH {
260320
return nil
261321
}
262322
}

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)