...

Source file src/google.golang.org/grpc/test/xds/xds_server_certificate_providers_test.go

Documentation: google.golang.org/grpc/test/xds

     1  /*
     2   *
     3   * Copyright 2023 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package xds_test
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"net"
    25  	"strconv"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/google/uuid"
    30  	"google.golang.org/grpc"
    31  	"google.golang.org/grpc/connectivity"
    32  	"google.golang.org/grpc/credentials/insecure"
    33  	xdscreds "google.golang.org/grpc/credentials/xds"
    34  	"google.golang.org/grpc/internal/testutils"
    35  	"google.golang.org/grpc/internal/testutils/xds/bootstrap"
    36  	"google.golang.org/grpc/internal/testutils/xds/e2e"
    37  	"google.golang.org/grpc/xds"
    38  	"google.golang.org/protobuf/types/known/wrapperspb"
    39  
    40  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    41  	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    42  	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    43  	v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
    44  	v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
    45  	v3discoverypb "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    46  	testgrpc "google.golang.org/grpc/interop/grpc_testing"
    47  	testpb "google.golang.org/grpc/interop/grpc_testing"
    48  )
    49  
    50  // Tests the case where the bootstrap configuration contains no certificate
    51  // providers, and xDS credentials with an insecure fallback is specified at
    52  // server creation time. The management server is configured to return a
    53  // server-side xDS Listener resource with no security configuration. The test
    54  // verifies that a gRPC client configured with insecure credentials is able to
    55  // make RPCs to the backend. This ensures that the insecure fallback
    56  // credentials are getting used on the server.
    57  func (s) TestServerSideXDS_WithNoCertificateProvidersInBootstrap_Success(t *testing.T) {
    58  	// Spin up an xDS management server.
    59  	mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{AllowResourceSubset: true})
    60  	if err != nil {
    61  		t.Fatalf("Failed to start management server: %v", err)
    62  	}
    63  	defer mgmtServer.Stop()
    64  
    65  	// Create bootstrap configuration with no certificate providers.
    66  	nodeID := uuid.New().String()
    67  	bs, err := bootstrap.Contents(bootstrap.Options{
    68  		NodeID:                             nodeID,
    69  		ServerURI:                          mgmtServer.Address,
    70  		ServerListenerResourceNameTemplate: e2e.ServerListenerResourceNameTemplate,
    71  	})
    72  	if err != nil {
    73  		t.Fatalf("Failed to create bootstrap configuration: %v", err)
    74  	}
    75  
    76  	// Spin up an xDS-enabled gRPC server that uses xDS credentials with
    77  	// insecure fallback, and the above bootstrap configuration.
    78  	lis, cleanup := setupGRPCServer(t, bs)
    79  	defer cleanup()
    80  
    81  	// Create an inbound xDS listener resource for the server side that does not
    82  	// contain any security configuration. This should force the server-side
    83  	// xdsCredentials to use fallback.
    84  	host, port, err := hostPortFromListener(lis)
    85  	if err != nil {
    86  		t.Fatalf("Failed to retrieve host and port of server: %v", err)
    87  	}
    88  	resources := e2e.UpdateOptions{
    89  		NodeID:         nodeID,
    90  		Listeners:      []*v3listenerpb.Listener{e2e.DefaultServerListener(host, port, e2e.SecurityLevelNone, "routeName")},
    91  		SkipValidation: true,
    92  	}
    93  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
    94  	defer cancel()
    95  	if err := mgmtServer.Update(ctx, resources); err != nil {
    96  		t.Fatal(err)
    97  	}
    98  
    99  	// Create a client that uses insecure creds and verify RPCs.
   100  	cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   101  	if err != nil {
   102  		t.Fatalf("Failed to dial local test server: %v", err)
   103  	}
   104  	defer cc.Close()
   105  
   106  	client := testgrpc.NewTestServiceClient(cc)
   107  	if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil {
   108  		t.Fatalf("EmptyCall() failed: %v", err)
   109  	}
   110  }
   111  
   112  // Tests the case where the bootstrap configuration contains no certificate
   113  // providers, and xDS credentials with an insecure fallback is specified at
   114  // server creation time. The management server is configured to return a
   115  // server-side xDS Listener resource with mTLS security configuration. The xDS
   116  // client is expected to NACK this resource because the certificate provider
   117  // instance name specified in the Listener resource will not be present in the
   118  // bootstrap file. The test verifies that server creation does not fail and that
   119  // the xDS-enabled gRPC server does not enter "serving" mode.
   120  func (s) TestServerSideXDS_WithNoCertificateProvidersInBootstrap_Failure(t *testing.T) {
   121  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   122  	defer cancel()
   123  
   124  	// Spin up an xDS management server that pushes on a channel when it
   125  	// receives a NACK for an LDS response.
   126  	nackCh := make(chan struct{}, 1)
   127  	mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{
   128  		OnStreamRequest: func(_ int64, req *v3discoverypb.DiscoveryRequest) error {
   129  			if req.GetTypeUrl() != "type.googleapis.com/envoy.config.listener.v3.Listener" {
   130  				return nil
   131  			}
   132  			if req.GetErrorDetail() == nil {
   133  				return nil
   134  			}
   135  			select {
   136  			case nackCh <- struct{}{}:
   137  			case <-ctx.Done():
   138  			}
   139  			return nil
   140  		},
   141  		AllowResourceSubset: true,
   142  	})
   143  	if err != nil {
   144  		t.Fatalf("Failed to start management server: %v", err)
   145  	}
   146  	defer mgmtServer.Stop()
   147  
   148  	// Create bootstrap configuration with no certificate providers.
   149  	nodeID := uuid.New().String()
   150  	bs, err := bootstrap.Contents(bootstrap.Options{
   151  		NodeID:                             nodeID,
   152  		ServerURI:                          mgmtServer.Address,
   153  		ServerListenerResourceNameTemplate: e2e.ServerListenerResourceNameTemplate,
   154  	})
   155  	if err != nil {
   156  		t.Fatalf("Failed to create bootstrap configuration: %v", err)
   157  	}
   158  
   159  	// Configure xDS credentials with an insecure fallback to be used on the
   160  	// server-side.
   161  	creds, err := xdscreds.NewServerCredentials(xdscreds.ServerOptions{FallbackCreds: insecure.NewCredentials()})
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  
   166  	// Initialize an xDS-enabled gRPC server and register the stubServer on it.
   167  	// Pass it a mode change server option that pushes on a channel the mode
   168  	// changes to "not serving".
   169  	servingModeCh := make(chan struct{})
   170  	modeChangeOpt := xds.ServingModeCallback(func(addr net.Addr, args xds.ServingModeChangeArgs) {
   171  		if args.Mode == connectivity.ServingModeServing {
   172  			close(servingModeCh)
   173  		}
   174  	})
   175  	server, err := xds.NewGRPCServer(grpc.Creds(creds), modeChangeOpt, xds.BootstrapContentsForTesting(bs))
   176  	if err != nil {
   177  		t.Fatalf("Failed to create an xDS enabled gRPC server: %v", err)
   178  	}
   179  	testgrpc.RegisterTestServiceServer(server, &testService{})
   180  	defer server.Stop()
   181  
   182  	// Create a local listener and pass it to Serve().
   183  	lis, err := testutils.LocalTCPListener()
   184  	if err != nil {
   185  		t.Fatalf("testutils.LocalTCPListener() failed: %v", err)
   186  	}
   187  
   188  	go func() {
   189  		if err := server.Serve(lis); err != nil {
   190  			t.Errorf("Serve() failed: %v", err)
   191  		}
   192  	}()
   193  
   194  	// Create an inbound xDS listener resource for the server side that contains
   195  	// mTLS security configuration. Since the received certificate provider
   196  	// instance name would be missing in the bootstrap configuration, this
   197  	// resource is expected to NACKed by the xDS client.
   198  	host, port, err := hostPortFromListener(lis)
   199  	if err != nil {
   200  		t.Fatalf("Failed to retrieve host and port of server: %v", err)
   201  	}
   202  	resources := e2e.UpdateOptions{
   203  		NodeID:         nodeID,
   204  		Listeners:      []*v3listenerpb.Listener{e2e.DefaultServerListener(host, port, e2e.SecurityLevelMTLS, "routeName")},
   205  		SkipValidation: true,
   206  	}
   207  	if err := mgmtServer.Update(ctx, resources); err != nil {
   208  		t.Fatal(err)
   209  	}
   210  
   211  	// Wait for the NACK from the xDS client.
   212  	select {
   213  	case <-nackCh:
   214  	case <-ctx.Done():
   215  		t.Fatal("Timeout when waiting for an NACK from the xDS client for the LDS response")
   216  	}
   217  
   218  	// Wait a short duration and ensure that the server does not enter "serving"
   219  	// mode.
   220  	select {
   221  	case <-time.After(2 * defaultTestShortTimeout):
   222  	case <-servingModeCh:
   223  		t.Fatal("Server changed to serving mode when not expected to")
   224  	}
   225  
   226  	// Create a client that uses insecure creds and verify that RPCs don't
   227  	// succeed.
   228  	cc, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   229  	if err != nil {
   230  		t.Fatalf("Failed to dial local test server: %v", err)
   231  	}
   232  	defer cc.Close()
   233  
   234  	waitForFailedRPCWithStatus(ctx, t, cc, errAcceptAndClose)
   235  }
   236  
   237  // Tests the case where the bootstrap configuration contains one certificate
   238  // provider, and xDS credentials with an insecure fallback is specified at
   239  // server creation time. Two listeners are configured on the xDS-enabled gRPC
   240  // server. The management server responds with two listener resources:
   241  //  1. contains valid security configuration pointing to the certificate provider
   242  //     instance specified in the bootstrap
   243  //  2. contains invalid security configuration pointing to a non-existent
   244  //     certificate provider instance
   245  //
   246  // The test verifies that an RPC to the first listener succeeds, while the
   247  // second listener never moves to "serving" mode and RPCs to it fail.
   248  func (s) TestServerSideXDS_WithValidAndInvalidSecurityConfiguration(t *testing.T) {
   249  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   250  	defer cancel()
   251  
   252  	// Spin up an xDS management server that pushes on a channel when it
   253  	// receives a NACK for an LDS response.
   254  	nackCh := make(chan struct{}, 1)
   255  	mgmtServer, nodeID, bs, _, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{
   256  		OnStreamRequest: func(_ int64, req *v3discoverypb.DiscoveryRequest) error {
   257  			if req.GetTypeUrl() != "type.googleapis.com/envoy.config.listener.v3.Listener" {
   258  				return nil
   259  			}
   260  			if req.GetErrorDetail() == nil {
   261  				return nil
   262  			}
   263  			select {
   264  			case nackCh <- struct{}{}:
   265  			case <-ctx.Done():
   266  			}
   267  			return nil
   268  		},
   269  		AllowResourceSubset: true,
   270  	})
   271  	defer cleanup()
   272  
   273  	// Create two local listeners.
   274  	lis1, err := testutils.LocalTCPListener()
   275  	if err != nil {
   276  		t.Fatalf("testutils.LocalTCPListener() failed: %v", err)
   277  	}
   278  	lis2, err := testutils.LocalTCPListener()
   279  	if err != nil {
   280  		t.Fatalf("testutils.LocalTCPListener() failed: %v", err)
   281  	}
   282  
   283  	// Create an xDS-enabled grpc server that is configured to use xDS
   284  	// credentials, and register the test service on it. Configure a mode change
   285  	// option that closes a channel when listener2 enter serving mode.
   286  	creds, err := xdscreds.NewServerCredentials(xdscreds.ServerOptions{FallbackCreds: insecure.NewCredentials()})
   287  	if err != nil {
   288  		t.Fatal(err)
   289  	}
   290  	servingModeCh := make(chan struct{})
   291  	modeChangeOpt := xds.ServingModeCallback(func(addr net.Addr, args xds.ServingModeChangeArgs) {
   292  		if addr.String() == lis2.Addr().String() {
   293  			if args.Mode == connectivity.ServingModeServing {
   294  				close(servingModeCh)
   295  			}
   296  		}
   297  	})
   298  	server, err := xds.NewGRPCServer(grpc.Creds(creds), modeChangeOpt, xds.BootstrapContentsForTesting(bs))
   299  	if err != nil {
   300  		t.Fatalf("Failed to create an xDS enabled gRPC server: %v", err)
   301  	}
   302  	testgrpc.RegisterTestServiceServer(server, &testService{})
   303  	defer server.Stop()
   304  
   305  	go func() {
   306  		if err := server.Serve(lis1); err != nil {
   307  			t.Errorf("Serve() failed: %v", err)
   308  		}
   309  	}()
   310  	go func() {
   311  		if err := server.Serve(lis2); err != nil {
   312  			t.Errorf("Serve() failed: %v", err)
   313  		}
   314  	}()
   315  
   316  	// Create inbound xDS listener resources for the server side that contains
   317  	// mTLS security configuration.
   318  	// lis1 --> security configuration pointing to a valid cert provider
   319  	// lis2 --> security configuration pointing to a non-existent cert provider
   320  	host1, port1, err := hostPortFromListener(lis1)
   321  	if err != nil {
   322  		t.Fatalf("Failed to retrieve host and port of server: %v", err)
   323  	}
   324  	resource1 := e2e.DefaultServerListener(host1, port1, e2e.SecurityLevelMTLS, "routeName")
   325  	host2, port2, err := hostPortFromListener(lis2)
   326  	if err != nil {
   327  		t.Fatalf("Failed to retrieve host and port of server: %v", err)
   328  	}
   329  	hcm := &v3httppb.HttpConnectionManager{
   330  		RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
   331  			RouteConfig: &v3routepb.RouteConfiguration{
   332  				Name: "routeName",
   333  				VirtualHosts: []*v3routepb.VirtualHost{{
   334  					Domains: []string{"*"},
   335  					Routes: []*v3routepb.Route{{
   336  						Match: &v3routepb.RouteMatch{
   337  							PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
   338  						},
   339  						Action: &v3routepb.Route_NonForwardingAction{},
   340  					}}}}},
   341  		},
   342  		HttpFilters: []*v3httppb.HttpFilter{e2e.RouterHTTPFilter},
   343  	}
   344  	ts := &v3corepb.TransportSocket{
   345  		Name: "envoy.transport_sockets.tls",
   346  		ConfigType: &v3corepb.TransportSocket_TypedConfig{
   347  			TypedConfig: testutils.MarshalAny(t, &v3tlspb.DownstreamTlsContext{
   348  				RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
   349  				CommonTlsContext: &v3tlspb.CommonTlsContext{
   350  					TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   351  						InstanceName: "non-existent-certificate-provider",
   352  					},
   353  					ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
   354  						ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   355  							InstanceName: "non-existent-certificate-provider",
   356  						},
   357  					},
   358  				},
   359  			}),
   360  		},
   361  	}
   362  	resource2 := &v3listenerpb.Listener{
   363  		Name: fmt.Sprintf(e2e.ServerListenerResourceNameTemplate, net.JoinHostPort(host2, strconv.Itoa(int(port2)))),
   364  		Address: &v3corepb.Address{
   365  			Address: &v3corepb.Address_SocketAddress{
   366  				SocketAddress: &v3corepb.SocketAddress{
   367  					Address: host2,
   368  					PortSpecifier: &v3corepb.SocketAddress_PortValue{
   369  						PortValue: port2,
   370  					},
   371  				},
   372  			},
   373  		},
   374  		FilterChains: []*v3listenerpb.FilterChain{
   375  			{
   376  				Name: "v4-wildcard",
   377  				FilterChainMatch: &v3listenerpb.FilterChainMatch{
   378  					PrefixRanges: []*v3corepb.CidrRange{
   379  						{
   380  							AddressPrefix: "0.0.0.0",
   381  							PrefixLen: &wrapperspb.UInt32Value{
   382  								Value: uint32(0),
   383  							},
   384  						},
   385  					},
   386  					SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
   387  					SourcePrefixRanges: []*v3corepb.CidrRange{
   388  						{
   389  							AddressPrefix: "0.0.0.0",
   390  							PrefixLen: &wrapperspb.UInt32Value{
   391  								Value: uint32(0),
   392  							},
   393  						},
   394  					},
   395  				},
   396  				Filters: []*v3listenerpb.Filter{
   397  					{
   398  						Name:       "filter-1",
   399  						ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: testutils.MarshalAny(t, hcm)},
   400  					},
   401  				},
   402  				TransportSocket: ts,
   403  			},
   404  			{
   405  				Name: "v6-wildcard",
   406  				FilterChainMatch: &v3listenerpb.FilterChainMatch{
   407  					PrefixRanges: []*v3corepb.CidrRange{
   408  						{
   409  							AddressPrefix: "::",
   410  							PrefixLen: &wrapperspb.UInt32Value{
   411  								Value: uint32(0),
   412  							},
   413  						},
   414  					},
   415  					SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
   416  					SourcePrefixRanges: []*v3corepb.CidrRange{
   417  						{
   418  							AddressPrefix: "::",
   419  							PrefixLen: &wrapperspb.UInt32Value{
   420  								Value: uint32(0),
   421  							},
   422  						},
   423  					},
   424  				},
   425  				Filters: []*v3listenerpb.Filter{
   426  					{
   427  						Name:       "filter-1",
   428  						ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: testutils.MarshalAny(t, hcm)},
   429  					},
   430  				},
   431  				TransportSocket: ts,
   432  			},
   433  		},
   434  	}
   435  	resources := e2e.UpdateOptions{
   436  		NodeID:         nodeID,
   437  		Listeners:      []*v3listenerpb.Listener{resource1, resource2},
   438  		SkipValidation: true,
   439  	}
   440  	if err := mgmtServer.Update(ctx, resources); err != nil {
   441  		t.Fatal(err)
   442  	}
   443  
   444  	// Create a client that uses TLS creds and verify RPCs to listener1.
   445  	clientCreds := e2e.CreateClientTLSCredentials(t)
   446  	cc1, err := grpc.NewClient(lis1.Addr().String(), grpc.WithTransportCredentials(clientCreds))
   447  	if err != nil {
   448  		t.Fatalf("Failed to dial local test server: %v", err)
   449  	}
   450  	defer cc1.Close()
   451  
   452  	client1 := testgrpc.NewTestServiceClient(cc1)
   453  	if _, err := client1.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil {
   454  		t.Fatalf("EmptyCall() failed: %v", err)
   455  	}
   456  
   457  	// Wait for the NACK from the xDS client.
   458  	select {
   459  	case <-nackCh:
   460  	case <-ctx.Done():
   461  		t.Fatal("Timeout when waiting for an NACK from the xDS client for the LDS response")
   462  	}
   463  
   464  	// Wait a short duration and ensure that the server does not enter "serving"
   465  	// mode.
   466  	select {
   467  	case <-time.After(2 * defaultTestShortTimeout):
   468  	case <-servingModeCh:
   469  		t.Fatal("Server changed to serving mode when not expected to")
   470  	}
   471  
   472  	// Create a client that uses insecure creds and verify that RPCs don't
   473  	// succeed to listener2.
   474  	cc2, err := grpc.NewClient(lis2.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
   475  	if err != nil {
   476  		t.Fatalf("Failed to dial local test server: %v", err)
   477  	}
   478  	defer cc2.Close()
   479  
   480  	waitForFailedRPCWithStatus(ctx, t, cc2, errAcceptAndClose)
   481  }
   482  

View as plain text