google.golang.org/grpc@v1.72.2/test/xds/xds_server_integration_test.go (about) 1 /* 2 * 3 * Copyright 2020 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 "io" 25 "net" 26 "strconv" 27 "testing" 28 "time" 29 30 "github.com/google/uuid" 31 "google.golang.org/grpc" 32 "google.golang.org/grpc/codes" 33 "google.golang.org/grpc/credentials/insecure" 34 xdscreds "google.golang.org/grpc/credentials/xds" 35 "google.golang.org/grpc/internal" 36 "google.golang.org/grpc/internal/grpcsync" 37 "google.golang.org/grpc/internal/stubserver" 38 "google.golang.org/grpc/internal/testutils" 39 "google.golang.org/grpc/internal/testutils/xds/e2e" 40 "google.golang.org/grpc/internal/testutils/xds/e2e/setup" 41 "google.golang.org/grpc/resolver" 42 "google.golang.org/grpc/status" 43 "google.golang.org/grpc/xds" 44 45 testgrpc "google.golang.org/grpc/interop/grpc_testing" 46 testpb "google.golang.org/grpc/interop/grpc_testing" 47 ) 48 49 func testModeChangeServerOption(t *testing.T) grpc.ServerOption { 50 // Create a server option to get notified about serving mode changes. We don't 51 // do anything other than throwing a log entry here. But this is required, 52 // since the server code emits a log entry at the default level (which is 53 // ERROR) if no callback is registered for serving mode changes. Our 54 // testLogger fails the test if there is any log entry at ERROR level. It does 55 // provide an ExpectError() method, but that takes a string and it would be 56 // painful to construct the exact error message expected here. Instead this 57 // works just fine. 58 return xds.ServingModeCallback(func(addr net.Addr, args xds.ServingModeChangeArgs) { 59 t.Logf("Serving mode for listener %q changed to %q, err: %v", addr.String(), args.Mode, args.Err) 60 }) 61 } 62 63 // acceptNotifyingListener wraps a listener and notifies users when a server 64 // calls the Listener.Accept() method. This can be used to ensure that the 65 // server is ready before requests are sent to it. 66 type acceptNotifyingListener struct { 67 net.Listener 68 serverReady grpcsync.Event 69 } 70 71 func (l *acceptNotifyingListener) Accept() (net.Conn, error) { 72 l.serverReady.Fire() 73 return l.Listener.Accept() 74 } 75 76 // setupGRPCServer performs the following: 77 // - spin up an xDS-enabled gRPC server, configure it with xdsCredentials and 78 // register the test service on it 79 // - create a local TCP listener and start serving on it 80 // 81 // Returns the following: 82 // - local listener on which the xDS-enabled gRPC server is serving on 83 // - cleanup function to be invoked by the tests when done 84 func setupGRPCServer(t *testing.T, bootstrapContents []byte) (net.Listener, func()) { 85 t.Helper() 86 87 // Configure xDS credentials to be used on the server-side. 88 creds, err := xdscreds.NewServerCredentials(xdscreds.ServerOptions{ 89 FallbackCreds: insecure.NewCredentials(), 90 }) 91 if err != nil { 92 t.Fatal(err) 93 } 94 95 // Initialize a test gRPC server, assign it to the stub server, and start 96 // the test service. 97 stub := &stubserver.StubServer{ 98 EmptyCallF: func(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) { 99 return &testpb.Empty{}, nil 100 }, 101 UnaryCallF: func(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) { 102 return &testpb.SimpleResponse{}, nil 103 }, 104 FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error { 105 for { 106 _, err := stream.Recv() // hangs here forever if stream doesn't shut down...doesn't receive EOF without any errors 107 if err == io.EOF { 108 return nil 109 } 110 } 111 }, 112 } 113 114 if stub.S, err = xds.NewGRPCServer(grpc.Creds(creds), testModeChangeServerOption(t), xds.BootstrapContentsForTesting(bootstrapContents)); err != nil { 115 t.Fatalf("Failed to create an xDS enabled gRPC server: %v", err) 116 } 117 118 stubserver.StartTestService(t, stub) 119 120 // Create a local listener and pass it to Serve(). 121 lis, err := testutils.LocalTCPListener() 122 if err != nil { 123 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 124 } 125 126 readyLis := &acceptNotifyingListener{ 127 Listener: lis, 128 serverReady: *grpcsync.NewEvent(), 129 } 130 131 go func() { 132 if err := stub.S.Serve(readyLis); err != nil { 133 t.Errorf("Serve() failed: %v", err) 134 } 135 }() 136 137 // Wait for the server to start running. 138 select { 139 case <-readyLis.serverReady.Done(): 140 case <-time.After(defaultTestTimeout): 141 t.Fatalf("Timed out while waiting for the backend server to start serving") 142 } 143 144 return lis, func() { 145 stub.S.Stop() 146 } 147 } 148 149 func hostPortFromListener(lis net.Listener) (string, uint32, error) { 150 host, p, err := net.SplitHostPort(lis.Addr().String()) 151 if err != nil { 152 return "", 0, fmt.Errorf("net.SplitHostPort(%s) failed: %v", lis.Addr().String(), err) 153 } 154 port, err := strconv.ParseInt(p, 10, 32) 155 if err != nil { 156 return "", 0, fmt.Errorf("strconv.ParseInt(%s, 10, 32) failed: %v", p, err) 157 } 158 return host, uint32(port), nil 159 } 160 161 // TestServerSideXDS_Fallback is an e2e test which verifies xDS credentials 162 // fallback functionality. 163 // 164 // The following sequence of events happen as part of this test: 165 // - An xDS-enabled gRPC server is created and xDS credentials are configured. 166 // - xDS is enabled on the client by the use of the xds:/// scheme, and xDS 167 // credentials are configured. 168 // - Control plane is configured to not send any security configuration to both 169 // the client and the server. This results in both of them using the 170 // configured fallback credentials (which is insecure creds in this case). 171 func (s) TestServerSideXDS_Fallback(t *testing.T) { 172 managementServer, nodeID, bootstrapContents, xdsResolver := setup.ManagementServerAndResolver(t) 173 174 lis, cleanup2 := setupGRPCServer(t, bootstrapContents) 175 defer cleanup2() 176 177 // Grab the host and port of the server and create client side xDS resources 178 // corresponding to it. This contains default resources with no security 179 // configuration in the Cluster resources. 180 host, port, err := hostPortFromListener(lis) 181 if err != nil { 182 t.Fatalf("failed to retrieve host and port of server: %v", err) 183 } 184 const serviceName = "my-service-fallback" 185 resources := e2e.DefaultClientResources(e2e.ResourceParams{ 186 DialTarget: serviceName, 187 NodeID: nodeID, 188 Host: host, 189 Port: port, 190 SecLevel: e2e.SecurityLevelNone, 191 }) 192 193 // Create an inbound xDS listener resource for the server side that does not 194 // contain any security configuration. This should force the server-side 195 // xdsCredentials to use fallback. 196 inboundLis := e2e.DefaultServerListener(host, port, e2e.SecurityLevelNone, "routeName") 197 resources.Listeners = append(resources.Listeners, inboundLis) 198 199 // Setup the management server with client and server-side resources. 200 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 201 defer cancel() 202 if err := managementServer.Update(ctx, resources); err != nil { 203 t.Fatal(err) 204 } 205 206 // Create client-side xDS credentials with an insecure fallback. 207 creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{ 208 FallbackCreds: insecure.NewCredentials(), 209 }) 210 if err != nil { 211 t.Fatal(err) 212 } 213 214 // Create a ClientConn with the xds scheme and make a successful RPC. 215 cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(creds), grpc.WithResolvers(xdsResolver)) 216 if err != nil { 217 t.Fatalf("failed to create a client for server: %v", err) 218 } 219 defer cc.Close() 220 221 client := testgrpc.NewTestServiceClient(cc) 222 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 223 t.Errorf("rpc EmptyCall() failed: %v", err) 224 } 225 } 226 227 // TestServerSideXDS_FileWatcherCerts is an e2e test which verifies xDS 228 // credentials with file watcher certificate provider. 229 // 230 // The following sequence of events happen as part of this test: 231 // - An xDS-enabled gRPC server is created and xDS credentials are configured. 232 // - xDS is enabled on the client by the use of the xds:/// scheme, and xDS 233 // credentials are configured. 234 // - Control plane is configured to send security configuration to both the 235 // client and the server, pointing to the file watcher certificate provider. 236 // We verify both TLS and mTLS scenarios. 237 func (s) TestServerSideXDS_FileWatcherCerts(t *testing.T) { 238 tests := []struct { 239 name string 240 secLevel e2e.SecurityLevel 241 }{ 242 { 243 name: "tls", 244 secLevel: e2e.SecurityLevelTLS, 245 }, 246 { 247 name: "mtls", 248 secLevel: e2e.SecurityLevelMTLS, 249 }, 250 } 251 for _, test := range tests { 252 t.Run(test.name, func(t *testing.T) { 253 managementServer, nodeID, bootstrapContents, xdsResolver := setup.ManagementServerAndResolver(t) 254 lis, cleanup2 := setupGRPCServer(t, bootstrapContents) 255 defer cleanup2() 256 257 // Grab the host and port of the server and create client side xDS 258 // resources corresponding to it. 259 host, port, err := hostPortFromListener(lis) 260 if err != nil { 261 t.Fatalf("failed to retrieve host and port of server: %v", err) 262 } 263 264 // Create xDS resources to be consumed on the client side. This 265 // includes the listener, route configuration, cluster (with 266 // security configuration) and endpoint resources. 267 serviceName := "my-service-file-watcher-certs-" + test.name 268 resources := e2e.DefaultClientResources(e2e.ResourceParams{ 269 DialTarget: serviceName, 270 NodeID: nodeID, 271 Host: host, 272 Port: port, 273 SecLevel: test.secLevel, 274 }) 275 276 // Create an inbound xDS listener resource for the server side that 277 // contains security configuration pointing to the file watcher 278 // plugin. 279 inboundLis := e2e.DefaultServerListener(host, port, test.secLevel, "routeName") 280 resources.Listeners = append(resources.Listeners, inboundLis) 281 282 // Setup the management server with client and server resources. 283 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 284 defer cancel() 285 if err := managementServer.Update(ctx, resources); err != nil { 286 t.Fatal(err) 287 } 288 289 // Create client-side xDS credentials with an insecure fallback. 290 creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{ 291 FallbackCreds: insecure.NewCredentials(), 292 }) 293 if err != nil { 294 t.Fatal(err) 295 } 296 297 // Create a ClientConn with the xds scheme and make an RPC. 298 cc, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(creds), grpc.WithResolvers(xdsResolver)) 299 if err != nil { 300 t.Fatalf("failed to create a client for server: %v", err) 301 } 302 defer cc.Close() 303 304 client := testgrpc.NewTestServiceClient(cc) 305 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 306 t.Fatalf("rpc EmptyCall() failed: %v", err) 307 } 308 }) 309 } 310 } 311 312 // TestServerSideXDS_SecurityConfigChange is an e2e test where xDS is enabled on 313 // the server-side and xdsCredentials are configured for security. The control 314 // plane initially does not any security configuration. This forces the 315 // xdsCredentials to use fallback creds, which is this case is insecure creds. 316 // We verify that a client connecting with TLS creds is not able to successfully 317 // make an RPC. The control plane then sends a listener resource with security 318 // configuration pointing to the use of the file_watcher plugin and we verify 319 // that the same client is now able to successfully make an RPC. 320 func (s) TestServerSideXDS_SecurityConfigChange(t *testing.T) { 321 managementServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{AllowResourceSubset: true}) 322 323 // Create bootstrap configuration pointing to the above management server. 324 nodeID := uuid.New().String() 325 bootstrapContents := e2e.DefaultBootstrapContents(t, nodeID, managementServer.Address) 326 327 // Create an xDS resolver with the above bootstrap configuration. 328 if internal.NewXDSResolverWithConfigForTesting == nil { 329 t.Fatalf("internal.NewXDSResolverWithConfigForTesting is nil") 330 } 331 xdsResolver, err := internal.NewXDSResolverWithConfigForTesting.(func([]byte) (resolver.Builder, error))(bootstrapContents) 332 if err != nil { 333 t.Fatalf("Failed to create xDS resolver for testing: %v", err) 334 } 335 336 lis, cleanup2 := setupGRPCServer(t, bootstrapContents) 337 defer cleanup2() 338 339 // Grab the host and port of the server and create client side xDS resources 340 // corresponding to it. This contains default resources with no security 341 // configuration in the Cluster resource. This should force the xDS 342 // credentials on the client to use its fallback. 343 host, port, err := hostPortFromListener(lis) 344 if err != nil { 345 t.Fatalf("failed to retrieve host and port of server: %v", err) 346 } 347 const serviceName = "my-service-security-config-change" 348 resources := e2e.DefaultClientResources(e2e.ResourceParams{ 349 DialTarget: serviceName, 350 NodeID: nodeID, 351 Host: host, 352 Port: port, 353 SecLevel: e2e.SecurityLevelNone, 354 }) 355 356 // Create an inbound xDS listener resource for the server side that does not 357 // contain any security configuration. This should force the xDS credentials 358 // on server to use its fallback. 359 inboundLis := e2e.DefaultServerListener(host, port, e2e.SecurityLevelNone, "routeName") 360 resources.Listeners = append(resources.Listeners, inboundLis) 361 362 // Setup the management server with client and server-side resources. 363 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 364 defer cancel() 365 if err := managementServer.Update(ctx, resources); err != nil { 366 t.Fatal(err) 367 } 368 369 // Create client-side xDS credentials with an insecure fallback. 370 xdsCreds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{ 371 FallbackCreds: insecure.NewCredentials(), 372 }) 373 if err != nil { 374 t.Fatal(err) 375 } 376 377 // Create a ClientConn with the xds scheme and make a successful RPC. 378 xdsCC, err := grpc.NewClient(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(xdsCreds), grpc.WithResolvers(xdsResolver)) 379 if err != nil { 380 t.Fatalf("failed to create a client for server: %v", err) 381 } 382 defer xdsCC.Close() 383 384 client := testgrpc.NewTestServiceClient(xdsCC) 385 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 386 t.Fatalf("rpc EmptyCall() failed: %v", err) 387 } 388 389 // Create a ClientConn with TLS creds. This should fail since the server is 390 // using fallback credentials which in this case in insecure creds. 391 tlsCreds := testutils.CreateClientTLSCredentials(t) 392 tlsCC, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(tlsCreds)) 393 if err != nil { 394 t.Fatalf("failed to create a client for server: %v", err) 395 } 396 defer tlsCC.Close() 397 398 // We don't set 'waitForReady` here since we want this call to failfast. 399 client = testgrpc.NewTestServiceClient(tlsCC) 400 if _, err := client.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.Unavailable { 401 t.Fatal("rpc EmptyCall() succeeded when expected to fail") 402 } 403 404 // Switch server and client side resources with ones that contain required 405 // security configuration for mTLS with a file watcher certificate provider. 406 resources = e2e.DefaultClientResources(e2e.ResourceParams{ 407 DialTarget: serviceName, 408 NodeID: nodeID, 409 Host: host, 410 Port: port, 411 SecLevel: e2e.SecurityLevelMTLS, 412 }) 413 inboundLis = e2e.DefaultServerListener(host, port, e2e.SecurityLevelMTLS, "routeName") 414 resources.Listeners = append(resources.Listeners, inboundLis) 415 if err := managementServer.Update(ctx, resources); err != nil { 416 t.Fatal(err) 417 } 418 419 // Make another RPC with `waitForReady` set and expect this to succeed. 420 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 421 t.Fatalf("rpc EmptyCall() failed: %v", err) 422 } 423 }