From 282954a65a06d919afd761fb6ddfe2e079bcfdea Mon Sep 17 00:00:00 2001 From: Ruben Vargas Date: Tue, 7 May 2019 16:39:48 -0500 Subject: [PATCH] Testing kerberos auth Signed-off-by: Ruben Vargas --- broker_test.go | 57 +++++++++++++++- config_test.go | 80 ++++++++++++++++++++++ gssapi_kerberos.go | 39 ++++------- mockbroker.go | 167 ++++++++++++++++++++++++++++++++------------- mockkerberos.go | 104 ++++++++++++++++++++++++++++ 5 files changed, 375 insertions(+), 72 deletions(-) diff --git a/broker_test.go b/broker_test.go index 2e7f93fd4d..159aa5eeb9 100644 --- a/broker_test.go +++ b/broker_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - metrics "github.com/rcrowley/go-metrics" + "github.com/rcrowley/go-metrics" ) func ExampleBroker() { @@ -477,6 +477,61 @@ func TestSASLPlainAuth(t *testing.T) { } } +func TestGSSAPIKerberosAuth_Authorize(t *testing.T) { + + mockBroker := NewMockBroker(t, 0) + // broker executes SASL requests against mockBroker + + mockBroker.SetGSSAPIHandler(func(bytes []byte) []byte { + return nil + }) + broker := NewBroker(mockBroker.Addr()) + broker.requestRate = metrics.NilMeter{} + broker.outgoingByteRate = metrics.NilMeter{} + broker.incomingByteRate = metrics.NilMeter{} + broker.requestSize = metrics.NilHistogram{} + broker.responseSize = metrics.NilHistogram{} + broker.responseRate = metrics.NilMeter{} + broker.requestLatency = metrics.NilHistogram{} + conf := NewConfig() + conf.Net.SASL.Mechanism = SASLTypeGSSAPI + conf.Net.SASL.GSSAPI.ServiceName = "kafka" + conf.Net.SASL.GSSAPI.KerberosConfigPath = "password" + conf.Net.SASL.GSSAPI.Realm = "EXAMPLE" + conf.Net.SASL.GSSAPI.Username = "kafka" + conf.Net.SASL.GSSAPI.Password = "kafka" + conf.Net.SASL.GSSAPI.KeyTabPath = "kafka.keytab" + + broker.conf = conf + broker.conf.Version = V1_0_0_0 + dialer := net.Dialer{ + Timeout: conf.Net.DialTimeout, + KeepAlive: conf.Net.KeepAlive, + LocalAddr: conf.Net.LocalAddr, + } + + conn, err := dialer.Dial("tcp", mockBroker.listener.Addr().String()) + + if err != nil { + t.Fatal(err) + } + kerberosClient := NewKerberosMockClient() + gssapiHandler := KafkaGSSAPIHandler{ + client: kerberosClient, + } + mockBroker.SetGSSAPIHandler(gssapiHandler.MockKafkaGSSAPI) + broker.conn = conn + kerberosAuthenticator := NewGSSAPIKerberosAuthenticator(&broker.conf.Net.SASL.GSSAPI) + kerberosAuthenticator.client = kerberosClient + err = kerberosAuthenticator.Authorize(broker) + if err != nil { + t.Errorf("Expected success authentication, got %s", err) + + } + mockBroker.Close() + +} + func TestBuildClientInitialResponse(t *testing.T) { testTable := []struct { diff --git a/config_test.go b/config_test.go index 7e1568e1a7..3f7a7a7a50 100644 --- a/config_test.go +++ b/config_test.go @@ -117,6 +117,86 @@ func TestNetConfigValidates(t *testing.T) { cfg.Net.SASL.Password = "stong_password" }, "A SCRAMClientGeneratorFunc function must be provided to Net.SASL.SCRAMClientGeneratorFunc"}, + {"SASL.Mechanism GSSAPI (Kerberos) - Using User/Password, Missing password field", + func(cfg *Config) { + cfg.Net.SASL.Enable = true + cfg.Net.SASL.Mechanism = SASLTypeGSSAPI + cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH + cfg.Net.SASL.GSSAPI.Username = "sarama" + cfg.Net.SASL.GSSAPI.ServiceName = "kafka" + cfg.Net.SASL.GSSAPI.Realm = "kafka" + cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf" + }, + "Net.SASL.GSSAPI.Password must not be empty when GSS-API " + + "mechanism is used and Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH"}, + {"SASL.Mechanism GSSAPI (Kerberos) - Using User/Password, Missing KeyTabPath field", + func(cfg *Config) { + cfg.Net.SASL.Enable = true + cfg.Net.SASL.Mechanism = SASLTypeGSSAPI + cfg.Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH + cfg.Net.SASL.GSSAPI.Username = "sarama" + cfg.Net.SASL.GSSAPI.ServiceName = "kafka" + cfg.Net.SASL.GSSAPI.Realm = "kafka" + cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf" + }, + "Net.SASL.GSSAPI.KeyTabPath must not be empty when GSS-API mechanism is used" + + " and Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH"}, + {"SASL.Mechanism GSSAPI (Kerberos) - Missing username", + func(cfg *Config) { + cfg.Net.SASL.Enable = true + cfg.Net.SASL.Mechanism = SASLTypeGSSAPI + cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH + cfg.Net.SASL.GSSAPI.Password = "sarama" + cfg.Net.SASL.GSSAPI.ServiceName = "kafka" + cfg.Net.SASL.GSSAPI.Realm = "kafka" + cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf" + }, + "Net.SASL.GSSAPI.Username must not be empty when GSS-API mechanism is used"}, + {"SASL.Mechanism GSSAPI (Kerberos) - Missing ServiceName", + func(cfg *Config) { + cfg.Net.SASL.Enable = true + cfg.Net.SASL.Mechanism = SASLTypeGSSAPI + cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH + cfg.Net.SASL.GSSAPI.Username = "sarama" + cfg.Net.SASL.GSSAPI.Password = "sarama" + cfg.Net.SASL.GSSAPI.Realm = "kafka" + cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf" + }, + "Net.SASL.GSSAPI.ServiceName must not be empty when GSS-API mechanism is used"}, + {"SASL.Mechanism GSSAPI (Kerberos) - Missing AuthType", + func(cfg *Config) { + cfg.Net.SASL.Enable = true + cfg.Net.SASL.GSSAPI.ServiceName = "kafka" + cfg.Net.SASL.Mechanism = SASLTypeGSSAPI + cfg.Net.SASL.GSSAPI.Username = "sarama" + cfg.Net.SASL.GSSAPI.Password = "sarama" + cfg.Net.SASL.GSSAPI.Realm = "kafka" + cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf" + }, + "Net.SASL.GSSAPI.AuthType is invalid. Possible values are KRB5_USER_AUTH and KRB5_KEYTAB_AUTH"}, + {"SASL.Mechanism GSSAPI (Kerberos) - Missing KerberosConfigPath", + func(cfg *Config) { + cfg.Net.SASL.Enable = true + cfg.Net.SASL.GSSAPI.ServiceName = "kafka" + cfg.Net.SASL.Mechanism = SASLTypeGSSAPI + cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH + cfg.Net.SASL.GSSAPI.Username = "sarama" + cfg.Net.SASL.GSSAPI.Password = "sarama" + cfg.Net.SASL.GSSAPI.Realm = "kafka" + }, + "Net.SASL.GSSAPI.KerberosConfigPath must not be empty when GSS-API mechanism is used"}, + {"SASL.Mechanism GSSAPI (Kerberos) - Missing Realm", + func(cfg *Config) { + cfg.Net.SASL.Enable = true + cfg.Net.SASL.GSSAPI.ServiceName = "kafka" + cfg.Net.SASL.Mechanism = SASLTypeGSSAPI + cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH + cfg.Net.SASL.GSSAPI.Username = "sarama" + cfg.Net.SASL.GSSAPI.Password = "sarama" + cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf" + + }, + "Net.SASL.GSSAPI.Realm must not be empty when GSS-API mechanism is used"}, } for i, test := range tests { diff --git a/gssapi_kerberos.go b/gssapi_kerberos.go index 27a9fe3d47..47143bf6d2 100644 --- a/gssapi_kerberos.go +++ b/gssapi_kerberos.go @@ -19,23 +19,20 @@ import ( "time" ) - - - const ( TOK_ID_KRB_AP_REQ = "0100" GSS_API_GENERIC_TAG = 0x60 - KRB5_USER_AUTH = 1 - KRB5_KEYTAB_AUTH = 2 + KRB5_USER_AUTH = 1 + KRB5_KEYTAB_AUTH = 2 ) type GSSAPIConfig struct { - AuthType int + AuthType int KeyTabPath string KerberosConfigPath string ServiceName string Username string - Password string + Password string Realm string } @@ -54,22 +51,22 @@ type KerberosClient interface { } type KerberosGoKrb5Client struct { - client *krb5client.Client + client *krb5client.Client } -func (c *KerberosGoKrb5Client)Login() error { +func (c *KerberosGoKrb5Client) Login() error { return c.client.Login() } -func (c *KerberosGoKrb5Client)GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error) { +func (c *KerberosGoKrb5Client) GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error) { return c.client.GetServiceTicket(spn) } -func (c *KerberosGoKrb5Client)Domain() string { +func (c *KerberosGoKrb5Client) Domain() string { return c.client.Credentials.Domain() } -func (c *KerberosGoKrb5Client)CName() types.PrincipalName { +func (c *KerberosGoKrb5Client) CName() types.PrincipalName { return c.client.Credentials.CName() } @@ -83,9 +80,9 @@ func (c *KerberosGoKrb5Client)CName() types.PrincipalName { */ func createKerberosClient(config *GSSAPIConfig) (*KerberosGoKrb5Client, error) { cfg, err := krb5config.Load(config.KerberosConfigPath) - var client *krb5client.Client + var client *krb5client.Client if err != nil { - return nil,err + return nil, err } if config.AuthType == KRB5_KEYTAB_AUTH { kt, err := keytab.Load(config.KeyTabPath) @@ -142,14 +139,11 @@ func (krbAuth *GSSAPIKerberosAuth) readPackage(broker *Broker) ([]byte, int, err return payloadBytes, bytesRead, nil } -func (krbAuth *GSSAPIKerberosAuth) newAuthenticatorChecksum(flags []int) []byte { +func (krbAuth *GSSAPIKerberosAuth) newAuthenticatorChecksum() []byte { a := make([]byte, 24) + flags := []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf} binary.LittleEndian.PutUint32(a[:4], 16) for _, i := range flags { - if i == gssapi.ContextFlagDeleg { - x := make([]byte, 28-len(a)) - a = append(a, x...) - } f := binary.LittleEndian.Uint32(a[20:24]) f |= uint32(i) binary.LittleEndian.PutUint32(a[20:24], f) @@ -164,14 +158,13 @@ func (krbAuth *GSSAPIKerberosAuth) newAuthenticatorChecksum(flags []int) []byte * */ func (krbAuth *GSSAPIKerberosAuth) createKrb5Token(client KerberosClient, ticket messages.Ticket, sessionKey types.EncryptionKey) ([]byte, error) { - var GSSAPIFlags = []int{gssapi.ContextFlagInteg, gssapi.ContextFlagConf} auth, err := types.NewAuthenticator(client.Domain(), client.CName()) if err != nil { return nil, err } auth.Cksum = types.Checksum{ CksumType: chksumtype.GSSAPI, - Checksum: krbAuth.newAuthenticatorChecksum(GSSAPIFlags), + Checksum: krbAuth.newAuthenticatorChecksum(), } APReq, err := messages.NewAPReq( ticket, @@ -215,12 +208,10 @@ func (krbAuth *GSSAPIKerberosAuth) appendGSSAPIHeader(payload []byte) ([]byte, e return GSSPackage, nil } - - /* This does the handshake for authorization */ func (krbAuth *GSSAPIKerberosAuth) Authorize(broker *Broker) error { if krbAuth.client == nil { - kerberosClient, err :=createKerberosClient(krbAuth.config) + kerberosClient, err := createKerberosClient(krbAuth.config) if err != nil { Logger.Printf("Kerberos client error: %s", err) return err diff --git a/mockbroker.go b/mockbroker.go index 55ef1e2920..4ed46a61aa 100644 --- a/mockbroker.go +++ b/mockbroker.go @@ -18,6 +18,8 @@ const ( expectationTimeout = 500 * time.Millisecond ) +type GSSApiHandlerFunc func([]byte) []byte + type requestHandlerFunc func(req *request) (res encoder) // RequestNotifierFunc is invoked when a mock broker processes a request successfully @@ -49,18 +51,19 @@ type RequestNotifierFunc func(bytesRead, bytesWritten int) // It is not necessary to prefix message length or correlation ID to your // response bytes, the server does that automatically as a convenience. type MockBroker struct { - brokerID int32 - port int32 - closing chan none - stopper chan none - expectations chan encoder - listener net.Listener - t TestReporter - latency time.Duration - handler requestHandlerFunc - notifier RequestNotifierFunc - history []RequestResponse - lock sync.Mutex + brokerID int32 + port int32 + closing chan none + stopper chan none + expectations chan encoder + listener net.Listener + t TestReporter + latency time.Duration + handler requestHandlerFunc + notifier RequestNotifierFunc + history []RequestResponse + lock sync.Mutex + gssApiHandler GSSApiHandlerFunc } // RequestResponse represents a Request/Response pair processed by MockBroker. @@ -173,6 +176,43 @@ func (b *MockBroker) serverLoop() { Logger.Printf("*** mockbroker/%d: listener closed, err=%v", b.BrokerID(), err) } +func (b *MockBroker) SetGSSAPIHandler(handler GSSApiHandlerFunc) { + b.gssApiHandler = handler +} + +func (b *MockBroker) readToBytes(r io.Reader) ([]byte, error) { + var ( + bytesRead int + lengthBytes = make([]byte, 4) + ) + + if _, err := io.ReadFull(r, lengthBytes); err != nil { + return nil, err + } + + bytesRead += len(lengthBytes) + length := int32(binary.BigEndian.Uint32(lengthBytes)) + + if length <= 4 || length > MaxRequestSize { + return nil, PacketDecodingError{fmt.Sprintf("message of length %d too large or too small", length)} + } + + encodedReq := make([]byte, length) + if _, err := io.ReadFull(r, encodedReq); err != nil { + return nil, err + } + + bytesRead += len(encodedReq) + + fullBytes := append(lengthBytes, encodedReq...) + + return fullBytes, nil +} + +func (b *MockBroker) isGSSAPI(buffer []byte) bool { + return buffer[4] == 0x60 || bytes.Equal(buffer[4:6], []byte{0x05, 0x04}) +} + func (b *MockBroker) handleRequests(conn net.Conn, idx int, wg *sync.WaitGroup) { defer wg.Done() defer func() { @@ -192,59 +232,92 @@ func (b *MockBroker) handleRequests(conn net.Conn, idx int, wg *sync.WaitGroup) }() resHeader := make([]byte, 8) + var bytesWritten int + var bytesRead int for { - req, bytesRead, err := decodeRequest(conn) + + buffer, err := b.readToBytes(conn) if err != nil { - Logger.Printf("*** mockbroker/%d/%d: invalid request: err=%+v, %+v", b.brokerID, idx, err, spew.Sdump(req)) + Logger.Printf("*** mockbroker/%d/%d: invalid request: err=%+v, %+v", b.brokerID, idx, err, spew.Sdump(buffer)) b.serverError(err) break } - if b.latency > 0 { - time.Sleep(b.latency) - } + bytesWritten = 0 + if !b.isGSSAPI(buffer) { - b.lock.Lock() - res := b.handler(req) - b.history = append(b.history, RequestResponse{req.body, res}) - b.lock.Unlock() + req, br, err := decodeRequest(bytes.NewReader(buffer)) + bytesRead = br + if err != nil { + Logger.Printf("*** mockbroker/%d/%d: invalid request: err=%+v, %+v", b.brokerID, idx, err, spew.Sdump(req)) + b.serverError(err) + break + } - if res == nil { - Logger.Printf("*** mockbroker/%d/%d: ignored %v", b.brokerID, idx, spew.Sdump(req)) - continue - } - Logger.Printf("*** mockbroker/%d/%d: served %v -> %v", b.brokerID, idx, req, res) + if b.latency > 0 { + time.Sleep(b.latency) + } - encodedRes, err := encode(res, nil) - if err != nil { - b.serverError(err) - break - } - if len(encodedRes) == 0 { b.lock.Lock() - if b.notifier != nil { - b.notifier(bytesRead, 0) - } + res := b.handler(req) + b.history = append(b.history, RequestResponse{req.body, res}) b.lock.Unlock() - continue - } - binary.BigEndian.PutUint32(resHeader, uint32(len(encodedRes)+4)) - binary.BigEndian.PutUint32(resHeader[4:], uint32(req.correlationID)) - if _, err = conn.Write(resHeader); err != nil { - b.serverError(err) - break - } - if _, err = conn.Write(encodedRes); err != nil { - b.serverError(err) - break + if res == nil { + Logger.Printf("*** mockbroker/%d/%d: ignored %v", b.brokerID, idx, spew.Sdump(req)) + continue + } + Logger.Printf("*** mockbroker/%d/%d: served %v -> %v", b.brokerID, idx, req, res) + + encodedRes, err := encode(res, nil) + if err != nil { + b.serverError(err) + break + } + if len(encodedRes) == 0 { + b.lock.Lock() + if b.notifier != nil { + b.notifier(bytesRead, 0) + } + b.lock.Unlock() + continue + } + + binary.BigEndian.PutUint32(resHeader, uint32(len(encodedRes)+4)) + binary.BigEndian.PutUint32(resHeader[4:], uint32(req.correlationID)) + if _, err = conn.Write(resHeader); err != nil { + b.serverError(err) + break + } + if _, err = conn.Write(encodedRes); err != nil { + b.serverError(err) + break + } + bytesWritten = len(resHeader) + len(encodedRes) + + } else { + // GSSAPI is not part of kafka protocol, but is supported for authentication proposes. + // Don't support history for this kind of request as is only used for test GSSAPI authentication mechanism + b.lock.Lock() + res := b.gssApiHandler(buffer) + b.lock.Unlock() + if res == nil { + Logger.Printf("*** mockbroker/%d/%d: ignored %v", b.brokerID, idx, spew.Sdump(buffer)) + continue + } + if _, err = conn.Write(res); err != nil { + b.serverError(err) + break + } + bytesWritten = len(res) } b.lock.Lock() if b.notifier != nil { - b.notifier(bytesRead, len(resHeader)+len(encodedRes)) + b.notifier(bytesRead, bytesWritten) } b.lock.Unlock() + } Logger.Printf("*** mockbroker/%d/%d: connection closed, err=%v", b.BrokerID(), idx, err) } diff --git a/mockkerberos.go b/mockkerberos.go index 6a82e9ece8..f4bd15c183 100644 --- a/mockkerberos.go +++ b/mockkerberos.go @@ -1 +1,105 @@ package sarama + +import ( + "encoding/binary" + "encoding/hex" + "gopkg.in/jcmturner/gokrb5.v7/credentials" + "gopkg.in/jcmturner/gokrb5.v7/gssapi" + "gopkg.in/jcmturner/gokrb5.v7/iana/keyusage" + "gopkg.in/jcmturner/gokrb5.v7/messages" + "gopkg.in/jcmturner/gokrb5.v7/types" +) + +type KafkaGSSAPIHandler struct { + client *MockKerberosClient +} + +func (h *KafkaGSSAPIHandler) MockKafkaGSSAPI(buffer []byte) []byte { + // Default payload used for verify + payload, err := hex.DecodeString("1100") + if err != nil { + return nil + } + var pack = gssapi.WrapToken{ + Flags: KRB5_USER_AUTH, + EC: 12, + RRC: 0, + SndSeqNum: 3398292281, + Payload: payload, + } + // Compute checksum + err = pack.SetCheckSum(h.client.ASRep.DecryptedEncPart.Key, keyusage.GSSAPI_ACCEPTOR_SEAL) + if err != nil { + return nil + } + packBytes, err := pack.Marshal() + if err != nil { + return nil + } + lenBytes := len(packBytes) + response := make([]byte, lenBytes+4) + copy(response[4:], packBytes) + binary.BigEndian.PutUint32(response, uint32(lenBytes)) + return response +} + +type MockKerberosClient struct { + asReqBytes string + asRepBytes string + ASRep messages.ASRep + credentials *credentials.Credentials +} + +func (c *MockKerberosClient) Login() error { + c.asRepBytes = "6b8202e9308202e5a003020105a10302010ba22b30293027a103020113a220041e301c301aa003020112a1131b114" + + "558414d504c452e434f4d636c69656e74a30d1b0b4558414d504c452e434f4da4133011a003020101a10a30081b06636c69656e7" + + "4a5820156618201523082014ea003020105a10d1b0b4558414d504c452e434f4da220301ea003020102a11730151b066b7262746" + + "7741b0b4558414d504c452e434f4da382011430820110a003020112a103020101a28201020481ffdb9891175d106818e61008c51" + + "d0b3462bca92f3bf9d4cfa82de4c4d7aff9994ec87c573e3a3d54dcb2bb79618c76f2bf4a3d006f90d5bdbd049bc18f48be39203" + + "549ca02acaf63f292b12404f9b74c34b83687119d8f56552ccc0c50ebee2a53bb114c1b4619bb1d5d31f0f49b4d40a08a9b4c046" + + "2e1398d0b648be1c0e50c552ad16e1d8d8e74263dd0bf0ec591e4797dfd40a9a1be4ae830d03a306e053fd7586fef84ffc5e4a83" + + "7c3122bf3e6a40fe87e84019f6283634461b955712b44a5f7386c278bff94ec2c2dc0403247e29c2450e853471ceababf9b8911f" + + "997f2e3010b046d2c49eb438afb0f4c210821e80d4ffa4c9521eb895dcd68610b3feaa682012c30820128a003020112a282011f0" + + "482011bce73cbce3f1dd17661c412005f0f2257c756fe8e98ff97e6ec24b7bab66e5fd3a3827aeeae4757af0c6e892948122d8b2" + + "03c8df48df0ef5d142d0e416d688f11daa0fcd63d96bdd431d02b8e951c664eeff286a2be62383d274a04016d5f0e141da58cb86" + + "331de64063062f4f885e8e9ce5b181ca2fdc67897c5995e0ae1ae0c171a64493ff7bd91bc6d89cd4fce1e2b3ea0a10e34b0d5eda" + + "aa38ee727b50c5632ed1d2f2b457908e616178d0d80b72af209fb8ac9dbaa1768fa45931392b36b6d8c12400f8ded2efaa0654d0" + + "da1db966e8b5aab4706c800f95d559664646041fdb38b411c62fc0fbe0d25083a28562b0e1c8df16e62e9d5626b0addee489835f" + + "eedb0f26c05baa596b69b17f47920aa64b29dc77cfcc97ba47885" + apRepBytes, err := hex.DecodeString(c.asRepBytes) + if err != nil { + return err + } + err = c.ASRep.Unmarshal(apRepBytes) + if err != nil { + return err + } + c.credentials = credentials.New("client", "EXAMPLE.COM").WithPassword("qwerty") + _, err = c.ASRep.DecryptEncPart(c.credentials) + if err != nil { + return err + } + return nil +} + +func (c *MockKerberosClient) GetServiceTicket(spn string) (messages.Ticket, types.EncryptionKey, error) { + return c.ASRep.Ticket, c.ASRep.DecryptedEncPart.Key, nil +} + +func (c *MockKerberosClient) Domain() string { + return "EXAMPLE.COM" +} +func (c *MockKerberosClient) CName() types.PrincipalName { + var p = types.PrincipalName{ + NameType: KRB5_USER_AUTH, + NameString: []string{ + "kafka", + "kafka", + }, + } + return p +} + +func NewKerberosMockClient() *MockKerberosClient { + return &MockKerberosClient{} +}