Skip to content

Commit 41f00bc

Browse files
authored
Merge pull request #1366 from rubenvp8510/kerberos_support
Add kerberos support
2 parents 72033d7 + 3ec45e5 commit 41f00bc

11 files changed

+916
-53
lines changed

broker.go

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

1918
// Broker represents a single Kafka broker connection. All operations on this object are entirely concurrency-safe.
@@ -47,6 +46,8 @@ type Broker struct {
4746
brokerOutgoingByteRate metrics.Meter
4847
brokerResponseRate metrics.Meter
4948
brokerResponseSize metrics.Histogram
49+
50+
kerberosAuthenticator GSSAPIKerberosAuth
5051
}
5152

5253
// SASLMechanism specifies the SASL mechanism the client uses to authenticate with the broker
@@ -61,6 +62,7 @@ const (
6162
SASLTypeSCRAMSHA256 = "SCRAM-SHA-256"
6263
// SASLTypeSCRAMSHA512 represents the SCRAM-SHA-512 mechanism.
6364
SASLTypeSCRAMSHA512 = "SCRAM-SHA-512"
65+
SASLTypeGSSAPI = "GSSAPI"
6466
// SASLHandshakeV0 is v0 of the Kafka SASL handshake protocol. Client and
6567
// server negotiate SASL auth using opaque packets.
6668
SASLHandshakeV0 = int16(0)
@@ -844,11 +846,21 @@ func (b *Broker) authenticateViaSASL() error {
844846
return b.sendAndReceiveSASLOAuth(b.conf.Net.SASL.TokenProvider)
845847
case SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512:
846848
return b.sendAndReceiveSASLSCRAMv1()
849+
case SASLTypeGSSAPI:
850+
return b.sendAndReceiveKerberos()
847851
default:
848852
return b.sendAndReceiveSASLPlainAuth()
849853
}
850854
}
851855

