google.golang.org/grpc@v1.74.2/xds/internal/balancer/cdsbalancer/cdsbalancer_security_test.go (about) 1 /* 2 * Copyright 2020 gRPC authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cdsbalancer 18 19 import ( 20 "context" 21 "crypto/tls" 22 "crypto/x509" 23 "encoding/json" 24 "fmt" 25 "os" 26 "strings" 27 "testing" 28 "unsafe" 29 30 "github.com/google/go-cmp/cmp" 31 "github.com/google/uuid" 32 "google.golang.org/grpc" 33 "google.golang.org/grpc/attributes" 34 "google.golang.org/grpc/balancer" 35 "google.golang.org/grpc/connectivity" 36 "google.golang.org/grpc/credentials" 37 "google.golang.org/grpc/credentials/insecure" 38 "google.golang.org/grpc/credentials/tls/certprovider" 39 "google.golang.org/grpc/credentials/xds" 40 "google.golang.org/grpc/internal" 41 "google.golang.org/grpc/internal/balancer/stub" 42 xdscredsinternal "google.golang.org/grpc/internal/credentials/xds" 43 "google.golang.org/grpc/internal/envconfig" 44 "google.golang.org/grpc/internal/stubserver" 45 "google.golang.org/grpc/internal/testutils" 46 "google.golang.org/grpc/internal/testutils/xds/e2e" 47 "google.golang.org/grpc/internal/xds/bootstrap" 48 "google.golang.org/grpc/peer" 49 "google.golang.org/grpc/resolver" 50 "google.golang.org/grpc/resolver/manual" 51 "google.golang.org/grpc/serviceconfig" 52 "google.golang.org/grpc/testdata" 53 "google.golang.org/grpc/xds/internal/xdsclient" 54 55 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 56 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 57 v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 58 v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 59 testgrpc "google.golang.org/grpc/interop/grpc_testing" 60 testpb "google.golang.org/grpc/interop/grpc_testing" 61 62 _ "google.golang.org/grpc/credentials/tls/certprovider/pemfile" // Register the file watcher certificate provider plugin. 63 ) 64 65 // testCCWrapper wraps a balancer.ClientConn and intercepts NewSubConn and 66 // returns the xDS handshake info back to the test for inspection. 67 type testCCWrapper struct { 68 balancer.ClientConn 69 handshakeInfoCh chan *xdscredsinternal.HandshakeInfo 70 } 71 72 // NewSubConn forwards the call to the underlying balancer.ClientConn, but 73 // before that, it validates the following: 74 // - there is only one address in the addrs slice 75 // - the single address contains xDS handshake information, which is then 76 // pushed onto the handshakeInfoCh channel 77 func (tcc *testCCWrapper) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) { 78 if len(addrs) != 1 { 79 return nil, fmt.Errorf("NewSubConn got %d addresses, want 1", len(addrs)) 80 } 81 getHI := internal.GetXDSHandshakeInfoForTesting.(func(attr *attributes.Attributes) *unsafe.Pointer) 82 hi := getHI(addrs[0].Attributes) 83 if hi == nil { 84 return nil, fmt.Errorf("NewSubConn got address without xDS handshake info") 85 } 86 87 sc, err := tcc.ClientConn.NewSubConn(addrs, opts) 88 select { 89 case tcc.handshakeInfoCh <- (*xdscredsinternal.HandshakeInfo)(*hi): 90 default: 91 } 92 return sc, err 93 } 94 95 // Registers a wrapped cds LB policy for the duration of this test that retains 96 // all the functionality of the original cds LB policy, but overrides the 97 // NewSubConn method passed to the policy and makes the xDS handshake 98 // information passed to NewSubConn available to the test. 99 // 100 // Accepts as argument a channel onto which xDS handshake information passed to 101 // NewSubConn is written to. 102 func registerWrappedCDSPolicyWithNewSubConnOverride(t *testing.T, ch chan *xdscredsinternal.HandshakeInfo) { 103 cdsBuilder := balancer.Get(cdsName) 104 internal.BalancerUnregister(cdsBuilder.Name()) 105 var ccWrapper *testCCWrapper 106 stub.Register(cdsBuilder.Name(), stub.BalancerFuncs{ 107 Init: func(bd *stub.BalancerData) { 108 ccWrapper = &testCCWrapper{ 109 ClientConn: bd.ClientConn, 110 handshakeInfoCh: ch, 111 } 112 bd.ChildBalancer = cdsBuilder.Build(ccWrapper, bd.BuildOptions) 113 }, 114 ParseConfig: func(lbCfg json.RawMessage) (serviceconfig.LoadBalancingConfig, error) { 115 return cdsBuilder.(balancer.ConfigParser).ParseConfig(lbCfg) 116 }, 117 UpdateClientConnState: func(bd *stub.BalancerData, ccs balancer.ClientConnState) error { 118 return bd.ChildBalancer.UpdateClientConnState(ccs) 119 }, 120 Close: func(bd *stub.BalancerData) { 121 bd.ChildBalancer.Close() 122 }, 123 }) 124 t.Cleanup(func() { balancer.Register(cdsBuilder) }) 125 } 126 127 // Common setup for security tests: 128 // - creates an xDS client with the specified bootstrap configuration 129 // - creates a manual resolver that specifies cds as the top-level LB policy 130 // - creates a channel that uses the passed in client creds and the manual 131 // resolver 132 // - creates a test server that uses the passed in server creds 133 // 134 // Returns the following: 135 // - a client channel to make RPCs 136 // - address of the test backend server 137 func setupForSecurityTests(t *testing.T, bootstrapContents []byte, clientCreds, serverCreds credentials.TransportCredentials) (*grpc.ClientConn, string) { 138 t.Helper() 139 140 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 141 if err != nil { 142 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 143 } 144 pool := xdsclient.NewPool(config) 145 xdsClient, xdsClose, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 146 Name: t.Name(), 147 }) 148 if err != nil { 149 t.Fatalf("Failed to create xDS client: %v", err) 150 } 151 t.Cleanup(xdsClose) 152 153 // Create a manual resolver that configures the CDS LB policy as the 154 // top-level LB policy on the channel. 155 r := manual.NewBuilderWithScheme("whatever") 156 jsonSC := fmt.Sprintf(`{ 157 "loadBalancingConfig":[{ 158 "cds_experimental":{ 159 "cluster": "%s" 160 } 161 }] 162 }`, clusterName) 163 scpr := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(jsonSC) 164 state := xdsclient.SetClient(resolver.State{ServiceConfig: scpr}, xdsClient) 165 r.InitialState(state) 166 167 // Create a ClientConn with the specified transport credentials. 168 cc, err := grpc.NewClient(r.Scheme()+":///test.service", grpc.WithTransportCredentials(clientCreds), grpc.WithResolvers(r)) 169 if err != nil { 170 t.Fatalf("grpc.NewClient() failed: %v", err) 171 } 172 cc.Connect() 173 t.Cleanup(func() { cc.Close() }) 174 175 // Start a test service backend with the specified transport credentials. 176 sOpts := []grpc.ServerOption{} 177 if serverCreds != nil { 178 sOpts = append(sOpts, grpc.Creds(serverCreds)) 179 } 180 server := stubserver.StartTestService(t, nil, sOpts...) 181 t.Cleanup(server.Stop) 182 183 return cc, server.Address 184 } 185 186 // Creates transport credentials to be used on the client side that rely on xDS 187 // to provide the security configuration. It falls back to insecure creds if no 188 // security information is received from the management server. 189 func xdsClientCredsWithInsecureFallback(t *testing.T) credentials.TransportCredentials { 190 t.Helper() 191 192 xdsCreds, err := xds.NewClientCredentials(xds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) 193 if err != nil { 194 t.Fatalf("Failed to create xDS credentials: %v", err) 195 } 196 return xdsCreds 197 } 198 199 // Creates transport credentials to be used on the server side from certificate 200 // files in testdata/x509. 201 // 202 // The certificate returned by this function has a CommonName of "test-server1". 203 func tlsServerCreds(t *testing.T) credentials.TransportCredentials { 204 t.Helper() 205 206 cert, err := tls.LoadX509KeyPair(testdata.Path("x509/server1_cert.pem"), testdata.Path("x509/server1_key.pem")) 207 if err != nil { 208 t.Fatalf("Failed to load server cert and key: %v", err) 209 210 } 211 pemData, err := os.ReadFile(testdata.Path("x509/client_ca_cert.pem")) 212 if err != nil { 213 t.Fatalf("Failed to read client CA cert: %v", err) 214 } 215 roots := x509.NewCertPool() 216 roots.AppendCertsFromPEM(pemData) 217 cfg := &tls.Config{ 218 Certificates: []tls.Certificate{cert}, 219 ClientCAs: roots, 220 } 221 return credentials.NewTLS(cfg) 222 } 223 224 // Checks the AuthInfo available in the peer if it matches the expected security 225 // level of the connection. 226 func verifySecurityInformationFromPeer(t *testing.T, pr *peer.Peer, wantSecLevel e2e.SecurityLevel) { 227 // This is not a true helper in the Go sense, because it does not perform 228 // setup or cleanup tasks. Marking it a helper is to ensure that when the 229 // test fails, the line information of the caller is outputted instead of 230 // from here. 231 // 232 // And this function directly calls t.Fatalf() instead of returning an error 233 // and letting the caller decide what to do with it. This is also OK since 234 // all callers will simply end up calling t.Fatalf() with the returned 235 // error, and can't add any contextual information of value to the error 236 // message. 237 t.Helper() 238 239 switch wantSecLevel { 240 case e2e.SecurityLevelNone: 241 if pr.AuthInfo.AuthType() != "insecure" { 242 t.Fatalf("AuthType() is %s, want insecure", pr.AuthInfo.AuthType()) 243 } 244 case e2e.SecurityLevelMTLS: 245 ai, ok := pr.AuthInfo.(credentials.TLSInfo) 246 if !ok { 247 t.Fatalf("AuthInfo type is %T, want %T", pr.AuthInfo, credentials.TLSInfo{}) 248 } 249 if len(ai.State.PeerCertificates) != 1 { 250 t.Fatalf("Number of peer certificates is %d, want 1", len(ai.State.PeerCertificates)) 251 } 252 cert := ai.State.PeerCertificates[0] 253 const wantCommonName = "test-server1" 254 if cn := cert.Subject.CommonName; cn != wantCommonName { 255 t.Fatalf("Common name in peer certificate is %s, want %s", cn, wantCommonName) 256 } 257 } 258 } 259 260 // Tests the case where xDS credentials are not in use, but the cds LB policy 261 // receives a Cluster update with security configuration. Verifies that the 262 // security configuration is not parsed by the cds LB policy by looking at the 263 // xDS handshake info passed to NewSubConn. 264 func (s) TestSecurityConfigWithoutXDSCreds(t *testing.T) { 265 // Register a wrapped cds LB policy for the duration of this test that writes 266 // the xDS handshake info passed to NewSubConn onto the given channel. 267 handshakeInfoCh := make(chan *xdscredsinternal.HandshakeInfo, 1) 268 registerWrappedCDSPolicyWithNewSubConnOverride(t, handshakeInfoCh) 269 270 // Spin up an xDS management server. 271 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 272 273 // Create bootstrap configuration pointing to the above management server. 274 nodeID := uuid.New().String() 275 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 276 277 // Create a grpc channel with insecure creds talking to a test server with 278 // insecure credentials. 279 cc, serverAddress := setupForSecurityTests(t, bc, insecure.NewCredentials(), nil) 280 281 // Configure cluster and endpoints resources in the management server. The 282 // cluster resource is configured to return security configuration. 283 resources := e2e.UpdateOptions{ 284 NodeID: nodeID, 285 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelMTLS)}, 286 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})}, 287 SkipValidation: true, 288 } 289 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 290 defer cancel() 291 if err := mgmtServer.Update(ctx, resources); err != nil { 292 t.Fatal(err) 293 } 294 295 // Verify that a successful RPC can be made. 296 client := testgrpc.NewTestServiceClient(cc) 297 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 298 t.Fatalf("EmptyCall() failed: %v", err) 299 } 300 301 // Ensure that the xDS handshake info passed to NewSubConn is empty. 302 var gotHI *xdscredsinternal.HandshakeInfo 303 select { 304 case gotHI = <-handshakeInfoCh: 305 case <-ctx.Done(): 306 t.Fatal("Timeout when waiting to read handshake info passed to NewSubConn") 307 } 308 wantHI := xdscredsinternal.NewHandshakeInfo(nil, nil, nil, false) 309 if !cmp.Equal(gotHI, wantHI) { 310 t.Fatalf("NewSubConn got handshake info %+v, want %+v", gotHI, wantHI) 311 } 312 } 313 314 // Tests the case where xDS credentials are in use, but the cds LB policy 315 // receives a Cluster update without security configuration. Verifies that the 316 // xDS handshake info passed to NewSubConn specified the use of fallback 317 // credentials. 318 func (s) TestNoSecurityConfigWithXDSCreds(t *testing.T) { 319 // Register a wrapped cds LB policy for the duration of this test that writes 320 // the xDS handshake info passed to NewSubConn onto the given channel. 321 handshakeInfoCh := make(chan *xdscredsinternal.HandshakeInfo, 1) 322 registerWrappedCDSPolicyWithNewSubConnOverride(t, handshakeInfoCh) 323 324 // Spin up an xDS management server. 325 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 326 327 // Create bootstrap configuration pointing to the above management server. 328 nodeID := uuid.New().String() 329 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 330 331 // Create a grpc channel with xDS creds talking to a test server with 332 // insecure credentials. 333 cc, serverAddress := setupForSecurityTests(t, bc, xdsClientCredsWithInsecureFallback(t), nil) 334 335 // Configure cluster and endpoints resources in the management server. The 336 // cluster resource is not configured to return any security configuration. 337 resources := e2e.UpdateOptions{ 338 NodeID: nodeID, 339 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)}, 340 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})}, 341 SkipValidation: true, 342 } 343 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 344 defer cancel() 345 if err := mgmtServer.Update(ctx, resources); err != nil { 346 t.Fatal(err) 347 } 348 349 // Verify that a successful RPC can be made. 350 client := testgrpc.NewTestServiceClient(cc) 351 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 352 t.Fatalf("EmptyCall() failed: %v", err) 353 } 354 355 // Ensure that the xDS handshake info passed to NewSubConn is empty. 356 var gotHI *xdscredsinternal.HandshakeInfo 357 select { 358 case gotHI = <-handshakeInfoCh: 359 case <-ctx.Done(): 360 t.Fatal("Timeout when waiting to read handshake info passed to NewSubConn") 361 } 362 wantHI := xdscredsinternal.NewHandshakeInfo(nil, nil, nil, false) 363 if !cmp.Equal(gotHI, wantHI) { 364 t.Fatalf("NewSubConn got handshake info %+v, want %+v", gotHI, wantHI) 365 } 366 if !gotHI.UseFallbackCreds() { 367 t.Fatal("NewSubConn got handshake info that does not specify the use of fallback creds") 368 } 369 } 370 371 // Tests the case where the security config returned by the management server 372 // cannot be resolved based on the contents of the bootstrap config. Verifies 373 // that the cds LB policy puts the channel in TRANSIENT_FAILURE. 374 func (s) TestSecurityConfigNotFoundInBootstrap(t *testing.T) { 375 // Spin up an xDS management server. 376 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 377 378 // Create bootstrap configuration pointing to the above management server, 379 // and one that does not have certificate providers configuration. 380 nodeID := uuid.New().String() 381 bootstrapContents, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{ 382 Servers: []byte(fmt.Sprintf(`[{ 383 "server_uri": %q, 384 "channel_creds": [{"type": "insecure"}] 385 }]`, mgmtServer.Address)), 386 Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)), 387 ServerListenerResourceNameTemplate: e2e.ServerListenerResourceNameTemplate, 388 }) 389 if err != nil { 390 t.Fatalf("Failed to create bootstrap configuration: %v", err) 391 } 392 393 // Create a grpc channel with xDS creds. 394 cc, _ := setupForSecurityTests(t, bootstrapContents, xdsClientCredsWithInsecureFallback(t), nil) 395 396 // Configure a cluster resource that contains security configuration, in the 397 // management server. 398 resources := e2e.UpdateOptions{ 399 NodeID: nodeID, 400 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelMTLS)}, 401 SkipValidation: true, 402 } 403 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 404 defer cancel() 405 if err := mgmtServer.Update(ctx, resources); err != nil { 406 t.Fatal(err) 407 } 408 409 testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure) 410 } 411 412 // A certificate provider builder that returns a nil Provider from the starter 413 // func passed to certprovider.NewBuildableConfig(). 414 type errCertProviderBuilder struct{} 415 416 const errCertProviderName = "err-cert-provider" 417 418 func (e errCertProviderBuilder) ParseConfig(any) (*certprovider.BuildableConfig, error) { 419 // Returning a nil Provider simulates the case where an error is encountered 420 // at the time of building the Provider. 421 bc := certprovider.NewBuildableConfig(errCertProviderName, nil, func(certprovider.BuildOptions) certprovider.Provider { return nil }) 422 return bc, nil 423 } 424 425 func (e errCertProviderBuilder) Name() string { 426 return errCertProviderName 427 } 428 429 func init() { 430 certprovider.Register(errCertProviderBuilder{}) 431 } 432 433 // Tests the case where the certprovider.Store returns an error when the cds LB 434 // policy attempts to build a certificate provider. Verifies that the cds LB 435 // policy puts the channel in TRANSIENT_FAILURE. 436 func (s) TestCertproviderStoreError(t *testing.T) { 437 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 438 439 // Create bootstrap configuration pointing to the above management server 440 // and one that includes certificate providers configuration for 441 // errCertProviderBuilder. 442 nodeID := uuid.New().String() 443 providerCfg := json.RawMessage(fmt.Sprintf(`{ 444 "plugin_name": "%s", 445 "config": {} 446 }`, errCertProviderName)) 447 bootstrapContents, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{ 448 Servers: []byte(fmt.Sprintf(`[{ 449 "server_uri": %q, 450 "channel_creds": [{"type": "insecure"}] 451 }]`, mgmtServer.Address)), 452 Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)), 453 ServerListenerResourceNameTemplate: e2e.ServerListenerResourceNameTemplate, 454 CertificateProviders: map[string]json.RawMessage{e2e.ClientSideCertProviderInstance: providerCfg}, 455 }) 456 if err != nil { 457 t.Fatalf("Failed to create bootstrap configuration: %v", err) 458 } 459 460 // Create a grpc channel with xDS creds. 461 cc, _ := setupForSecurityTests(t, bootstrapContents, xdsClientCredsWithInsecureFallback(t), nil) 462 463 // Configure a cluster resource that contains security configuration, in the 464 // management server. 465 resources := e2e.UpdateOptions{ 466 NodeID: nodeID, 467 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelMTLS)}, 468 SkipValidation: true, 469 } 470 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 471 defer cancel() 472 if err := mgmtServer.Update(ctx, resources); err != nil { 473 t.Fatal(err) 474 } 475 476 testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure) 477 } 478 479 // Tests the case where the cds LB policy receives security configuration as 480 // part of the Cluster resource that can be successfully resolved using the 481 // bootstrap file contents. Verifies that the connection between the client and 482 // the server is secure. 483 func (s) TestGoodSecurityConfig(t *testing.T) { 484 // Spin up an xDS management server. 485 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 486 487 // Create bootstrap configuration pointing to the above management server 488 // and one that includes certificate providers configuration. 489 nodeID := uuid.New().String() 490 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 491 492 // Create a grpc channel with xDS creds talking to a test server with TLS 493 // credentials. 494 cc, serverAddress := setupForSecurityTests(t, bc, xdsClientCredsWithInsecureFallback(t), tlsServerCreds(t)) 495 496 // Configure cluster and endpoints resources in the management server. The 497 // cluster resource is configured to return security configuration. 498 resources := e2e.UpdateOptions{ 499 NodeID: nodeID, 500 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelMTLS)}, 501 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})}, 502 SkipValidation: true, 503 } 504 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 505 defer cancel() 506 if err := mgmtServer.Update(ctx, resources); err != nil { 507 t.Fatal(err) 508 } 509 510 // Verify that a successful RPC can be made over a secure connection. 511 client := testgrpc.NewTestServiceClient(cc) 512 peer := &peer.Peer{} 513 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(peer)); err != nil { 514 t.Fatalf("EmptyCall() failed: %v", err) 515 } 516 verifySecurityInformationFromPeer(t, peer, e2e.SecurityLevelMTLS) 517 } 518 519 // Tests the case where the cds LB policy receives security configuration as 520 // part of the Cluster resource that contains a certificate provider instance 521 // that is missing in the bootstrap file. Verifies that the channel moves to 522 // TRANSIENT_FAILURE. Subsequently, the cds LB policy receives a cluster 523 // resource that contains a certificate provider that is present in the 524 // bootstrap file. Verifies that the connection between the client and the 525 // server is secure. 526 func (s) TestSecurityConfigUpdate_BadToGood(t *testing.T) { 527 // Spin up an xDS management server. 528 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 529 530 // Create bootstrap configuration pointing to the above management server. 531 nodeID := uuid.New().String() 532 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 533 534 // Create a grpc channel with xDS creds talking to a test server with TLS 535 // credentials. 536 cc, serverAddress := setupForSecurityTests(t, bc, xdsClientCredsWithInsecureFallback(t), tlsServerCreds(t)) 537 538 // Configure cluster and endpoints resources in the management server. The 539 // cluster resource contains security configuration with a certificate 540 // provider instance that is missing in the bootstrap configuration. 541 cluster := e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone) 542 cluster.TransportSocket = &v3corepb.TransportSocket{ 543 Name: "envoy.transport_sockets.tls", 544 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 545 TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{ 546 CommonTlsContext: &v3tlspb.CommonTlsContext{ 547 ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{ 548 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 549 InstanceName: "unknown-certificate-provider-instance", 550 }, 551 }, 552 }, 553 }), 554 }, 555 } 556 resources := e2e.UpdateOptions{ 557 NodeID: nodeID, 558 Clusters: []*v3clusterpb.Cluster{cluster}, 559 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})}, 560 SkipValidation: true, 561 } 562 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 563 defer cancel() 564 if err := mgmtServer.Update(ctx, resources); err != nil { 565 t.Fatalf("Failed to update management server with initial resources: %v", err) 566 } 567 568 testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure) 569 570 // Update the management server with a Cluster resource that contains a 571 // certificate provider instance that is present in the bootstrap 572 // configuration. 573 resources = e2e.UpdateOptions{ 574 NodeID: nodeID, 575 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelMTLS)}, 576 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})}, 577 SkipValidation: true, 578 } 579 if err := mgmtServer.Update(ctx, resources); err != nil { 580 t.Fatalf("Failed to update management server with valid resources: %v", err) 581 } 582 583 // Verify that a successful RPC can be made over a secure connection. 584 client := testgrpc.NewTestServiceClient(cc) 585 peer := &peer.Peer{} 586 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(peer)); err != nil { 587 t.Fatalf("EmptyCall() failed: %v", err) 588 } 589 verifySecurityInformationFromPeer(t, peer, e2e.SecurityLevelMTLS) 590 } 591 592 // Tests the case where the cds LB policy receives security configuration as 593 // part of the Cluster resource that can be successfully resolved using the 594 // bootstrap file contents. Verifies that the connection between the client and 595 // the server is secure. Subsequently, the cds LB policy receives a cluster 596 // resource without security configuration. Verifies that this results in the 597 // use of fallback credentials, which in this case is insecure creds. 598 func (s) TestSecurityConfigUpdate_GoodToFallback(t *testing.T) { 599 // Spin up an xDS management server. 600 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 601 602 // Create bootstrap configuration pointing to the above management server. 603 nodeID := uuid.New().String() 604 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 605 606 // Create a grpc channel with xDS creds talking to a test server with TLS 607 // credentials. 608 cc, serverAddress := setupForSecurityTests(t, bc, xdsClientCredsWithInsecureFallback(t), tlsServerCreds(t)) 609 610 // Configure cluster and endpoints resources in the management server. The 611 // cluster resource is configured to return security configuration. 612 resources := e2e.UpdateOptions{ 613 NodeID: nodeID, 614 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelMTLS)}, 615 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})}, 616 SkipValidation: true, 617 } 618 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 619 defer cancel() 620 if err := mgmtServer.Update(ctx, resources); err != nil { 621 t.Fatal(err) 622 } 623 624 // Verify that a successful RPC can be made over a secure connection. 625 client := testgrpc.NewTestServiceClient(cc) 626 peer := &peer.Peer{} 627 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(peer)); err != nil { 628 t.Fatalf("EmptyCall() failed: %v", err) 629 } 630 verifySecurityInformationFromPeer(t, peer, e2e.SecurityLevelMTLS) 631 632 // Start a test service backend that does not expect a secure connection. 633 insecureServer := stubserver.StartTestService(t, nil) 634 t.Cleanup(insecureServer.Stop) 635 636 // Update the resources in the management server to contain no security 637 // configuration. This should result in the use of fallback credentials, 638 // which is insecure in our case. 639 resources = e2e.UpdateOptions{ 640 NodeID: nodeID, 641 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone)}, 642 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, insecureServer.Address)})}, 643 SkipValidation: true, 644 } 645 if err := mgmtServer.Update(ctx, resources); err != nil { 646 t.Fatal(err) 647 } 648 649 // Wait for the connection to move to the new backend that expects 650 // connections without security. 651 for ctx.Err() == nil { 652 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(peer)); err != nil { 653 t.Logf("EmptyCall() failed: %v", err) 654 } 655 if peer.Addr.String() == insecureServer.Address { 656 break 657 } 658 } 659 if ctx.Err() != nil { 660 t.Fatal("Timed out when waiting for connection to switch to second backend") 661 } 662 verifySecurityInformationFromPeer(t, peer, e2e.SecurityLevelNone) 663 } 664 665 // Tests the case where the cds LB policy receives security configuration as 666 // part of the Cluster resource that can be successfully resolved using the 667 // bootstrap file contents. Verifies that the connection between the client and 668 // the server is secure. Subsequently, the cds LB policy receives a cluster 669 // resource that is NACKed by the xDS client. Test verifies that the cds LB 670 // policy continues to use the previous good configuration, but the error from 671 // the xDS client is propagated to the child policy. 672 func (s) TestSecurityConfigUpdate_GoodToBad(t *testing.T) { 673 // Register a wrapped clusterresolver LB policy (child policy of the cds LB 674 // policy) for the duration of this test that makes the resolver error 675 // pushed to it available to the test. 676 _, resolverErrCh, _, _ := registerWrappedClusterResolverPolicy(t) 677 678 // Spin up an xDS management server. 679 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 680 681 // Create bootstrap configuration pointing to the above management server. 682 nodeID := uuid.New().String() 683 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 684 685 // Create a grpc channel with xDS creds talking to a test server with TLS 686 // credentials. 687 cc, serverAddress := setupForSecurityTests(t, bc, xdsClientCredsWithInsecureFallback(t), tlsServerCreds(t)) 688 689 // Configure cluster and endpoints resources in the management server. The 690 // cluster resource is configured to return security configuration. 691 resources := e2e.UpdateOptions{ 692 NodeID: nodeID, 693 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelMTLS)}, 694 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})}, 695 SkipValidation: true, 696 } 697 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 698 defer cancel() 699 if err := mgmtServer.Update(ctx, resources); err != nil { 700 t.Fatal(err) 701 } 702 703 // Verify that a successful RPC can be made over a secure connection. 704 client := testgrpc.NewTestServiceClient(cc) 705 peer := &peer.Peer{} 706 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(peer)); err != nil { 707 t.Fatalf("EmptyCall() failed: %v", err) 708 } 709 verifySecurityInformationFromPeer(t, peer, e2e.SecurityLevelMTLS) 710 711 // Configure cluster and endpoints resources in the management server. The 712 // cluster resource contains security configuration with a certificate 713 // provider instance that is missing in the bootstrap configuration. 714 cluster := e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelNone) 715 cluster.TransportSocket = &v3corepb.TransportSocket{ 716 Name: "envoy.transport_sockets.tls", 717 ConfigType: &v3corepb.TransportSocket_TypedConfig{ 718 TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{ 719 CommonTlsContext: &v3tlspb.CommonTlsContext{ 720 ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{ 721 ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ 722 InstanceName: "unknown-certificate-provider-instance", 723 }, 724 }, 725 }, 726 }), 727 }, 728 } 729 resources = e2e.UpdateOptions{ 730 NodeID: nodeID, 731 Clusters: []*v3clusterpb.Cluster{cluster}, 732 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})}, 733 SkipValidation: true, 734 } 735 if err := mgmtServer.Update(ctx, resources); err != nil { 736 t.Fatal(err) 737 } 738 739 const wantNACKErr = "instance name \"unknown-certificate-provider-instance\" missing in bootstrap configuration" 740 select { 741 case err := <-resolverErrCh: 742 if !strings.Contains(err.Error(), wantNACKErr) { 743 t.Fatalf("Child policy got resolver error: %v, want err: %v", err, wantNACKErr) 744 } 745 case <-ctx.Done(): 746 t.Fatal("Timeout when waiting for resolver error to be pushed to the child policy") 747 } 748 749 // Verify that a successful RPC can be made over a secure connection. 750 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { 751 t.Fatalf("EmptyCall() failed: %v", err) 752 } 753 verifySecurityInformationFromPeer(t, peer, e2e.SecurityLevelMTLS) 754 } 755 756 // Tests the case where the cds LB policy receives security configuration as 757 // part of the Cluster resource that specifies the use system root certs. 758 // Verifies that the connection between the client and the server is secure. 759 func (s) TestSystemRootCertsSecurityConfig(t *testing.T) { 760 origFlag := envconfig.XDSSystemRootCertsEnabled 761 origSRCF := x509SystemCertPoolFunc 762 defer func() { 763 envconfig.XDSSystemRootCertsEnabled = origFlag 764 x509SystemCertPoolFunc = origSRCF 765 }() 766 envconfig.XDSSystemRootCertsEnabled = true 767 768 systemRootCertsFuncCalled := false 769 x509SystemCertPoolFunc = func() (*x509.CertPool, error) { 770 certData, err := os.ReadFile(testdata.Path("x509/server_ca_cert.pem")) 771 if err != nil { 772 return nil, fmt.Errorf("failed to read certificate file: %w", err) 773 } 774 certPool := x509.NewCertPool() 775 776 if ok := certPool.AppendCertsFromPEM(certData); !ok { 777 return nil, fmt.Errorf("failed to append certificate to cert pool") 778 } 779 systemRootCertsFuncCalled = true 780 return certPool, nil 781 } 782 // Spin up an xDS management server. 783 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{}) 784 785 // Create bootstrap configuration pointing to the above management server 786 // and one that includes certificate providers configuration. 787 nodeID := uuid.New().String() 788 bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address) 789 790 // Create a grpc channel with xDS creds talking to a test server with TLS 791 // credentials. 792 cc, serverAddress := setupForSecurityTests(t, bc, xdsClientCredsWithInsecureFallback(t), tlsServerCreds(t)) 793 794 // Configure cluster and endpoints resources in the management server. The 795 // cluster resource is configured to return security configuration. 796 resources := e2e.UpdateOptions{ 797 NodeID: nodeID, 798 Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelTLSWithSystemRootCerts)}, 799 Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})}, 800 SkipValidation: true, 801 } 802 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 803 defer cancel() 804 if err := mgmtServer.Update(ctx, resources); err != nil { 805 t.Fatal(err) 806 } 807 808 // Verify that a successful RPC can be made over a secure connection. 809 client := testgrpc.NewTestServiceClient(cc) 810 peer := &peer.Peer{} 811 if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.Peer(peer)); err != nil { 812 t.Fatalf("EmptyCall() failed: %v", err) 813 } 814 verifySecurityInformationFromPeer(t, peer, e2e.SecurityLevelMTLS) 815 816 if systemRootCertsFuncCalled != true { 817 t.Errorf("System root certs were not used during the test.") 818 } 819 }