snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
236
credentials/xds/xds.go
Normal file
236
credentials/xds/xds.go
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2020 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package xds provides a transport credentials implementation where the
|
||||
// security configuration is pushed by a management server using xDS APIs.
|
||||
package xds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/credentials"
|
||||
credinternal "google.golang.org/grpc/internal/credentials"
|
||||
xdsinternal "google.golang.org/grpc/internal/credentials/xds"
|
||||
"google.golang.org/grpc/internal/grpcsync"
|
||||
)
|
||||
|
||||
// ClientOptions contains parameters to configure a new client-side xDS
|
||||
// credentials implementation.
|
||||
type ClientOptions struct {
|
||||
// FallbackCreds specifies the fallback credentials to be used when either
|
||||
// the `xds` scheme is not used in the user's dial target or when the
|
||||
// management server does not return any security configuration. Attempts to
|
||||
// create client credentials without fallback credentials will fail.
|
||||
FallbackCreds credentials.TransportCredentials
|
||||
}
|
||||
|
||||
// NewClientCredentials returns a new client-side transport credentials
|
||||
// implementation which uses xDS APIs to fetch its security configuration.
|
||||
func NewClientCredentials(opts ClientOptions) (credentials.TransportCredentials, error) {
|
||||
if opts.FallbackCreds == nil {
|
||||
return nil, errors.New("missing fallback credentials")
|
||||
}
|
||||
return &credsImpl{
|
||||
isClient: true,
|
||||
fallback: opts.FallbackCreds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ServerOptions contains parameters to configure a new server-side xDS
|
||||
// credentials implementation.
|
||||
type ServerOptions struct {
|
||||
// FallbackCreds specifies the fallback credentials to be used when the
|
||||
// management server does not return any security configuration. Attempts to
|
||||
// create server credentials without fallback credentials will fail.
|
||||
FallbackCreds credentials.TransportCredentials
|
||||
}
|
||||
|
||||
// NewServerCredentials returns a new server-side transport credentials
|
||||
// implementation which uses xDS APIs to fetch its security configuration.
|
||||
func NewServerCredentials(opts ServerOptions) (credentials.TransportCredentials, error) {
|
||||
if opts.FallbackCreds == nil {
|
||||
return nil, errors.New("missing fallback credentials")
|
||||
}
|
||||
return &credsImpl{
|
||||
isClient: false,
|
||||
fallback: opts.FallbackCreds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// credsImpl is an implementation of the credentials.TransportCredentials
|
||||
// interface which uses xDS APIs to fetch its security configuration.
|
||||
type credsImpl struct {
|
||||
isClient bool
|
||||
fallback credentials.TransportCredentials
|
||||
}
|
||||
|
||||
// ClientHandshake performs the TLS handshake on the client-side.
|
||||
//
|
||||
// It looks for the presence of a HandshakeInfo value in the passed in context
|
||||
// (added using a call to NewContextWithHandshakeInfo()), and retrieves identity
|
||||
// and root certificates from there. It also retrieves a list of acceptable SANs
|
||||
// and uses a custom verification function to validate the certificate presented
|
||||
// by the peer. It uses fallback credentials if no HandshakeInfo is present in
|
||||
// the passed in context.
|
||||
func (c *credsImpl) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
if !c.isClient {
|
||||
return nil, nil, errors.New("ClientHandshake() is not supported for server credentials")
|
||||
}
|
||||
|
||||
// The clusterimpl balancer constructs a new HandshakeInfo using a call to
|
||||
// NewHandshakeInfo(), and then adds it to the attributes field of the
|
||||
// resolver.Address when handling calls to NewSubConn(). The transport layer
|
||||
// takes care of shipping these attributes in the context to this handshake
|
||||
// function. We first read the credentials.ClientHandshakeInfo type from the
|
||||
// context, which contains the attributes added by the clusterimpl balancer.
|
||||
// We then read the HandshakeInfo from the attributes to get to the actual
|
||||
// data that we need here for the handshake.
|
||||
chi := credentials.ClientHandshakeInfoFromContext(ctx)
|
||||
// If there are no attributes in the received context or the attributes does
|
||||
// not contain a HandshakeInfo, it could either mean that the user did not
|
||||
// specify an `xds` scheme in their dial target or that the xDS server did
|
||||
// not provide any security configuration. In both of these cases, we use
|
||||
// the fallback credentials specified by the user.
|
||||
if chi.Attributes == nil {
|
||||
return c.fallback.ClientHandshake(ctx, authority, rawConn)
|
||||
}
|
||||
|
||||
hiPtr := xdsinternal.HandshakeInfoFromAttributes(chi.Attributes)
|
||||
hostname := xdsinternal.Hostname(chi.Attributes)
|
||||
cfg, useFallback, done, err := xdsinternal.ClientSideTLSConfig(ctx, hiPtr, hostname)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if useFallback {
|
||||
return c.fallback.ClientHandshake(ctx, authority, rawConn)
|
||||
}
|
||||
defer done()
|
||||
|
||||
// Perform the TLS handshake with the tls.Config that we have. We run the
|
||||
// actual Handshake() function in a goroutine because we need to respect the
|
||||
// deadline specified on the passed in context, and we need a way to cancel
|
||||
// the handshake if the context is cancelled.
|
||||
conn := tls.Client(rawConn, cfg)
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
errCh <- conn.Handshake()
|
||||
close(errCh)
|
||||
}()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
case <-ctx.Done():
|
||||
conn.Close()
|
||||
return nil, nil, ctx.Err()
|
||||
}
|
||||
info := credentials.TLSInfo{
|
||||
State: conn.ConnectionState(),
|
||||
CommonAuthInfo: credentials.CommonAuthInfo{
|
||||
SecurityLevel: credentials.PrivacyAndIntegrity,
|
||||
},
|
||||
SPIFFEID: credinternal.SPIFFEIDFromState(conn.ConnectionState()),
|
||||
}
|
||||
return credinternal.WrapSyscallConn(rawConn, conn), info, nil
|
||||
}
|
||||
|
||||
// ServerHandshake performs the TLS handshake on the server-side.
|
||||
func (c *credsImpl) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||
if c.isClient {
|
||||
return nil, nil, errors.New("ServerHandshake is not supported for client credentials")
|
||||
}
|
||||
|
||||
// An xds-enabled gRPC server wraps the underlying raw net.Conn in a type
|
||||
// that provides a way to retrieve `HandshakeInfo`, which contains the
|
||||
// certificate providers to be used during the handshake. If the net.Conn
|
||||
// passed to this function does not implement this interface, or if the
|
||||
// `HandshakeInfo` does not contain the information we are looking for, we
|
||||
// delegate the handshake to the fallback credentials.
|
||||
hiConn, ok := rawConn.(interface {
|
||||
XDSHandshakeInfo() (*grpcsync.RefCounted[*xdsinternal.HandshakeInfo], error)
|
||||
})
|
||||
if !ok {
|
||||
return c.fallback.ServerHandshake(rawConn)
|
||||
}
|
||||
hi, err := hiConn.XDSHandshakeInfo()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// An xds-enabled gRPC server is expected to wrap the underlying raw
|
||||
// net.Conn in a type which provides a way to retrieve the deadline set on
|
||||
// it. If we cannot retrieve the deadline here, we fail (by setting deadline
|
||||
// to time.Now()), instead of using a default deadline and possibly taking
|
||||
// longer to eventually fail.
|
||||
deadline := time.Now()
|
||||
if dConn, ok := rawConn.(interface{ GetDeadline() time.Time }); ok {
|
||||
deadline = dConn.GetDeadline()
|
||||
}
|
||||
ctx, cancel := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel()
|
||||
|
||||
cfg, useFallback, done, err := xdsinternal.ServerSideTLSConfig(ctx, hi)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if useFallback {
|
||||
return c.fallback.ServerHandshake(rawConn)
|
||||
}
|
||||
defer done()
|
||||
|
||||
conn := tls.Server(rawConn, cfg)
|
||||
if err := conn.Handshake(); err != nil {
|
||||
conn.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
info := credentials.TLSInfo{
|
||||
State: conn.ConnectionState(),
|
||||
CommonAuthInfo: credentials.CommonAuthInfo{
|
||||
SecurityLevel: credentials.PrivacyAndIntegrity,
|
||||
},
|
||||
}
|
||||
info.SPIFFEID = credinternal.SPIFFEIDFromState(conn.ConnectionState())
|
||||
return credinternal.WrapSyscallConn(rawConn, conn), info, nil
|
||||
}
|
||||
|
||||
// Info provides the ProtocolInfo of this TransportCredentials.
|
||||
func (c *credsImpl) Info() credentials.ProtocolInfo {
|
||||
return credentials.ProtocolInfo{SecurityProtocol: "tls"}
|
||||
}
|
||||
|
||||
// Clone makes a copy of this TransportCredentials.
|
||||
func (c *credsImpl) Clone() credentials.TransportCredentials {
|
||||
clone := *c
|
||||
return &clone
|
||||
}
|
||||
|
||||
func (c *credsImpl) OverrideServerName(_ string) error {
|
||||
return errors.New("serverName for peer validation must be configured as a list of acceptable SANs")
|
||||
}
|
||||
|
||||
// UsesXDS returns true if c uses xDS to fetch security configuration
|
||||
// used at handshake time, and false otherwise.
|
||||
func (c *credsImpl) UsesXDS() bool {
|
||||
return true
|
||||
}
|
||||
676
credentials/xds/xds_client_test.go
Normal file
676
credentials/xds/xds_client_test.go
Normal file
@@ -0,0 +1,676 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2020 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package xds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/tls/certprovider"
|
||||
icredentials "google.golang.org/grpc/internal/credentials"
|
||||
xdsinternal "google.golang.org/grpc/internal/credentials/xds"
|
||||
"google.golang.org/grpc/internal/envconfig"
|
||||
"google.golang.org/grpc/internal/grpcsync"
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
"google.golang.org/grpc/internal/testutils"
|
||||
"google.golang.org/grpc/internal/xds/matcher"
|
||||
"google.golang.org/grpc/resolver"
|
||||
"google.golang.org/grpc/testdata"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTestTimeout = 1 * time.Second
|
||||
defaultTestShortTimeout = 10 * time.Millisecond
|
||||
defaultTestCertSAN = "abc.test.example.com"
|
||||
authority = "x.test.example.com"
|
||||
)
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
// Helper function to create a real TLS client credentials which is used as
|
||||
// fallback credentials from multiple tests.
|
||||
func makeFallbackClientCreds(t *testing.T) credentials.TransportCredentials {
|
||||
creds, err := credentials.NewClientTLSFromFile(testdata.Path("x509/server_ca_cert.pem"), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return creds
|
||||
}
|
||||
|
||||
// testServer is a no-op server which listens on a local TCP port for incoming
|
||||
// connections, and performs a manual TLS handshake on the received raw
|
||||
// connection using a user specified handshake function. It then makes the
|
||||
// result of the handshake operation available through a channel for tests to
|
||||
// inspect. Tests should stop the testServer as part of their cleanup.
|
||||
type testServer struct {
|
||||
lis net.Listener
|
||||
address string // Listening address of the test server.
|
||||
handshakeFunc testHandshakeFunc // Test specified handshake function.
|
||||
hsResult *testutils.Channel // Channel to deliver handshake results.
|
||||
}
|
||||
|
||||
// handshakeResult wraps the result of the handshake operation on the test
|
||||
// server. It consists of TLS connection state and an error, if the handshake
|
||||
// failed. This result is delivered on the `hsResult` channel on the testServer.
|
||||
type handshakeResult struct {
|
||||
connState tls.ConnectionState
|
||||
err error
|
||||
}
|
||||
|
||||
// Configurable handshake function for the testServer. Tests can set this to
|
||||
// simulate different conditions like handshake success, failure, timeout etc.
|
||||
type testHandshakeFunc func(net.Conn) handshakeResult
|
||||
|
||||
// newTestServerWithHandshakeFunc starts a new testServer which listens for
|
||||
// connections on a local TCP port, and uses the provided custom handshake
|
||||
// function to perform TLS handshake.
|
||||
func newTestServerWithHandshakeFunc(ctx context.Context, f testHandshakeFunc) *testServer {
|
||||
ts := &testServer{
|
||||
handshakeFunc: f,
|
||||
hsResult: testutils.NewChannel(),
|
||||
}
|
||||
ts.start(ctx)
|
||||
return ts
|
||||
}
|
||||
|
||||
// starts actually starts listening on a local TCP port, and spawns a goroutine
|
||||
// to handle new connections.
|
||||
func (ts *testServer) start(ctx context.Context) error {
|
||||
lis, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts.lis = lis
|
||||
ts.address = lis.Addr().String()
|
||||
go ts.handleConn(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConn accepts a new raw connection, and invokes the test provided
|
||||
// handshake function to perform TLS handshake, and returns the result on the
|
||||
// `hsResult` channel.
|
||||
func (ts *testServer) handleConn(ctx context.Context) {
|
||||
for {
|
||||
rawConn, err := ts.lis.Accept()
|
||||
if err != nil {
|
||||
// Once the listeners closed, Accept() will return with an error.
|
||||
return
|
||||
}
|
||||
hsr := ts.handshakeFunc(rawConn)
|
||||
ts.hsResult.SendContext(ctx, hsr)
|
||||
}
|
||||
}
|
||||
|
||||
// stop closes the associated listener which causes the connection handling
|
||||
// goroutine to exit.
|
||||
func (ts *testServer) stop() {
|
||||
ts.lis.Close()
|
||||
}
|
||||
|
||||
// A handshake function which simulates a successful handshake without client
|
||||
// authentication (server does not request for client certificate during the
|
||||
// handshake here).
|
||||
func testServerTLSHandshake(rawConn net.Conn) handshakeResult {
|
||||
cert, err := tls.LoadX509KeyPair(testdata.Path("x509/server1_cert.pem"), testdata.Path("x509/server1_key.pem"))
|
||||
if err != nil {
|
||||
return handshakeResult{err: err}
|
||||
}
|
||||
cfg := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
NextProtos: []string{"h2"},
|
||||
}
|
||||
conn := tls.Server(rawConn, cfg)
|
||||
if err := conn.Handshake(); err != nil {
|
||||
return handshakeResult{err: err}
|
||||
}
|
||||
return handshakeResult{connState: conn.ConnectionState()}
|
||||
}
|
||||
|
||||
// A handshake function which simulates a successful handshake with mutual
|
||||
// authentication.
|
||||
func testServerMutualTLSHandshake(rawConn net.Conn) handshakeResult {
|
||||
cert, err := tls.LoadX509KeyPair(testdata.Path("x509/server1_cert.pem"), testdata.Path("x509/server1_key.pem"))
|
||||
if err != nil {
|
||||
return handshakeResult{err: err}
|
||||
}
|
||||
pemData, err := os.ReadFile(testdata.Path("x509/client_ca_cert.pem"))
|
||||
if err != nil {
|
||||
return handshakeResult{err: err}
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
roots.AppendCertsFromPEM(pemData)
|
||||
cfg := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientCAs: roots,
|
||||
}
|
||||
conn := tls.Server(rawConn, cfg)
|
||||
if err := conn.Handshake(); err != nil {
|
||||
return handshakeResult{err: err}
|
||||
}
|
||||
return handshakeResult{connState: conn.ConnectionState()}
|
||||
}
|
||||
|
||||
// fakeProvider is an implementation of the certprovider.Provider interface
|
||||
// which returns the configured key material and error in calls to
|
||||
// KeyMaterial().
|
||||
type fakeProvider struct {
|
||||
km *certprovider.KeyMaterial
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeProvider) KeyMaterial(context.Context) (*certprovider.KeyMaterial, error) {
|
||||
return f.km, f.err
|
||||
}
|
||||
|
||||
func (f *fakeProvider) Close() {}
|
||||
|
||||
// makeIdentityProvider creates a new instance of the fakeProvider returning the
|
||||
// identity key material specified in the provider file paths.
|
||||
func makeIdentityProvider(t *testing.T, certPath, keyPath string) certprovider.Provider {
|
||||
t.Helper()
|
||||
cert, err := tls.LoadX509KeyPair(testdata.Path(certPath), testdata.Path(keyPath))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return &fakeProvider{km: &certprovider.KeyMaterial{Certs: []tls.Certificate{cert}}}
|
||||
}
|
||||
|
||||
// makeRootProvider creates a new instance of the fakeProvider returning the
|
||||
// root key material specified in the provider file paths.
|
||||
func makeRootProvider(t *testing.T, caPath string) *fakeProvider {
|
||||
pemData, err := os.ReadFile(testdata.Path(caPath))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
roots.AppendCertsFromPEM(pemData)
|
||||
return &fakeProvider{km: &certprovider.KeyMaterial{Roots: roots}}
|
||||
}
|
||||
|
||||
// newTestContextWithHandshakeInfo returns a copy of parent with HandshakeInfo
|
||||
// context value added to it.
|
||||
func newTestContextWithHandshakeInfo(parent context.Context, root, identity certprovider.Provider, sanExactMatch, sni string, validateSANUsingSNI bool) context.Context {
|
||||
// Creating the HandshakeInfo and adding it to the attributes is very
|
||||
// similar to what the CDS balancer would do when it intercepts calls to
|
||||
// NewSubConn().
|
||||
var sms []matcher.StringMatcher
|
||||
if sanExactMatch != "" {
|
||||
sms = []matcher.StringMatcher{matcher.NewExactStringMatcher(sanExactMatch, false)}
|
||||
}
|
||||
var hiPtr atomic.Pointer[grpcsync.RefCounted[*xdsinternal.HandshakeInfo]]
|
||||
info := xdsinternal.NewHandshakeInfo(root, identity, sms, sni, false, validateSANUsingSNI, false)
|
||||
hiPtr.Store(info)
|
||||
addr := xdsinternal.SetHandshakeInfo(resolver.Address{}, &hiPtr)
|
||||
|
||||
// Moving the attributes from the resolver.Address to the context passed to
|
||||
// the handshaker is done in the transport layer. Since we directly call the
|
||||
// handshaker in these tests, we need to do the same here.
|
||||
return icredentials.NewClientHandshakeInfoContext(parent, credentials.ClientHandshakeInfo{Attributes: addr.Attributes})
|
||||
}
|
||||
|
||||
// compareAuthInfo compares the AuthInfo received on the client side after a
|
||||
// successful handshake with the authInfo available on the testServer.
|
||||
func compareAuthInfo(ctx context.Context, ts *testServer, ai credentials.AuthInfo) error {
|
||||
if ai.AuthType() != "tls" {
|
||||
return fmt.Errorf("ClientHandshake returned authType %q, want %q", ai.AuthType(), "tls")
|
||||
}
|
||||
info, ok := ai.(credentials.TLSInfo)
|
||||
if !ok {
|
||||
return fmt.Errorf("ClientHandshake returned authInfo of type %T, want %T", ai, credentials.TLSInfo{})
|
||||
}
|
||||
gotState := info.State
|
||||
|
||||
// Read the handshake result from the testServer which contains the TLS
|
||||
// connection state and compare it with the one received on the client-side.
|
||||
val, err := ts.hsResult.Receive(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("testServer failed to return handshake result: %v", err)
|
||||
}
|
||||
hsr := val.(handshakeResult)
|
||||
if hsr.err != nil {
|
||||
return fmt.Errorf("testServer handshake failure: %v", hsr.err)
|
||||
}
|
||||
// AuthInfo contains a variety of information. We only verify a subset here.
|
||||
// This is the same subset which is verified in TLS credentials tests.
|
||||
if err := compareConnState(gotState, hsr.connState); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareConnState(got, want tls.ConnectionState) error {
|
||||
switch {
|
||||
case got.Version != want.Version:
|
||||
return fmt.Errorf("TLS.ConnectionState got Version: %v, want: %v", got.Version, want.Version)
|
||||
case got.HandshakeComplete != want.HandshakeComplete:
|
||||
return fmt.Errorf("TLS.ConnectionState got HandshakeComplete: %v, want: %v", got.HandshakeComplete, want.HandshakeComplete)
|
||||
case got.CipherSuite != want.CipherSuite:
|
||||
return fmt.Errorf("TLS.ConnectionState got CipherSuite: %v, want: %v", got.CipherSuite, want.CipherSuite)
|
||||
case got.NegotiatedProtocol != want.NegotiatedProtocol:
|
||||
return fmt.Errorf("TLS.ConnectionState got NegotiatedProtocol: %v, want: %v", got.NegotiatedProtocol, want.NegotiatedProtocol)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestClientCredsWithoutFallback verifies that the call to
|
||||
// NewClientCredentials() fails when no fallback is specified.
|
||||
func (s) TestClientCredsWithoutFallback(t *testing.T) {
|
||||
if _, err := NewClientCredentials(ClientOptions{}); err == nil {
|
||||
t.Fatal("NewClientCredentials() succeeded without specifying fallback")
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientCredsInvalidHandshakeInfo verifies scenarios where the passed in
|
||||
// HandshakeInfo is invalid because it does not contain the expected certificate
|
||||
// providers.
|
||||
func (s) TestClientCredsInvalidHandshakeInfo(t *testing.T) {
|
||||
opts := ClientOptions{FallbackCreds: makeFallbackClientCreds(t)}
|
||||
creds, err := NewClientCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
pCtx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
ctx := newTestContextWithHandshakeInfo(pCtx, nil, &fakeProvider{}, "", "", false)
|
||||
if _, _, err := creds.ClientHandshake(ctx, authority, nil); err == nil {
|
||||
t.Fatal("ClientHandshake succeeded without root certificate provider in HandshakeInfo")
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientCredsProviderFailure verifies the cases where an expected
|
||||
// certificate provider is missing in the HandshakeInfo value in the context.
|
||||
func (s) TestClientCredsProviderFailure(t *testing.T) {
|
||||
opts := ClientOptions{FallbackCreds: makeFallbackClientCreds(t)}
|
||||
creds, err := NewClientCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
rootProvider certprovider.Provider
|
||||
identityProvider certprovider.Provider
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
desc: "erroring root provider",
|
||||
rootProvider: &fakeProvider{err: errors.New("root provider error")},
|
||||
wantErr: "root provider error",
|
||||
},
|
||||
{
|
||||
desc: "erroring identity provider",
|
||||
rootProvider: &fakeProvider{km: &certprovider.KeyMaterial{}},
|
||||
identityProvider: &fakeProvider{err: errors.New("identity provider error")},
|
||||
wantErr: "identity provider error",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
ctx = newTestContextWithHandshakeInfo(ctx, test.rootProvider, test.identityProvider, "", "", false)
|
||||
if _, _, err := creds.ClientHandshake(ctx, authority, nil); err == nil || !strings.Contains(err.Error(), test.wantErr) {
|
||||
t.Fatalf("ClientHandshake() returned error: %q, wantErr: %q", err, test.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientCredsSuccess verifies successful client handshake cases.
|
||||
func (s) TestClientCredsSuccess(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
handshakeFunc testHandshakeFunc
|
||||
handshakeInfoCtx func(ctx context.Context) context.Context
|
||||
enableSNIFlag bool
|
||||
}{
|
||||
{
|
||||
desc: "fallback",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
handshakeInfoCtx: func(ctx context.Context) context.Context {
|
||||
// Since we don't add a HandshakeInfo to the context, the
|
||||
// ClientHandshake() method will delegate to the fallback.
|
||||
return ctx
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLS",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
handshakeInfoCtx: func(ctx context.Context) context.Context {
|
||||
return newTestContextWithHandshakeInfo(ctx, makeRootProvider(t, "x509/server_ca_cert.pem"), nil, defaultTestCertSAN, "", false)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "mTLS",
|
||||
handshakeFunc: testServerMutualTLSHandshake,
|
||||
handshakeInfoCtx: func(ctx context.Context) context.Context {
|
||||
return newTestContextWithHandshakeInfo(ctx, makeRootProvider(t, "x509/server_ca_cert.pem"), makeIdentityProvider(t, "x509/server1_cert.pem", "x509/server1_key.pem"), defaultTestCertSAN, "", false)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "mTLS with no acceptedSANs specified",
|
||||
handshakeFunc: testServerMutualTLSHandshake,
|
||||
handshakeInfoCtx: func(ctx context.Context) context.Context {
|
||||
return newTestContextWithHandshakeInfo(ctx, makeRootProvider(t, "x509/server_ca_cert.pem"), makeIdentityProvider(t, "x509/server1_cert.pem", "x509/server1_key.pem"), "", "", false)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLS with SNI",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
handshakeInfoCtx: func(ctx context.Context) context.Context {
|
||||
return newTestContextWithHandshakeInfo(ctx, makeRootProvider(t, "x509/server_ca_cert.pem"), nil, "bad-match", defaultTestCertSAN, true)
|
||||
},
|
||||
enableSNIFlag: true,
|
||||
},
|
||||
{
|
||||
desc: "TLS with SNI, env variable disabled, AutoSniSanValidation enabled",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
handshakeInfoCtx: func(ctx context.Context) context.Context {
|
||||
return newTestContextWithHandshakeInfo(ctx, makeRootProvider(t, "x509/server_ca_cert.pem"), nil, defaultTestCertSAN, "bad-sni", true)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLS with SNI, env variable enabled but AutoSniSanValidation disabled",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
handshakeInfoCtx: func(ctx context.Context) context.Context {
|
||||
return newTestContextWithHandshakeInfo(ctx, makeRootProvider(t, "x509/server_ca_cert.pem"), nil, defaultTestCertSAN, "bad-sni", false)
|
||||
},
|
||||
enableSNIFlag: true,
|
||||
},
|
||||
{
|
||||
desc: "TLS with empty SNI, env variable enabled, AutoSniSanValidation enabled",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
handshakeInfoCtx: func(ctx context.Context) context.Context {
|
||||
return newTestContextWithHandshakeInfo(ctx, makeRootProvider(t, "x509/server_ca_cert.pem"), nil, defaultTestCertSAN, "", true)
|
||||
},
|
||||
enableSNIFlag: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
testutils.SetEnvConfig(t, &envconfig.XDSSNIEnabled, test.enableSNIFlag)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
ts := newTestServerWithHandshakeFunc(ctx, test.handshakeFunc)
|
||||
defer ts.stop()
|
||||
|
||||
opts := ClientOptions{FallbackCreds: makeFallbackClientCreds(t)}
|
||||
creds, err := NewClientCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, ai, err := creds.ClientHandshake(test.handshakeInfoCtx(ctx), authority, conn)
|
||||
if err != nil {
|
||||
t.Fatalf("ClientHandshake() returned failed: %q", err)
|
||||
}
|
||||
if err := compareAuthInfo(ctx, ts, ai); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestClientCredsHandshakeTimeout(t *testing.T) {
|
||||
clientDone := make(chan struct{})
|
||||
ctx, sCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer sCancel()
|
||||
// A handshake function which simulates a handshake timeout from the
|
||||
// server-side by simply blocking on the client-side handshake to timeout
|
||||
// and not writing any handshake data.
|
||||
hErr := errors.New("server handshake error")
|
||||
ts := newTestServerWithHandshakeFunc(ctx, func(net.Conn) handshakeResult {
|
||||
<-clientDone
|
||||
return handshakeResult{err: hErr}
|
||||
})
|
||||
defer ts.stop()
|
||||
|
||||
opts := ClientOptions{FallbackCreds: makeFallbackClientCreds(t)}
|
||||
creds, err := NewClientCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
sCtx, sCancel := context.WithTimeout(context.Background(), defaultTestShortTimeout)
|
||||
defer sCancel()
|
||||
ctx = newTestContextWithHandshakeInfo(sCtx, makeRootProvider(t, "x509/server_ca_cert.pem"), nil, defaultTestCertSAN, "", false)
|
||||
if _, _, err := creds.ClientHandshake(ctx, authority, conn); err == nil {
|
||||
t.Fatal("ClientHandshake() succeeded when expected to timeout")
|
||||
}
|
||||
close(clientDone)
|
||||
|
||||
// Read the handshake result from the testServer and make sure the expected
|
||||
// error is returned.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
val, err := ts.hsResult.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("testServer failed to return handshake result: %v", err)
|
||||
}
|
||||
hsr := val.(handshakeResult)
|
||||
if hsr.err != hErr {
|
||||
t.Fatalf("testServer handshake returned error: %v, want: %v", hsr.err, hErr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientCredsHandshakeFailure verifies different handshake failure cases.
|
||||
func (s) TestClientCredsHandshakeFailure(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
handshakeFunc testHandshakeFunc
|
||||
rootProvider certprovider.Provider
|
||||
san string
|
||||
sni string
|
||||
validateSANUsingSNI bool
|
||||
enableSNIFlag bool
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
desc: "cert validation failure",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
rootProvider: makeRootProvider(t, "x509/client_ca_cert.pem"),
|
||||
san: defaultTestCertSAN,
|
||||
wantErr: "x509: certificate signed by unknown authority",
|
||||
},
|
||||
{
|
||||
desc: "SAN mismatch",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
rootProvider: makeRootProvider(t, "x509/server_ca_cert.pem"),
|
||||
san: "bad-san",
|
||||
wantErr: "do not match any of the accepted SANs",
|
||||
},
|
||||
{
|
||||
desc: "SNI SAN mismatch",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
rootProvider: makeRootProvider(t, "x509/server_ca_cert.pem"),
|
||||
sni: "bad-sni",
|
||||
validateSANUsingSNI: true,
|
||||
wantErr: "do not match the SNI",
|
||||
enableSNIFlag: true,
|
||||
},
|
||||
{
|
||||
desc: "SNI set, AutoSniSanValidation disabled with SAN mismatch",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
rootProvider: makeRootProvider(t, "x509/server_ca_cert.pem"),
|
||||
sni: defaultTestCertSAN,
|
||||
san: "bad-san",
|
||||
validateSANUsingSNI: false,
|
||||
wantErr: "do not match any of the accepted SANs",
|
||||
enableSNIFlag: true,
|
||||
},
|
||||
{
|
||||
desc: "SNI set with SAN mismatch and AutoSniSanValidation enabled, environment variable disabled",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
rootProvider: makeRootProvider(t, "x509/server_ca_cert.pem"),
|
||||
sni: defaultTestCertSAN,
|
||||
san: "bad-san",
|
||||
validateSANUsingSNI: true,
|
||||
wantErr: "do not match any of the accepted SANs",
|
||||
},
|
||||
{
|
||||
desc: "SNI empty, AutoSniSanValidation enabled with SAN mismatch",
|
||||
handshakeFunc: testServerTLSHandshake,
|
||||
rootProvider: makeRootProvider(t, "x509/server_ca_cert.pem"),
|
||||
sni: "",
|
||||
san: "bad-san",
|
||||
validateSANUsingSNI: true,
|
||||
wantErr: "do not match any of the accepted SANs",
|
||||
enableSNIFlag: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
testutils.SetEnvConfig(t, &envconfig.XDSSNIEnabled, test.enableSNIFlag)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
ts := newTestServerWithHandshakeFunc(ctx, test.handshakeFunc)
|
||||
defer ts.stop()
|
||||
|
||||
opts := ClientOptions{FallbackCreds: makeFallbackClientCreds(t)}
|
||||
creds, err := NewClientCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
ctx = newTestContextWithHandshakeInfo(ctx, test.rootProvider, nil, test.san, test.sni, test.validateSANUsingSNI)
|
||||
if _, _, err := creds.ClientHandshake(ctx, authority, conn); err == nil || !strings.Contains(err.Error(), test.wantErr) {
|
||||
t.Fatalf("ClientHandshake() returned %q, wantErr %q", err, test.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientCredsProviderSwitch verifies the case where the first attempt of
|
||||
// ClientHandshake fails because of a handshake failure. Then we update the
|
||||
// certificate provider and the second attempt succeeds. This is an
|
||||
// approximation of the flow of events when the control plane specifies new
|
||||
// security config which results in new certificate providers being used.
|
||||
func (s) TestClientCredsProviderSwitch(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
ts := newTestServerWithHandshakeFunc(ctx, testServerTLSHandshake)
|
||||
defer ts.stop()
|
||||
|
||||
opts := ClientOptions{FallbackCreds: makeFallbackClientCreds(t)}
|
||||
creds, err := NewClientCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Create a root provider which will fail the handshake because it does not
|
||||
// use the correct trust roots.
|
||||
root1 := makeRootProvider(t, "x509/client_ca_cert.pem")
|
||||
handshakeInfo := xdsinternal.NewHandshakeInfo(root1, nil, []matcher.StringMatcher{matcher.NewExactStringMatcher(defaultTestCertSAN, false)}, "", false, false, false)
|
||||
// We need to repeat most of what newTestContextWithHandshakeInfo() does
|
||||
// here because we need access to the underlying HandshakeInfo so that we
|
||||
// can update it before the next call to ClientHandshake().
|
||||
var hiPtr atomic.Pointer[grpcsync.RefCounted[*xdsinternal.HandshakeInfo]]
|
||||
hiPtr.Store(handshakeInfo)
|
||||
addr := xdsinternal.SetHandshakeInfo(resolver.Address{}, &hiPtr)
|
||||
ctx = icredentials.NewClientHandshakeInfoContext(ctx, credentials.ClientHandshakeInfo{Attributes: addr.Attributes})
|
||||
if _, _, err := creds.ClientHandshake(ctx, authority, conn); err == nil {
|
||||
t.Fatal("ClientHandshake() succeeded when expected to fail")
|
||||
}
|
||||
// Drain the result channel on the test server so that we can inspect the
|
||||
// result for the next handshake.
|
||||
_, err = ts.hsResult.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("testServer failed to return handshake result: %v", err)
|
||||
}
|
||||
|
||||
conn, err = net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Create a new root provider which uses the correct trust roots. And update
|
||||
// the HandshakeInfo with the new provider.
|
||||
root2 := makeRootProvider(t, "x509/server_ca_cert.pem")
|
||||
handshakeInfo = xdsinternal.NewHandshakeInfo(root2, nil, []matcher.StringMatcher{matcher.NewExactStringMatcher(defaultTestCertSAN, false)}, "", false, false, false)
|
||||
// Update the existing pointer, which address attribute will continue to
|
||||
// point to.
|
||||
hiPtr.Store(handshakeInfo)
|
||||
_, ai, err := creds.ClientHandshake(ctx, authority, conn)
|
||||
if err != nil {
|
||||
t.Fatalf("ClientHandshake() returned failed: %q", err)
|
||||
}
|
||||
if err := compareAuthInfo(ctx, ts, ai); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestClientClone verifies the Clone() method on client credentials.
|
||||
func (s) TestClientClone(t *testing.T) {
|
||||
opts := ClientOptions{FallbackCreds: makeFallbackClientCreds(t)}
|
||||
orig, err := NewClientCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClientCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
// The credsImpl does not have any exported fields, and it does not make
|
||||
// sense to use any cmp options to look deep into. So, all we make sure here
|
||||
// is that the cloned object points to a different location in memory.
|
||||
if clone := orig.Clone(); clone == orig {
|
||||
t.Fatal("return value from Clone() doesn't point to new credentials instance")
|
||||
}
|
||||
}
|
||||
543
credentials/xds/xds_server_test.go
Normal file
543
credentials/xds/xds_server_test.go
Normal file
@@ -0,0 +1,543 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2020 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package xds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/tls/certprovider"
|
||||
xdsinternal "google.golang.org/grpc/internal/credentials/xds"
|
||||
"google.golang.org/grpc/internal/grpcsync"
|
||||
"google.golang.org/grpc/testdata"
|
||||
)
|
||||
|
||||
func makeClientTLSConfig(t *testing.T, mTLS bool) *tls.Config {
|
||||
t.Helper()
|
||||
|
||||
pemData, err := os.ReadFile(testdata.Path("x509/server_ca_cert.pem"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
roots := x509.NewCertPool()
|
||||
roots.AppendCertsFromPEM(pemData)
|
||||
|
||||
var certs []tls.Certificate
|
||||
if mTLS {
|
||||
cert, err := tls.LoadX509KeyPair(testdata.Path("x509/client1_cert.pem"), testdata.Path("x509/client1_key.pem"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: certs,
|
||||
RootCAs: roots,
|
||||
ServerName: "*.test.example.com",
|
||||
// Setting this to true completely turns off the certificate validation
|
||||
// on the client side. So, the client side handshake always seems to
|
||||
// succeed. But if we want to turn this ON, we will need to generate
|
||||
// certificates which work with localhost, or supply a custom
|
||||
// verification function. So, the server credentials tests will rely
|
||||
// solely on the success/failure of the server-side handshake.
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"h2"},
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a real TLS server credentials which is used as
|
||||
// fallback credentials from multiple tests.
|
||||
func makeFallbackServerCreds(t *testing.T) credentials.TransportCredentials {
|
||||
t.Helper()
|
||||
|
||||
creds, err := credentials.NewServerTLSFromFile(testdata.Path("x509/server1_cert.pem"), testdata.Path("x509/server1_key.pem"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return creds
|
||||
}
|
||||
|
||||
type errorCreds struct {
|
||||
credentials.TransportCredentials
|
||||
}
|
||||
|
||||
// TestServerCredsWithoutFallback verifies that the call to
|
||||
// NewServerCredentials() fails when no fallback is specified.
|
||||
func (s) TestServerCredsWithoutFallback(t *testing.T) {
|
||||
if _, err := NewServerCredentials(ServerOptions{}); err == nil {
|
||||
t.Fatal("NewServerCredentials() succeeded without specifying fallback")
|
||||
}
|
||||
}
|
||||
|
||||
type wrapperConn struct {
|
||||
net.Conn
|
||||
xdsHI *grpcsync.RefCounted[*xdsinternal.HandshakeInfo]
|
||||
deadline time.Time
|
||||
handshakeInfoErr error
|
||||
}
|
||||
|
||||
func (wc *wrapperConn) XDSHandshakeInfo() (*grpcsync.RefCounted[*xdsinternal.HandshakeInfo], error) {
|
||||
return wc.xdsHI, wc.handshakeInfoErr
|
||||
}
|
||||
|
||||
func (wc *wrapperConn) GetDeadline() time.Time {
|
||||
return wc.deadline
|
||||
}
|
||||
|
||||
func newWrappedConn(conn net.Conn, xdsHI *grpcsync.RefCounted[*xdsinternal.HandshakeInfo], deadline time.Time) *wrapperConn {
|
||||
return &wrapperConn{Conn: conn, xdsHI: xdsHI, deadline: deadline}
|
||||
}
|
||||
|
||||
// TestServerCredsInvalidHandshakeInfo verifies scenarios where the passed in
|
||||
// HandshakeInfo is invalid because it does not contain the expected certificate
|
||||
// providers.
|
||||
func (s) TestServerCredsInvalidHandshakeInfo(t *testing.T) {
|
||||
opts := ServerOptions{FallbackCreds: &errorCreds{}}
|
||||
creds, err := NewServerCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewServerCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
info := xdsinternal.NewHandshakeInfo(&fakeProvider{}, nil, nil, "", false, false, false)
|
||||
conn := newWrappedConn(nil, info, time.Time{})
|
||||
if _, _, err := creds.ServerHandshake(conn); err == nil {
|
||||
t.Fatal("ServerHandshake succeeded without identity certificate provider in HandshakeInfo")
|
||||
}
|
||||
}
|
||||
|
||||
// TestServerCredsProviderFailure verifies the cases where an expected
|
||||
// certificate provider is missing in the HandshakeInfo value in the context.
|
||||
func (s) TestServerCredsProviderFailure(t *testing.T) {
|
||||
opts := ServerOptions{FallbackCreds: &errorCreds{}}
|
||||
creds, err := NewServerCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewServerCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
rootProvider certprovider.Provider
|
||||
identityProvider certprovider.Provider
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
desc: "erroring identity provider",
|
||||
identityProvider: &fakeProvider{err: errors.New("identity provider error")},
|
||||
wantErr: "identity provider error",
|
||||
},
|
||||
{
|
||||
desc: "erroring root provider",
|
||||
identityProvider: &fakeProvider{km: &certprovider.KeyMaterial{}},
|
||||
rootProvider: &fakeProvider{err: errors.New("root provider error")},
|
||||
wantErr: "root provider error",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
info := xdsinternal.NewHandshakeInfo(test.rootProvider, test.identityProvider, nil, "", false, false, false)
|
||||
conn := newWrappedConn(nil, info, time.Time{})
|
||||
if _, _, err := creds.ServerHandshake(conn); err == nil || !strings.Contains(err.Error(), test.wantErr) {
|
||||
t.Fatalf("ServerHandshake() returned error: %q, wantErr: %q", err, test.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestServerCredsHandshake_XDSHandshakeInfoError verifies the case where the
|
||||
// call to XDSHandshakeInfo() from the ServerHandshake() method returns an
|
||||
// error, and the test verifies that the ServerHandshake() fails with the
|
||||
// expected error.
|
||||
func (s) TestServerCredsHandshake_XDSHandshakeInfoError(t *testing.T) {
|
||||
opts := ServerOptions{FallbackCreds: &errorCreds{}}
|
||||
creds, err := NewServerCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewServerCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Create a test server which uses the xDS server credentials created above
|
||||
// to perform TLS handshake on incoming connections.
|
||||
ts := newTestServerWithHandshakeFunc(ctx, func(rawConn net.Conn) handshakeResult {
|
||||
// Create a wrapped conn which returns a nil HandshakeInfo and a non-nil error.
|
||||
conn := newWrappedConn(rawConn, nil, time.Now().Add(defaultTestTimeout))
|
||||
hiErr := errors.New("xdsHandshakeInfo error")
|
||||
conn.handshakeInfoErr = hiErr
|
||||
|
||||
// Invoke the ServerHandshake() method on the xDS credentials and verify
|
||||
// that the error returned by the XDSHandshakeInfo() method on the
|
||||
// wrapped conn is returned here.
|
||||
_, _, err := creds.ServerHandshake(conn)
|
||||
if !errors.Is(err, hiErr) {
|
||||
return handshakeResult{err: fmt.Errorf("ServerHandshake() returned err: %v, wantErr: %v", err, hiErr)}
|
||||
}
|
||||
return handshakeResult{}
|
||||
})
|
||||
defer ts.stop()
|
||||
|
||||
// Dial the test server, but don't trigger the TLS handshake. This will
|
||||
// cause ServerHandshake() to fail.
|
||||
rawConn, err := net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer rawConn.Close()
|
||||
|
||||
// Read handshake result from the testServer which will return an error if
|
||||
// the handshake succeeded.
|
||||
val, err := ts.hsResult.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("testServer failed to return handshake result: %v", err)
|
||||
}
|
||||
hsr := val.(handshakeResult)
|
||||
if hsr.err != nil {
|
||||
t.Fatalf("testServer handshake failure: %v", hsr.err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestServerCredsHandshakeTimeout verifies the case where the client does not
|
||||
// send required handshake data before the deadline set on the net.Conn passed
|
||||
// to ServerHandshake().
|
||||
func (s) TestServerCredsHandshakeTimeout(t *testing.T) {
|
||||
opts := ServerOptions{FallbackCreds: &errorCreds{}}
|
||||
creds, err := NewServerCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewServerCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Create a test server which uses the xDS server credentials created above
|
||||
// to perform TLS handshake on incoming connections.
|
||||
ts := newTestServerWithHandshakeFunc(ctx, func(rawConn net.Conn) handshakeResult {
|
||||
hi := xdsinternal.NewHandshakeInfo(makeRootProvider(t, "x509/client_ca_cert.pem"), makeIdentityProvider(t, "x509/server2_cert.pem", "x509/server2_key.pem"), nil, "", true, false, false)
|
||||
|
||||
// Create a wrapped conn which can return the HandshakeInfo created
|
||||
// above with a very small deadline.
|
||||
d := time.Now().Add(defaultTestShortTimeout)
|
||||
rawConn.SetDeadline(d)
|
||||
conn := newWrappedConn(rawConn, hi, d)
|
||||
|
||||
// ServerHandshake() on the xDS credentials is expected to fail.
|
||||
if _, _, err := creds.ServerHandshake(conn); err == nil {
|
||||
return handshakeResult{err: errors.New("ServerHandshake() succeeded when expected to timeout")}
|
||||
}
|
||||
return handshakeResult{}
|
||||
})
|
||||
defer ts.stop()
|
||||
|
||||
// Dial the test server, but don't trigger the TLS handshake. This will
|
||||
// cause ServerHandshake() to fail.
|
||||
rawConn, err := net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer rawConn.Close()
|
||||
|
||||
// Read handshake result from the testServer and expect a failure result.
|
||||
val, err := ts.hsResult.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("testServer failed to return handshake result: %v", err)
|
||||
}
|
||||
hsr := val.(handshakeResult)
|
||||
if hsr.err != nil {
|
||||
t.Fatalf("testServer handshake failure: %v", hsr.err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestServerCredsHandshakeFailure verifies the case where the server-side
|
||||
// credentials uses a root certificate which does not match the certificate
|
||||
// presented by the client, and hence the handshake must fail.
|
||||
func (s) TestServerCredsHandshakeFailure(t *testing.T) {
|
||||
opts := ServerOptions{FallbackCreds: &errorCreds{}}
|
||||
creds, err := NewServerCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewServerCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Create a test server which uses the xDS server credentials created above
|
||||
// to perform TLS handshake on incoming connections.
|
||||
ts := newTestServerWithHandshakeFunc(ctx, func(rawConn net.Conn) handshakeResult {
|
||||
// Create a HandshakeInfo which has a root provider which does not match
|
||||
// the certificate sent by the client.
|
||||
hi := xdsinternal.NewHandshakeInfo(makeRootProvider(t, "x509/server_ca_cert.pem"), makeIdentityProvider(t, "x509/client2_cert.pem", "x509/client2_key.pem"), nil, "", true, false, false)
|
||||
|
||||
// Create a wrapped conn which can return the HandshakeInfo and
|
||||
// configured deadline to the xDS credentials' ServerHandshake()
|
||||
// method.
|
||||
conn := newWrappedConn(rawConn, hi, time.Now().Add(defaultTestTimeout))
|
||||
|
||||
// ServerHandshake() on the xDS credentials is expected to fail.
|
||||
if _, _, err := creds.ServerHandshake(conn); err == nil {
|
||||
return handshakeResult{err: errors.New("ServerHandshake() succeeded when expected to fail")}
|
||||
}
|
||||
return handshakeResult{}
|
||||
})
|
||||
defer ts.stop()
|
||||
|
||||
// Dial the test server, and trigger the TLS handshake.
|
||||
rawConn, err := net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer rawConn.Close()
|
||||
tlsConn := tls.Client(rawConn, makeClientTLSConfig(t, true))
|
||||
tlsConn.SetDeadline(time.Now().Add(defaultTestTimeout))
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read handshake result from the testServer which will return an error if
|
||||
// the handshake succeeded.
|
||||
val, err := ts.hsResult.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("testServer failed to return handshake result: %v", err)
|
||||
}
|
||||
hsr := val.(handshakeResult)
|
||||
if hsr.err != nil {
|
||||
t.Fatalf("testServer handshake failure: %v", hsr.err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestServerCredsHandshakeSuccess verifies success handshake cases.
|
||||
func (s) TestServerCredsHandshakeSuccess(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
fallbackCreds credentials.TransportCredentials
|
||||
rootProvider certprovider.Provider
|
||||
identityProvider certprovider.Provider
|
||||
requireClientCert bool
|
||||
}{
|
||||
{
|
||||
desc: "fallback",
|
||||
fallbackCreds: makeFallbackServerCreds(t),
|
||||
},
|
||||
{
|
||||
desc: "TLS",
|
||||
fallbackCreds: &errorCreds{},
|
||||
identityProvider: makeIdentityProvider(t, "x509/server2_cert.pem", "x509/server2_key.pem"),
|
||||
},
|
||||
{
|
||||
desc: "mTLS",
|
||||
fallbackCreds: &errorCreds{},
|
||||
identityProvider: makeIdentityProvider(t, "x509/server2_cert.pem", "x509/server2_key.pem"),
|
||||
rootProvider: makeRootProvider(t, "x509/client_ca_cert.pem"),
|
||||
requireClientCert: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
// Create an xDS server credentials.
|
||||
opts := ServerOptions{FallbackCreds: test.fallbackCreds}
|
||||
creds, err := NewServerCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewServerCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Create a test server which uses the xDS server credentials
|
||||
// created above to perform TLS handshake on incoming connections.
|
||||
ts := newTestServerWithHandshakeFunc(ctx, func(rawConn net.Conn) handshakeResult {
|
||||
// Create a HandshakeInfo with information from the test table.
|
||||
hi := xdsinternal.NewHandshakeInfo(test.rootProvider, test.identityProvider, nil, "", test.requireClientCert, false, false)
|
||||
|
||||
// Create a wrapped conn which can return the HandshakeInfo and
|
||||
// configured deadline to the xDS credentials' ServerHandshake()
|
||||
// method.
|
||||
conn := newWrappedConn(rawConn, hi, time.Now().Add(defaultTestTimeout))
|
||||
|
||||
// Invoke the ServerHandshake() method on the xDS credentials
|
||||
// and make some sanity checks before pushing the result for
|
||||
// inspection by the main test body.
|
||||
_, ai, err := creds.ServerHandshake(conn)
|
||||
if err != nil {
|
||||
return handshakeResult{err: fmt.Errorf("ServerHandshake() failed: %v", err)}
|
||||
}
|
||||
if ai.AuthType() != "tls" {
|
||||
return handshakeResult{err: fmt.Errorf("ServerHandshake returned authType %q, want %q", ai.AuthType(), "tls")}
|
||||
}
|
||||
info, ok := ai.(credentials.TLSInfo)
|
||||
if !ok {
|
||||
return handshakeResult{err: fmt.Errorf("ServerHandshake returned authInfo of type %T, want %T", ai, credentials.TLSInfo{})}
|
||||
}
|
||||
return handshakeResult{connState: info.State}
|
||||
})
|
||||
defer ts.stop()
|
||||
|
||||
// Dial the test server, and trigger the TLS handshake.
|
||||
rawConn, err := net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer rawConn.Close()
|
||||
tlsConn := tls.Client(rawConn, makeClientTLSConfig(t, test.requireClientCert))
|
||||
tlsConn.SetDeadline(time.Now().Add(defaultTestTimeout))
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the handshake result from the testServer which contains the
|
||||
// TLS connection state on the server-side and compare it with the
|
||||
// one received on the client-side.
|
||||
val, err := ts.hsResult.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("testServer failed to return handshake result: %v", err)
|
||||
}
|
||||
hsr := val.(handshakeResult)
|
||||
if hsr.err != nil {
|
||||
t.Fatalf("testServer handshake failure: %v", hsr.err)
|
||||
}
|
||||
|
||||
// AuthInfo contains a variety of information. We only verify a
|
||||
// subset here. This is the same subset which is verified in TLS
|
||||
// credentials tests.
|
||||
if err := compareConnState(tlsConn.ConnectionState(), hsr.connState); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s) TestServerCredsProviderSwitch(t *testing.T) {
|
||||
opts := ServerOptions{FallbackCreds: &errorCreds{}}
|
||||
creds, err := NewServerCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewServerCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// The first time the handshake function is invoked, it returns a
|
||||
// HandshakeInfo which is expected to fail. Further invocations return a
|
||||
// HandshakeInfo which is expected to succeed.
|
||||
cnt := 0
|
||||
// Create a test server which uses the xDS server credentials created above
|
||||
// to perform TLS handshake on incoming connections.
|
||||
ts := newTestServerWithHandshakeFunc(ctx, func(rawConn net.Conn) handshakeResult {
|
||||
cnt++
|
||||
var hi *grpcsync.RefCounted[*xdsinternal.HandshakeInfo]
|
||||
if cnt == 1 {
|
||||
// Create a HandshakeInfo which has a root provider which does not match
|
||||
// the certificate sent by the client.
|
||||
hi = xdsinternal.NewHandshakeInfo(makeRootProvider(t, "x509/server_ca_cert.pem"), makeIdentityProvider(t, "x509/client2_cert.pem", "x509/client2_key.pem"), nil, "", true, false, false)
|
||||
|
||||
// Create a wrapped conn which can return the HandshakeInfo and
|
||||
// configured deadline to the xDS credentials' ServerHandshake()
|
||||
// method.
|
||||
conn := newWrappedConn(rawConn, hi, time.Now().Add(defaultTestTimeout))
|
||||
|
||||
// ServerHandshake() on the xDS credentials is expected to fail.
|
||||
if _, _, err := creds.ServerHandshake(conn); err == nil {
|
||||
return handshakeResult{err: errors.New("ServerHandshake() succeeded when expected to fail")}
|
||||
}
|
||||
return handshakeResult{}
|
||||
}
|
||||
|
||||
hi = xdsinternal.NewHandshakeInfo(makeRootProvider(t, "x509/client_ca_cert.pem"), makeIdentityProvider(t, "x509/server1_cert.pem", "x509/server1_key.pem"), nil, "", true, false, false)
|
||||
|
||||
// Create a wrapped conn which can return the HandshakeInfo and
|
||||
// configured deadline to the xDS credentials' ServerHandshake()
|
||||
// method.
|
||||
conn := newWrappedConn(rawConn, hi, time.Now().Add(defaultTestTimeout))
|
||||
|
||||
// Invoke the ServerHandshake() method on the xDS credentials
|
||||
// and make some sanity checks before pushing the result for
|
||||
// inspection by the main test body.
|
||||
_, ai, err := creds.ServerHandshake(conn)
|
||||
if err != nil {
|
||||
return handshakeResult{err: fmt.Errorf("ServerHandshake() failed: %v", err)}
|
||||
}
|
||||
if ai.AuthType() != "tls" {
|
||||
return handshakeResult{err: fmt.Errorf("ServerHandshake returned authType %q, want %q", ai.AuthType(), "tls")}
|
||||
}
|
||||
info, ok := ai.(credentials.TLSInfo)
|
||||
if !ok {
|
||||
return handshakeResult{err: fmt.Errorf("ServerHandshake returned authInfo of type %T, want %T", ai, credentials.TLSInfo{})}
|
||||
}
|
||||
return handshakeResult{connState: info.State}
|
||||
})
|
||||
defer ts.stop()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
// Dial the test server, and trigger the TLS handshake.
|
||||
rawConn, err := net.Dial("tcp", ts.address)
|
||||
if err != nil {
|
||||
t.Fatalf("net.Dial(%s) failed: %v", ts.address, err)
|
||||
}
|
||||
defer rawConn.Close()
|
||||
tlsConn := tls.Client(rawConn, makeClientTLSConfig(t, true))
|
||||
tlsConn.SetDeadline(time.Now().Add(defaultTestTimeout))
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the handshake result from the testServer which contains the
|
||||
// TLS connection state on the server-side and compare it with the
|
||||
// one received on the client-side.
|
||||
val, err := ts.hsResult.Receive(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("testServer failed to return handshake result: %v", err)
|
||||
}
|
||||
hsr := val.(handshakeResult)
|
||||
if hsr.err != nil {
|
||||
t.Fatalf("testServer handshake failure: %v", hsr.err)
|
||||
}
|
||||
if i == 0 {
|
||||
// We expect the first handshake to fail. So, we skip checks which
|
||||
// compare connection state.
|
||||
continue
|
||||
}
|
||||
// AuthInfo contains a variety of information. We only verify a
|
||||
// subset here. This is the same subset which is verified in TLS
|
||||
// credentials tests.
|
||||
if err := compareConnState(tlsConn.ConnectionState(), hsr.connState); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestServerClone verifies the Clone() method on client credentials.
|
||||
func (s) TestServerClone(t *testing.T) {
|
||||
opts := ServerOptions{FallbackCreds: makeFallbackServerCreds(t)}
|
||||
orig, err := NewServerCredentials(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("NewServerCredentials(%v) failed: %v", opts, err)
|
||||
}
|
||||
|
||||
// The credsImpl does not have any exported fields, and it does not make
|
||||
// sense to use any cmp options to look deep into. So, all we make sure here
|
||||
// is that the cloned object points to a different location in memory.
|
||||
if clone := orig.Clone(); clone == orig {
|
||||
t.Fatal("return value from Clone() doesn't point to new credentials instance")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user