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

This commit is contained in:
upstream-mirror
2026-09-15 20:40:15 +05:00
commit e241b33f26
1365 changed files with 339570 additions and 0 deletions

236
credentials/xds/xds.go Normal file
View 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
}

View 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")
}
}

View 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")
}
}