856+
func (b *Broker) sendAndReceiveKerberos() error {
857+
b.kerberosAuthenticator.Config = &b.conf.Net.SASL.GSSAPI
858+
if b.kerberosAuthenticator.NewKerberosClientFunc == nil {
859+
b.kerberosAuthenticator.NewKerberosClientFunc = NewKerberosClient
860+
}
861+
return b.kerberosAuthenticator.Authorize(b)
862+
}
863+
852864
func (b *Broker) sendAndReceiveSASLHandshake(saslType SASLMechanism, version int16) error {
853865
rb := &SaslHandshakeRequest{Mechanism: string(saslType), Version: version}
854866

broker_test.go

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package sarama
33
import (
44
"errors"
55
"fmt"
6+
"gopkg.in/jcmturner/gokrb5.v7/krberror"
67
"net"
78
"reflect"
89
"testing"
910
"time"
1011

11-
metrics "github.com/rcrowley/go-metrics"
12+
"github.com/rcrowley/go-metrics"
1213
)
1314

1415
func ExampleBroker() {
@@ -477,6 +478,126 @@ func TestSASLPlainAuth(t *testing.T) {
477478
}
478479
}
479480

481+
func TestGSSAPIKerberosAuth_Authorize(t *testing.T) {
482+
483+
testTable := []struct {
484+
name string
485+
error error
486+
mockKerberosClient bool
487+
errorStage string
488+
badResponse bool
489+
badKeyChecksum bool
490+
}{
491+
{
492+
name: "Kerberos authentication success",
493+
error: nil,
494+
mockKerberosClient: true,
495+
},
496+
{
497+
name: "Kerberos login fails",
498+
error: krberror.NewErrorf(krberror.KDCError, "KDC_Error: AS Exchange Error: "+
499+
"kerberos error response from KDC: KRB Error: (24) KDC_ERR_PREAUTH_FAILED Pre-authenti"+
500+
"cation information was invalid - PREAUTH_FAILED"),
501+
mockKerberosClient: true,
502+
errorStage: "login",
503+
},
504+
{
505+
name: "Kerberos service ticket fails",
506+
error: krberror.NewErrorf(krberror.KDCError, "KDC_Error: AS Exchange Error: "+
507+
"kerberos error response from KDC: KRB Error: (24) KDC_ERR_PREAUTH_FAILED Pre-authenti"+
508+
"cation information was invalid - PREAUTH_FAILED"),
509+
mockKerberosClient: true,
510+
errorStage: "service_ticket",
511+
},
512+
{
513+
name: "Kerberos client creation fails",
514+
error: errors.New("configuration file could not be opened: krb5.conf open krb5.conf: no such file or directory"),
515+
},
516+
{
517+
name: "Bad server response, unmarshall key error",
518+
error: errors.New("bytes shorter than header length"),
519+
badResponse: true,
520+
mockKerberosClient: true,
521+
},
522+
{
523+
name: "Bad token checksum",
524+
error: errors.New("checksum mismatch. Computed: 39feb88ac2459f2b77738493, Contained in token: ffffffffffffffff00000000"),
525+
badResponse: false,
526+
badKeyChecksum: true,
527+
mockKerberosClient: true,
528+
},
529+
}
530+
for i, test := range testTable {
531+
mockBroker := NewMockBroker(t, 0)
532+
// broker executes SASL requests against mockBroker
533+
534+
mockBroker.SetGSSAPIHandler(func(bytes []byte) []byte {
535+
return nil
536+
})
537+
broker := NewBroker(mockBroker.Addr())
538+
broker.requestRate = metrics.NilMeter{}
539+
broker.outgoingByteRate = metrics.NilMeter{}
540+
broker.incomingByteRate = metrics.NilMeter{}
541+
broker.requestSize = metrics.NilHistogram{}
542+
broker.responseSize = metrics.NilHistogram{}
543+
broker.responseRate = metrics.NilMeter{}
544+
broker.requestLatency = metrics.NilHistogram{}
545+
conf := NewConfig()
546+
conf.Net.SASL.Mechanism = SASLTypeGSSAPI
547+
conf.Net.SASL.GSSAPI.ServiceName = "kafka"
548+
conf.Net.SASL.GSSAPI.KerberosConfigPath = "krb5.conf"
549+
conf.Net.SASL.GSSAPI.Realm = "EXAMPLE.COM"
550+
conf.Net.SASL.GSSAPI.Username = "kafka"
551+
conf.Net.SASL.GSSAPI.Password = "kafka"
552+
conf.Net.SASL.GSSAPI.KeyTabPath = "kafka.keytab"
553+
conf.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH
554+
broker.conf = conf
555+
broker.conf.Version = V1_0_0_0
556+
dialer := net.Dialer{
557+
Timeout: conf.Net.DialTimeout,
558+
KeepAlive: conf.Net.KeepAlive,
559+
LocalAddr: conf.Net.LocalAddr,
560+
}
561+
562+
conn, err := dialer.Dial("tcp", mockBroker.listener.Addr().String())
563+
564+
if err != nil {
565+
t.Fatal(err)
566+
}
567+
568+
gssapiHandler := KafkaGSSAPIHandler{
569+
client: &MockKerberosClient{},
570+
badResponse: test.badResponse,
571+
badKeyChecksum: test.badKeyChecksum,
572+
}
573+
mockBroker.SetGSSAPIHandler(gssapiHandler.MockKafkaGSSAPI)
574+
broker.conn = conn
575+
if test.mockKerberosClient {
576+
broker.kerberosAuthenticator.NewKerberosClientFunc = func(config *GSSAPIConfig) (KerberosClient, error) {
577+
return &MockKerberosClient{
578+
mockError: test.error,
579+
errorStage: test.errorStage,
580+
}, nil
581+
}
582+
} else {
583+
broker.kerberosAuthenticator.NewKerberosClientFunc = nil
584+
}
585+
586+
err = broker.authenticateViaSASL()
587+
588+
if err != nil && test.error != nil {
589+
if test.error.Error() != err.Error() {
590+
t.Errorf("[%d] Expected error:%s, got:%s.", i, test.error, err)
591+
}
592+
} else if (err == nil && test.error != nil) || (err != nil && test.error == nil) {
593+
t.Errorf("[%d] Expected error:%s, got:%s.", i, test.error, err)
594+
}
595+
596+
mockBroker.Close()
597+
}
598+
599+
}
600+
480601
func TestBuildClientInitialResponse(t *testing.T) {
481602

482603
testTable := []struct {

config.go

Lines changed: 31 additions & 2 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.
@@ -527,9 +529,36 @@ func (c *Config) Validate() error {
527529
if c.Net.SASL.SCRAMClientGeneratorFunc == nil {
528530
return ConfigurationError("A SCRAMClientGeneratorFunc function must be provided to Net.SASL.SCRAMClientGeneratorFunc")
529531
}
532+
case SASLTypeGSSAPI:
533+
if c.Net.SASL.GSSAPI.ServiceName == "" {
534+
return ConfigurationError("Net.SASL.GSSAPI.ServiceName must not be empty when GSS-API mechanism is used")
535+
}
536+
537+
if c.Net.SASL.GSSAPI.AuthType == KRB5_USER_AUTH {
538+
if c.Net.SASL.GSSAPI.Password == "" {
539+
return ConfigurationError("Net.SASL.GSSAPI.Password must not be empty when GSS-API " +
540+
"mechanism is used and Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH")
541+
}
542+
} else if c.Net.SASL.GSSAPI.AuthType == KRB5_KEYTAB_AUTH {
543+
if c.Net.SASL.GSSAPI.KeyTabPath == "" {
544+
return ConfigurationError("Net.SASL.GSSAPI.KeyTabPath must not be empty when GSS-API mechanism is used" +
545+
" and Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH")
546+
}
547+
} else {
548+
return ConfigurationError("Net.SASL.GSSAPI.AuthType is invalid. Possible values are KRB5_USER_AUTH and KRB5_KEYTAB_AUTH")
549+
}
550+
if c.Net.SASL.GSSAPI.KerberosConfigPath == "" {
551+
return ConfigurationError("Net.SASL.GSSAPI.KerberosConfigPath must not be empty when GSS-API mechanism is used")
552+
}
553+
if c.Net.SASL.GSSAPI.Username == "" {
554+
return ConfigurationError("Net.SASL.GSSAPI.Username must not be empty when GSS-API mechanism is used")
555+
}
556+
if c.Net.SASL.GSSAPI.Realm == "" {
557+
return ConfigurationError("Net.SASL.GSSAPI.Realm must not be empty when GSS-API mechanism is used")
558+
}
530559
default:
531-
msg := fmt.Sprintf("The SASL mechanism configuration is invalid. Possible values are `%s`, `%s`, `%s` and `%s`",
532-
SASLTypeOAuth, SASLTypePlaintext, SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512)
560+
msg := fmt.Sprintf("The SASL mechanism configuration is invalid. Possible values are `%s`, `%s`, `%s`, `%s` and `%s`",
561+
SASLTypeOAuth, SASLTypePlaintext, SASLTypeSCRAMSHA256, SASLTypeSCRAMSHA512, SASLTypeGSSAPI)
533562
return ConfigurationError(msg)
534563
}
535564
}

config_test.go

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func TestNetConfigValidates(t *testing.T) {
9191
cfg.Net.SASL.Mechanism = "AnIncorrectSASLMechanism"
9292
cfg.Net.SASL.TokenProvider = &DummyTokenProvider{}
9393
},
94-
"The SASL mechanism configuration is invalid. Possible values are `OAUTHBEARER`, `PLAIN`, `SCRAM-SHA-256` and `SCRAM-SHA-512`"},
94+
"The SASL mechanism configuration is invalid. Possible values are `OAUTHBEARER`, `PLAIN`, `SCRAM-SHA-256`, `SCRAM-SHA-512` and `GSSAPI`"},
9595
{"SASL.Mechanism.OAUTHBEARER - Missing token provider",
9696
func(cfg *Config) {
9797
cfg.Net.SASL.Enable = true
@@ -117,6 +117,86 @@ func TestNetConfigValidates(t *testing.T) {
117117
cfg.Net.SASL.Password = "stong_password"
118118
},
119119
"A SCRAMClientGeneratorFunc function must be provided to Net.SASL.SCRAMClientGeneratorFunc"},
120+
{"SASL.Mechanism GSSAPI (Kerberos) - Using User/Password, Missing password field",
121+
func(cfg *Config) {
122+
cfg.Net.SASL.Enable = true
123+
cfg.Net.SASL.Mechanism = SASLTypeGSSAPI
124+
cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH
125+
cfg.Net.SASL.GSSAPI.Username = "sarama"
126+
cfg.Net.SASL.GSSAPI.ServiceName = "kafka"
127+
cfg.Net.SASL.GSSAPI.Realm = "kafka"
128+
cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf"
129+
},
130+
"Net.SASL.GSSAPI.Password must not be empty when GSS-API " +
131+
"mechanism is used and Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH"},
132+
{"SASL.Mechanism GSSAPI (Kerberos) - Using User/Password, Missing KeyTabPath field",
133+
func(cfg *Config) {
134+
cfg.Net.SASL.Enable = true
135+
cfg.Net.SASL.Mechanism = SASLTypeGSSAPI
136+
cfg.Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH
137+
cfg.Net.SASL.GSSAPI.Username = "sarama"
138+
cfg.Net.SASL.GSSAPI.ServiceName = "kafka"
139+
cfg.Net.SASL.GSSAPI.Realm = "kafka"
140+
cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf"
141+
},
142+
"Net.SASL.GSSAPI.KeyTabPath must not be empty when GSS-API mechanism is used" +
143+
" and Net.SASL.GSSAPI.AuthType = KRB5_KEYTAB_AUTH"},
144+
{"SASL.Mechanism GSSAPI (Kerberos) - Missing username",
145+
func(cfg *Config) {
146+
cfg.Net.SASL.Enable = true
147+
cfg.Net.SASL.Mechanism = SASLTypeGSSAPI
148+
cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH
149+
cfg.Net.SASL.GSSAPI.Password = "sarama"
150+
cfg.Net.SASL.GSSAPI.ServiceName = "kafka"
151+
cfg.Net.SASL.GSSAPI.Realm = "kafka"
152+
cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf"
153+
},
154+
"Net.SASL.GSSAPI.Username must not be empty when GSS-API mechanism is used"},
155+
{"SASL.Mechanism GSSAPI (Kerberos) - Missing ServiceName",
156+
func(cfg *Config) {
157+
cfg.Net.SASL.Enable = true
158+
cfg.Net.SASL.Mechanism = SASLTypeGSSAPI
159+
cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH
160+
cfg.Net.SASL.GSSAPI.Username = "sarama"
161+
cfg.Net.SASL.GSSAPI.Password = "sarama"
162+
cfg.Net.SASL.GSSAPI.Realm = "kafka"
163+
cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf"
164+
},
165+
"Net.SASL.GSSAPI.ServiceName must not be empty when GSS-API mechanism is used"},
166+
{"SASL.Mechanism GSSAPI (Kerberos) - Missing AuthType",
167+
func(cfg *Config) {
168+
cfg.Net.SASL.Enable = true
169+
cfg.Net.SASL.GSSAPI.ServiceName = "kafka"
170+
cfg.Net.SASL.Mechanism = SASLTypeGSSAPI
171+
cfg.Net.SASL.GSSAPI.Username = "sarama"
172+
cfg.Net.SASL.GSSAPI.Password = "sarama"
173+
cfg.Net.SASL.GSSAPI.Realm = "kafka"
174+
cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf"
175+
},
176+
"Net.SASL.GSSAPI.AuthType is invalid. Possible values are KRB5_USER_AUTH and KRB5_KEYTAB_AUTH"},
177+
{"SASL.Mechanism GSSAPI (Kerberos) - Missing KerberosConfigPath",
178+
func(cfg *Config) {
179+
cfg.Net.SASL.Enable = true
180+
cfg.Net.SASL.GSSAPI.ServiceName = "kafka"
181+
cfg.Net.SASL.Mechanism = SASLTypeGSSAPI
182+
cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH
183+
cfg.Net.SASL.GSSAPI.Username = "sarama"
184+
cfg.Net.SASL.GSSAPI.Password = "sarama"
185+
cfg.Net.SASL.GSSAPI.Realm = "kafka"
186+
},
187+
"Net.SASL.GSSAPI.KerberosConfigPath must not be empty when GSS-API mechanism is used"},
188+
{"SASL.Mechanism GSSAPI (Kerberos) - Missing Realm",
189+
func(cfg *Config) {
190+
cfg.Net.SASL.Enable = true
191+
cfg.Net.SASL.GSSAPI.ServiceName = "kafka"
192+
cfg.Net.SASL.Mechanism = SASLTypeGSSAPI
193+
cfg.Net.SASL.GSSAPI.AuthType = KRB5_USER_AUTH
194+
cfg.Net.SASL.GSSAPI.Username = "sarama"
195+
cfg.Net.SASL.GSSAPI.Password = "sarama"
196+
cfg.Net.SASL.GSSAPI.KerberosConfigPath = "/etc/krb5.conf"
197+
198+
},
199+
"Net.SASL.GSSAPI.Realm must not be empty when GSS-API mechanism is used"},
120200
}
121201

122202
for i, test := range tests {

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@ require (
88
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21
99
github.com/eapache/queue v1.1.0
1010
github.com/golang/snappy v0.0.1 // indirect
11+
github.com/hashicorp/go-uuid v1.0.1 // indirect
12+
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03
1113
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41
1214
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a
1315
github.com/stretchr/testify v1.3.0
1416
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c
1517
github.com/xdg/stringprep v1.0.0 // indirect
1618
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5 // indirect
1719
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
20+
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
21+
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
22+
gopkg.in/jcmturner/gokrb5.v7 v7.2.3
23+
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
1824
)

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
1313
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
1414
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
1515
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
16+
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
17+
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
18+
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM=
19+
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
1620
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41 h1:GeinFsrjWz97fAxVUEd748aV0cYL+I6k44gFJTCVvpU=
1721
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
1822
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
@@ -37,3 +41,11 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
3741
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
3842
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
3943
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
44+
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
45+
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
46+
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
47+
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
48+
gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc44010=
49+
gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
50+
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
51+
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=

0 commit comments

Comments
 (0)