Skip to content

Commit dd3b45e

Browse files
committed
Add kerberos support
Signed-off-by: Ruben Vargas <[email protected]>
1 parent ea9ab1c commit dd3b45e

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
lines changed

broker.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"crypto/tls"
55
"encoding/binary"
66
"fmt"
7+
"github.com/rcrowley/go-metrics"
78
"io"
89
"net"
910
"sort"
@@ -12,7 +13,6 @@ import (
1213
"sync"
1314
"sync/atomic"
1415
"time"
15-
1616
metrics "github.com/rcrowley/go-metrics"
1717
)
1818

@@ -61,6 +61,7 @@ const (
6161
SASLTypeSCRAMSHA256 = "SCRAM-SHA-256"
6262
// SASLTypeSCRAMSHA512 represents the SCRAM-SHA-512 mechanism.
6363
SASLTypeSCRAMSHA512 = "SCRAM-SHA-512"
64+
SASLTypeGSSAPI = "GSSAPI"
6465
// SASLHandshakeV0 is v0 of the Kafka SASL handshake protocol. Client and
6566
// server negotiate SASL auth using opaque packets.
6667
SASLHandshakeV0 = int16(0)
@@ -844,11 +845,17 @@ func (b *Broker) authenticateViaSASL() error {
844845
return b.sendAndReceiveSASLOAuth(b.conf.Net.SASL.TokenProvider)
845846
case SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512:
846847
return b.sendAndReceiveSASLSCRAMv1()
848+
case SASLTypeGSSAPI:
849+
return b.sendAndReceiveKerberos()
847850
default:
848851
return b.sendAndReceiveSASLPlainAuth()
849852
}
850853
}
851854

