mirror of
https://github.com/fullstorydev/grpcurl.git
synced 2026-07-05 01:01:10 +03:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0ce1852cd | |||
| afea969b8a | |||
| 4ea1554ec7 | |||
| 0521a49a6a |
@@ -1,8 +1,6 @@
|
|||||||
module github.com/fullstorydev/grpcurl
|
module github.com/fullstorydev/grpcurl
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.0
|
||||||
|
|
||||||
toolchain go1.24.1
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.5.4
|
github.com/golang/protobuf v1.5.4
|
||||||
@@ -24,11 +22,11 @@ require (
|
|||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||||
github.com/stretchr/testify v1.11.1 // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.55.0 // indirect
|
||||||
golang.org/x/oauth2 v0.34.0 // indirect
|
golang.org/x/oauth2 v0.34.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.40.0 // indirect
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
golang.org/x/text v0.33.0 // indirect
|
golang.org/x/text v0.37.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -56,16 +56,16 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM=
|
||||||
|
|||||||
+37
-6
@@ -568,9 +568,6 @@ func ClientTLSConfig(insecureSkipVerify bool, cacertFile, clientCertFile, client
|
|||||||
// client certs. The serverCertFile and serverKeyFile must both not be blank.
|
// client certs. The serverCertFile and serverKeyFile must both not be blank.
|
||||||
func ServerTransportCredentials(cacertFile, serverCertFile, serverKeyFile string, requireClientCerts bool) (credentials.TransportCredentials, error) {
|
func ServerTransportCredentials(cacertFile, serverCertFile, serverKeyFile string, requireClientCerts bool) (credentials.TransportCredentials, error) {
|
||||||
var tlsConf tls.Config
|
var tlsConf tls.Config
|
||||||
// TODO(jh): Remove this line once https://github.com/golang/go/issues/28779 is fixed
|
|
||||||
// in Go tip. Until then, the recently merged TLS 1.3 support breaks the TLS tests.
|
|
||||||
tlsConf.MaxVersion = tls.VersionTLS12
|
|
||||||
|
|
||||||
// Load the server certificates from disk
|
// Load the server certificates from disk
|
||||||
certificate, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
|
certificate, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
|
||||||
@@ -617,8 +614,8 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if strings.HasPrefix(address, "xds:///") {
|
if strings.HasPrefix(address, "xds://") {
|
||||||
// The xds:/// prefix is used to signal to the gRPC client to use an xDS server to resolve the
|
// The xds:// prefix is used to signal to the gRPC client to use an xDS server to resolve the
|
||||||
// target. The relevant credentials will be automatically pulled from the GRPC_XDS_BOOTSTRAP or
|
// target. The relevant credentials will be automatically pulled from the GRPC_XDS_BOOTSTRAP or
|
||||||
// GRPC_XDS_BOOTSTRAP_CONFIG env vars.
|
// GRPC_XDS_BOOTSTRAP_CONFIG env vars.
|
||||||
creds, err = xdsCredentials.NewClientCredentials(xdsCredentials.ClientOptions{FallbackCreds: creds})
|
creds, err = xdsCredentials.NewClientCredentials(xdsCredentials.ClientOptions{FallbackCreds: creds})
|
||||||
@@ -734,6 +731,40 @@ func (c *errSignalingCreds) ClientHandshake(ctx context.Context, addr string, ra
|
|||||||
conn, auth, err := c.TransportCredentials.ClientHandshake(ctx, addr, rawConn)
|
conn, auth, err := c.TransportCredentials.ClientHandshake(ctx, addr, rawConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.writeResult(err)
|
c.writeResult(err)
|
||||||
|
return conn, auth, err
|
||||||
}
|
}
|
||||||
return conn, auth, err
|
// Wrap the connection to capture post-handshake errors, e.g.:
|
||||||
|
// - TLS 1.3 client cert rejection (server sends alert after handshake)
|
||||||
|
// - Plaintext client to TLS server (server closes conn immediately)
|
||||||
|
return &errSignalingConn{Conn: conn, writeResult: c.writeResult}, auth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// errSignalingConn wraps a net.Conn to capture the first read error and
|
||||||
|
// report it via writeResult.
|
||||||
|
type errSignalingConn struct {
|
||||||
|
net.Conn
|
||||||
|
writeResult func(res interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *errSignalingConn) Read(b []byte) (int, error) {
|
||||||
|
n, err := c.Conn.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
c.writeResult(err)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsesXDS forwards the optional UsesXDS marker of the wrapped credentials. The
|
||||||
|
// xDS credentials returned for "xds://" targets implement this method, and
|
||||||
|
// grpc-go's cds balancer relies on a type assertion for it to decide whether to
|
||||||
|
// apply the security configuration (e.g. UpstreamTlsContext) delivered by the
|
||||||
|
// management server. Because errSignalingCreds embeds the TransportCredentials
|
||||||
|
// interface, that extra method is not promoted automatically, so we forward it
|
||||||
|
// explicitly. Without this, xDS-supplied mTLS is silently ignored and the
|
||||||
|
// connection falls back to the plain credentials.
|
||||||
|
func (c *errSignalingCreds) UsesXDS() bool {
|
||||||
|
if x, ok := c.TransportCredentials.(interface{ UsesXDS() bool }); ok {
|
||||||
|
return x.UsesXDS()
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-59
@@ -2,6 +2,7 @@ package grpcurl_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/peer"
|
||||||
|
|
||||||
. "github.com/fullstorydev/grpcurl"
|
. "github.com/fullstorydev/grpcurl"
|
||||||
grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing"
|
grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing"
|
||||||
@@ -101,64 +103,85 @@ func TestRequireClientCertTLS(t *testing.T) {
|
|||||||
simpleTest(t, e.cc)
|
simpleTest(t, e.cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTLS12(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
tlsConf, err := ClientTLSConfig(false, "internal/testing/tls/ca.crt", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client TLS config: %v", err)
|
||||||
|
}
|
||||||
|
tlsConf.MaxVersion = tls.VersionTLS12
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, credentials.NewTLS(tlsConf))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
tlsVersion := negotiatedTLSVersion(t, e.cc)
|
||||||
|
if tlsVersion != tls.VersionTLS12 {
|
||||||
|
t.Errorf("expected TLS 1.2, got 0x%04x", tlsVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTLS13(t *testing.T) {
|
||||||
|
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
|
}
|
||||||
|
clientCreds, err := ClientTransportCredentials(false, "internal/testing/tls/ca.crt", "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create client creds: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := createTestServerAndClient(serverCreds, clientCreds)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to setup server and client: %v", err)
|
||||||
|
}
|
||||||
|
defer e.Close()
|
||||||
|
|
||||||
|
tlsVersion := negotiatedTLSVersion(t, e.cc)
|
||||||
|
if tlsVersion != tls.VersionTLS13 {
|
||||||
|
t.Errorf("expected TLS 1.3, got 0x%04x", tlsVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func negotiatedTLSVersion(t *testing.T, cc *grpc.ClientConn) uint16 {
|
||||||
|
t.Helper()
|
||||||
|
cl := grpcurl_testing.NewTestServiceClient(cc)
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
var p peer.Peer
|
||||||
|
_, err := cl.UnaryCall(ctx, &grpcurl_testing.SimpleRequest{}, grpc.WaitForReady(true), grpc.Peer(&p))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("RPC failed: %v", err)
|
||||||
|
}
|
||||||
|
tlsInfo, ok := p.AuthInfo.(credentials.TLSInfo)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected TLS auth info, got %T", p.AuthInfo)
|
||||||
|
}
|
||||||
|
return tlsInfo.State.Version
|
||||||
|
}
|
||||||
|
|
||||||
func TestBrokenTLS_ClientPlainText(t *testing.T) {
|
func TestBrokenTLS_ClientPlainText(t *testing.T) {
|
||||||
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
serverCreds, err := ServerTransportCredentials("", "internal/testing/tls/server.crt", "internal/testing/tls/server.key", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create server creds: %v", err)
|
t.Fatalf("failed to create server creds: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// client connection (usually) succeeds since client is not waiting for TLS handshake
|
// Plaintext client to TLS server: the server expects a TLS handshake,
|
||||||
// (we try several times, but if we never get a connection and the error message is
|
// gets an HTTP/2 preface instead, and closes the connection.
|
||||||
// a known/expected possibility, we'll just bail)
|
e, err := createTestServerAndClient(serverCreds, nil)
|
||||||
var e testEnv
|
|
||||||
failCount := 0
|
|
||||||
for {
|
|
||||||
e, err = createTestServerAndClient(serverCreds, nil)
|
|
||||||
if err == nil {
|
|
||||||
// success!
|
|
||||||
defer e.Close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(err.Error(), "deadline exceeded") ||
|
|
||||||
strings.Contains(err.Error(), "use of closed network connection") {
|
|
||||||
// It is possible that the connection never becomes healthy:
|
|
||||||
// 1) grpc connects successfully
|
|
||||||
// 2) grpc client tries to send HTTP/2 preface and settings frame
|
|
||||||
// 3) server, expecting handshake, closes the connection
|
|
||||||
// 4) in the client, the write fails, so the connection never
|
|
||||||
// becomes ready
|
|
||||||
// The client will attempt to reconnect on transient errors, so
|
|
||||||
// may eventually bump into the connect time limit. This used to
|
|
||||||
// result in a "deadline exceeded" error, but more recent versions
|
|
||||||
// of the grpc library report any underlying I/O error instead, so
|
|
||||||
// we also check for "use of closed network connection".
|
|
||||||
failCount++
|
|
||||||
if failCount > 5 {
|
|
||||||
return // bail...
|
|
||||||
}
|
|
||||||
// we'll try again
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// some other error occurred, so we'll consider that a test failure
|
|
||||||
t.Fatalf("failed to setup server and client: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// but request fails because server closes connection upon seeing request
|
|
||||||
// bytes that are not a TLS handshake
|
|
||||||
cl := grpcurl_testing.NewTestServiceClient(e.cc)
|
|
||||||
_, err = cl.UnaryCall(context.Background(), &grpcurl_testing.SimpleRequest{})
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expecting failure")
|
e.Close()
|
||||||
|
t.Fatal("expecting failure when connecting plaintext to TLS server")
|
||||||
}
|
}
|
||||||
// various errors possible when server closes connection
|
if !strings.Contains(err.Error(), "EOF") &&
|
||||||
if !strings.Contains(err.Error(), "transport is closing") &&
|
|
||||||
!strings.Contains(err.Error(), "connection is unavailable") &&
|
|
||||||
!strings.Contains(err.Error(), "use of closed network connection") &&
|
!strings.Contains(err.Error(), "use of closed network connection") &&
|
||||||
!strings.Contains(err.Error(), "all SubConns are in TransientFailure") {
|
!strings.Contains(err.Error(), "connection reset by peer") {
|
||||||
|
t.Fatalf("expecting connection closed error, got: %v", err)
|
||||||
t.Fatalf("expecting transport failure, got: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,12 +276,14 @@ func TestBrokenTLS_ClientNotTrusted(t *testing.T) {
|
|||||||
e.Close()
|
e.Close()
|
||||||
t.Fatal("expecting TLS failure setting up server and client")
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
}
|
}
|
||||||
// Check for either the old error (Go <=1.24) or the new one (Go 1.25+)
|
// The exact TLS alert varies by Go version and TLS version negotiated:
|
||||||
// Go 1.24: "bad certificate"
|
// - TLS 1.2: "bad certificate" (Go <=1.24) or "handshake failure" (Go 1.25+)
|
||||||
// Go 1.25: "handshake failure"
|
// - TLS 1.3: "certificate required" (server rejects after handshake)
|
||||||
errMsg := err.Error()
|
errMsg := err.Error()
|
||||||
if !strings.Contains(errMsg, "bad certificate") && !strings.Contains(errMsg, "handshake failure") {
|
if !strings.Contains(errMsg, "bad certificate") &&
|
||||||
t.Fatalf("expecting a specific TLS certificate or handshake error, got: %v", err)
|
!strings.Contains(errMsg, "handshake failure") &&
|
||||||
|
!strings.Contains(errMsg, "certificate required") {
|
||||||
|
t.Fatalf("expecting a TLS certificate error, got: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,12 +322,14 @@ func TestBrokenTLS_RequireClientCertButNonePresented(t *testing.T) {
|
|||||||
e.Close()
|
e.Close()
|
||||||
t.Fatal("expecting TLS failure setting up server and client")
|
t.Fatal("expecting TLS failure setting up server and client")
|
||||||
}
|
}
|
||||||
// Check for either the old error (Go <=1.24) or the new one (Go 1.25+)
|
// The exact TLS alert varies by Go version and TLS version negotiated:
|
||||||
// Go 1.24: "bad certificate"
|
// - TLS 1.2: "bad certificate" (Go <=1.24) or "handshake failure" (Go 1.25+)
|
||||||
// Go 1.25: "handshake failure"
|
// - TLS 1.3: "certificate required" (server rejects after handshake)
|
||||||
errMsg := err.Error()
|
errMsg := err.Error()
|
||||||
if !strings.Contains(errMsg, "bad certificate") && !strings.Contains(errMsg, "handshake failure") {
|
if !strings.Contains(errMsg, "bad certificate") &&
|
||||||
t.Fatalf("expecting a specific TLS certificate or handshake error, got: %v", err)
|
!strings.Contains(errMsg, "handshake failure") &&
|
||||||
|
!strings.Contains(errMsg, "certificate required") {
|
||||||
|
t.Fatalf("expecting a TLS certificate error, got: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user