google.golang.org/grpc@v1.62.1/test/xds/xds_server_certificate_providers_test.go (about) 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.Dial(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.Dial(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.Dial(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.Dial(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 }