855+
func (b *Broker) sendAndReceiveKerberos() error{
856+
return NewGSSAPIKerberosAuthenticator(&b.conf.Net.SASL.GSSAPI).Authorize(b.conn)
857+
}
858+
852859
func (b *Broker) sendAndReceiveSASLHandshake(saslType SASLMechanism, version int16) error {
853860
rb := &SaslHandshakeRequest{Mechanism: string(saslType), Version: version}
854861

config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ type Config struct {
7575
// AccessTokenProvider interface docs for proper implementation
7676
// guidelines.
7777
TokenProvider AccessTokenProvider
78+
79+
GSSAPI GSSAPIConfig
7880
}
7981

8082
// KeepAlive specifies the keep-alive period for an active network connection.
@@ -520,6 +522,8 @@ func (c *Config) Validate() error {
520522
if c.Net.SASL.SCRAMClientGeneratorFunc == nil {
521523
return ConfigurationError("A SCRAMClientGeneratorFunc function must be provided to Net.SASL.SCRAMClientGeneratorFunc")
522524
}
525+
case SASLTypeGSSAPI:
526+
break
523527
default:
524528
msg := fmt.Sprintf("The SASL mechanism configuration is invalid. Possible values are `%s`, `%s`, `%s` and `%s`",
525529
SASLTypeOAuth, SASLTypePlaintext, SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512)

gssapi_kerberos.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package sarama
2+
3+
import (
4+
"encoding/binary"
5+
"encoding/hex"
6+
"github.com/gokrb5/iana/keyusage"
7+
"github.com/jcmturner/gofork/encoding/asn1"
8+
krbclient "gopkg.in/jcmturner/gokrb5.v7/client"
9+
krbconfig "gopkg.in/jcmturner/gokrb5.v7/config"
10+
"gopkg.in/jcmturner/gokrb5.v7/gssapi"
11+
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
12+
"gopkg.in/jcmturner/gokrb5.v7/keytab"
13+
"gopkg.in/jcmturner/gokrb5.v7/messages"
14+
"gopkg.in/jcmturner/gokrb5.v7/types"
15+
"io"
16+
"net"
17+
)
18+
19+
const (
20+
TOK_ID_KRB_AP_REQ = "0100"
21+
GSS_API_GENERIC_TAG = 0x60
22+
)
23+
24+
type GSSAPIConfig struct {
25+
KeyTabPath string
26+
KerberosConfigPath string
27+
SPN string
28+
Username string
29+
Realm string
30+
}
31+
32+
type GSSAPIKerberosAuth struct {
33+
config *GSSAPIConfig
34+
client *krbclient.Client
35+
ticket messages.Ticket
36+
encryptionKey types.EncryptionKey
37+
}
38+
39+
func NewGSSAPIKerberosAuthenticator(config *GSSAPIConfig) *GSSAPIKerberosAuth {
40+
return &GSSAPIKerberosAuth{
41+
config:config,
42+
}
43+
}
44+
45+
func (krbAuth *GSSAPIKerberosAuth) writePackage(conn net.Conn, payload []byte) {
46+
length := len(payload)
47+
finalPackage := make([]byte, length+4) //4 byte length header + payload
48+
copy(finalPackage[4:], payload)
49+
binary.BigEndian.PutUint32(finalPackage, uint32(length))
50+
conn.Write(finalPackage)
51+
}
52+
53+
func (krbAuth *GSSAPIKerberosAuth) readPackage(conn net.Conn) []byte {
54+
lengthInBytes := make([]byte, 4)
55+
io.ReadFull(conn, lengthInBytes)
56+
payloadLength := binary.BigEndian.Uint32(lengthInBytes)
57+
payloadBytes := make([]byte, payloadLength) // buffer for read..
58+
io.ReadFull(conn, payloadBytes) // read bytes
59+
return payloadBytes
60+
}
61+
62+
63+
func (krbAuth *GSSAPIKerberosAuth) gssapi_encodeLength(len int32) []byte {
64+
var bytes []byte
65+
if len < 128 {
66+
return append(bytes, byte(len))
67+
} else if len < (1 << 8) {
68+
return append(bytes, 0x081, byte(len))
69+
} else if len < (1 << 16) {
70+
return append(bytes, 0x082, byte(len>>8), byte(len))
71+
} else if len < (1 << 24) {
72+
return append(bytes, 0x083, byte(len>>16), byte(len>>8), byte(len))
73+
}
74+
return append(bytes, 0x084, byte(len>>24), byte(len>>16), byte(len>>8), byte(len))
75+
}
76+
77+
func (krbAuth *GSSAPIKerberosAuth) newAuthenticatorChecksum(flags []int) []byte {
78+
a := make([]byte, 24)
79+
binary.LittleEndian.PutUint32(a[:4], 16)
80+
for _, i := range flags {
81+
if i == gssapi.ContextFlagDeleg {
82+
x := make([]byte, 28-len(a))
83+
a = append(a, x...)
84+
}
85+
f := binary.LittleEndian.Uint32(a[20:24])
86+
f |= uint32(i)
87+
binary.LittleEndian.PutUint32(a[20:24], f)
88+
}
89+
return a
90+
}
91+
92+
func (krbAuth *GSSAPIKerberosAuth) createKrb5Token(client *krbclient.Client, ticket messages.Ticket, sessionKey types.EncryptionKey) []byte {
93+
var GSSAPIFlags = []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf}
94+
auth, _ := types.NewAuthenticator(client.Credentials.Domain(), client.Credentials.CName())
95+
auth.Cksum = types.Checksum{
96+
CksumType: chksumtype.GSSAPI,
97+
Checksum: krbAuth.newAuthenticatorChecksum(GSSAPIFlags),
98+
}
99+
APReq, _ := messages.NewAPReq(
100+
ticket,
101+
sessionKey,
102+
auth,
103+
)
104+
aprBytes, _ := hex.DecodeString(TOK_ID_KRB_AP_REQ)
105+
tb, _ := APReq.Marshal()
106+
aprBytes = append(aprBytes, tb...)
107+
return aprBytes
108+
}
109+
110+
func (krbAuth *GSSAPIKerberosAuth) appendGSSAPIHeader(payload []byte) []byte {
111+
oidBytes, _ := asn1.Marshal(gssapi.OID(gssapi.OIDKRB5))
112+
tkoLengthBytes := krbAuth.gssapi_encodeLength(int32(len(oidBytes) + len(payload)))
113+
GSSHeader := append([]byte{GSS_API_GENERIC_TAG}, tkoLengthBytes...)
114+
GSSHeader = append(GSSHeader, oidBytes...)
115+
GSSPackage := append(GSSHeader, payload...)
116+
return GSSPackage
117+
}
118+
119+
func (krbAuth *GSSAPIKerberosAuth) createKerberosClient() error{
120+
kt, _ := keytab.Load(krbAuth.config.KeyTabPath)
121+
cfg, _ := krbconfig.Load(krbAuth.config.KerberosConfigPath)
122+
krbAuth.client = krbclient.NewClientWithKeytab(krbAuth.config.Username, krbAuth.config.Realm, kt, cfg)
123+
return nil
124+
}
125+
126+
func (krbAuth *GSSAPIKerberosAuth) Authorize(conn net.Conn) error {
127+
128+
krbAuth.createKerberosClient()
129+
ticket, encryptionKey, err := krbAuth.client.GetServiceTicket(krbAuth.config.SPN)
130+
if err != nil {
131+
print(err)
132+
}
133+
krbAuth.ticket = ticket
134+
krbAuth.encryptionKey = encryptionKey
135+
aprBytes := krbAuth.createKrb5Token(krbAuth.client, krbAuth.ticket, krbAuth.encryptionKey)
136+
GSSPackage := krbAuth.appendGSSAPIHeader(aprBytes)
137+
krbAuth.writePackage(conn, GSSPackage)
138+
wrapBytes := krbAuth.readPackage(conn)
139+
wrapTokenReq := gssapi.WrapToken{}
140+
err = wrapTokenReq.Unmarshal(wrapBytes, true)
141+
if err != nil {
142+
Logger.Printf("Error while performing Kerberos Authentication: %s\n", err)
143+
return err
144+
}
145+
146+
// Validate response.
147+
isValid, _ := wrapTokenReq.Verify(krbAuth.encryptionKey, keyusage.GSSAPI_ACCEPTOR_SEAL)
148+
if !isValid {
149+
Logger.Printf("Invalid key")
150+
151+
}
152+
153+
// Is valid response, reply..
154+
wrapTokenResponse, _ := gssapi.NewInitiatorWrapToken(wrapTokenReq.Payload, krbAuth.encryptionKey)
155+
wrapResponseBytes, _ := wrapTokenResponse.Marshal()
156+
krbAuth.writePackage(conn, wrapResponseBytes)
157+
return nil
158+
}

0 commit comments

Comments
 (0)