-
Notifications
You must be signed in to change notification settings - Fork 4.5k
xds: don't fail channel/server startup when xds creds is specified, but bootstrap is missing certificate providers #6848
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
b7dc2dc
xds: don't fail channel/server startup when xds creds is specified, b…
easwars a1143b6
make vet happy
easwars 6d26f6f
e2e style tests for client side
easwars 99c6171
e2e style tests for server side
easwars 55f51d4
make vet happy
easwars e76c29e
review comments pass 1
easwars 25050b6
review comments pass 2
easwars File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,362 @@ | ||
/* | ||
* | ||
* Copyright 2023 gRPC authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
|
||
package xds_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/google/uuid" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/connectivity" | ||
"google.golang.org/grpc/credentials/insecure" | ||
xdscreds "google.golang.org/grpc/credentials/xds" | ||
"google.golang.org/grpc/internal" | ||
"google.golang.org/grpc/internal/stubserver" | ||
"google.golang.org/grpc/internal/testutils" | ||
"google.golang.org/grpc/internal/testutils/xds/bootstrap" | ||
"google.golang.org/grpc/internal/testutils/xds/e2e" | ||
"google.golang.org/grpc/peer" | ||
"google.golang.org/grpc/resolver" | ||
"google.golang.org/grpc/status" | ||
|
||
v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" | ||
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" | ||
v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" | ||
v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" | ||
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" | ||
v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" | ||
testgrpc "google.golang.org/grpc/interop/grpc_testing" | ||
testpb "google.golang.org/grpc/interop/grpc_testing" | ||
) | ||
|
||
// Tests the case where the bootstrap configuration contains no certificate | ||
// providers, and xDS credentials with an insecure fallback is specified at dial | ||
// time. The management server is configured to return client side xDS resources | ||
// with no security configuration. The test verifies that the gRPC client is | ||
// able to make RPCs to the backend which is configured to accept plaintext | ||
// connections. This ensures that the insecure fallback credentials are getting | ||
// used on the client. | ||
func (s) TestClientSideXDS_WithNoCertificateProvidersInBootstrap_Success(t *testing.T) { | ||
// Spin up an xDS management server. | ||
mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{}) | ||
if err != nil { | ||
t.Fatalf("Failed to start management server: %v", err) | ||
} | ||
defer mgmtServer.Stop() | ||
|
||
// Create bootstrap configuration with no certificate providers. | ||
nodeID := uuid.New().String() | ||
bs, err := bootstrap.Contents(bootstrap.Options{ | ||
NodeID: nodeID, | ||
ServerURI: mgmtServer.Address, | ||
}) | ||
if err != nil { | ||
t.Fatalf("Failed to create bootstrap configuration: %v", err) | ||
} | ||
|
||
// Create an xDS resolver with the above bootstrap configuration. | ||
newResolver := internal.NewXDSResolverWithConfigForTesting | ||
if newResolver == nil { | ||
t.Fatal("internal.NewXDSResolverWithConfigForTesting is unset") | ||
} | ||
resolverBuilder, err := newResolver.(func([]byte) (resolver.Builder, error))(bs) | ||
if err != nil { | ||
t.Fatalf("Failed to create xDS resolver for testing: %v", err) | ||
} | ||
|
||
// Spin up a test backend. | ||
server := stubserver.StartTestService(t, nil) | ||
defer server.Stop() | ||
|
||
// Configure client side xDS resources on the management server, with no | ||
// security configuration in the Cluster resource. | ||
const serviceName = "my-service-client-side-xds" | ||
resources := e2e.DefaultClientResources(e2e.ResourceParams{ | ||
DialTarget: serviceName, | ||
NodeID: nodeID, | ||
Host: "localhost", | ||
Port: testutils.ParsePort(t, server.Address), | ||
SecLevel: e2e.SecurityLevelNone, | ||
}) | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) | ||
defer cancel() | ||
if err := mgmtServer.Update(ctx, resources); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Create client-side xDS credentials with an insecure fallback. | ||
creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Create a ClientConn and make a successful RPC. | ||
cc, err := grpc.Dial(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(creds), grpc.WithResolvers(resolverBuilder)) | ||
if err != nil { | ||
t.Fatalf("failed to dial local test server: %v", err) | ||
} | ||
defer cc.Close() | ||
|
||
client := testgrpc.NewTestServiceClient(cc) | ||
if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true)); err != nil { | ||
t.Fatalf("EmptyCall() failed: %v", err) | ||
} | ||
} | ||
|
||
// Tests the case where the bootstrap configuration contains no certificate | ||
// providers, and xDS credentials with an insecure fallback is specified at dial | ||
// time. The management server is configured to return client side xDS resources | ||
// with an mTLS security configuration. The test verifies that the gRPC client | ||
// moves to TRANSIENT_FAILURE and rpcs fail with the expected error code and | ||
// string. This ensures that when the certificate provider instance name | ||
// specified in the security configuration is not present in the bootstrap, | ||
// channel creation does not fail, but it moves to TRANSIENT_FAILURE and | ||
// subsequent rpcs fail. | ||
func (s) TestClientSideXDS_WithNoCertificateProvidersInBootstrap_Failure(t *testing.T) { | ||
// Spin up an xDS management server. | ||
mgmtServer, err := e2e.StartManagementServer(e2e.ManagementServerOptions{}) | ||
if err != nil { | ||
t.Fatalf("Failed to start management server: %v", err) | ||
} | ||
defer mgmtServer.Stop() | ||
|
||
// Create bootstrap configuration with no certificate providers. | ||
nodeID := uuid.New().String() | ||
bs, err := bootstrap.Contents(bootstrap.Options{ | ||
NodeID: nodeID, | ||
ServerURI: mgmtServer.Address, | ||
}) | ||
if err != nil { | ||
t.Fatalf("Failed to create bootstrap configuration: %v", err) | ||
} | ||
|
||
// Create an xDS resolver with the above bootstrap configuration. | ||
newResolver := internal.NewXDSResolverWithConfigForTesting | ||
if newResolver == nil { | ||
t.Fatal("internal.NewXDSResolverWithConfigForTesting is unset") | ||
} | ||
resolverBuilder, err := newResolver.(func([]byte) (resolver.Builder, error))(bs) | ||
if err != nil { | ||
t.Fatalf("Failed to create xDS resolver for testing: %v", err) | ||
} | ||
|
||
// Spin up a test backend. | ||
server := stubserver.StartTestService(t, nil) | ||
defer server.Stop() | ||
|
||
// Configure client side xDS resources on the management server, with mTLS | ||
// security configuration in the Cluster resource. | ||
const serviceName = "my-service-client-side-xds" | ||
const clusterName = "cluster-" + serviceName | ||
const endpointsName = "endpoints-" + serviceName | ||
resources := e2e.DefaultClientResources(e2e.ResourceParams{ | ||
DialTarget: serviceName, | ||
NodeID: nodeID, | ||
Host: "localhost", | ||
Port: testutils.ParsePort(t, server.Address), | ||
SecLevel: e2e.SecurityLevelNone, | ||
}) | ||
resources.Clusters = []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, endpointsName, e2e.SecurityLevelMTLS)} | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) | ||
defer cancel() | ||
if err := mgmtServer.Update(ctx, resources); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Create client-side xDS credentials with an insecure fallback. | ||
creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Create a ClientConn and ensure that it moves to TRANSIENT_FAILURE. | ||
cc, err := grpc.Dial(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(creds), grpc.WithResolvers(resolverBuilder)) | ||
if err != nil { | ||
t.Fatalf("failed to dial local test server: %v", err) | ||
} | ||
defer cc.Close() | ||
testutils.AwaitState(ctx, t, cc, connectivity.TransientFailure) | ||
|
||
// Make an RPC and ensure that expected error is returned. | ||
wantErr := fmt.Sprintf("identitiy certificate provider instance name %q missing in bootstrap configuration", e2e.ClientSideCertProviderInstance) | ||
client := testgrpc.NewTestServiceClient(cc) | ||
if _, err := client.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.Unavailable || !strings.Contains(err.Error(), wantErr) { | ||
t.Fatalf("EmptyCall() failed: %v, wantCode: %s, wantErr: %s", err, codes.Unavailable, wantErr) | ||
} | ||
} | ||
|
||
// Tests the case where the bootstrap configuration contains one certificate | ||
// provider, and xDS credentials with an insecure fallback is specified at dial | ||
// time. The management server responds with three clusters: | ||
// 1. contains valid security configuration pointing to the certificate provider | ||
// instance specified in the bootstrap | ||
// 2. contains no security configuration, hence should use insecure fallback | ||
// 3. contains invalid security configuration pointing to a non-existent | ||
// certificate provider instance | ||
// | ||
// The test verifies that RPCs to the first two clusters succeed, while RPCs to | ||
// the third cluster fails with an appropriate code and error message. | ||
func (s) TestClientSideXDS_WithValidAndInvalidSecurityConfiguration(t *testing.T) { | ||
// Spin up an xDS management server. This uses a bootstrap config with a | ||
// certificate provider instance name e2e.ClientSideCertProviderInstance. | ||
mgmtServer, nodeID, _, resolver, cleanup := e2e.SetupManagementServer(t, e2e.ManagementServerOptions{AllowResourceSubset: true}) | ||
defer cleanup() | ||
|
||
// Create test backends for all three clusters | ||
// backend1 configured with TLS creds, represents cluster1 | ||
// backend2 configured with insecure creds, represents cluster2 | ||
// backend3 configured with insecure creds, represents cluster3 | ||
creds := e2e.CreateServerTLSCredentials(t) | ||
server1 := stubserver.StartTestService(t, nil, grpc.Creds(creds)) | ||
defer server1.Stop() | ||
server2 := stubserver.StartTestService(t, nil) | ||
defer server2.Stop() | ||
server3 := stubserver.StartTestService(t, nil) | ||
defer server3.Stop() | ||
|
||
// Configure client side xDS resources on the management server. | ||
const serviceName = "my-service-client-side-xds" | ||
const routeConfigName = "route-" + serviceName | ||
const clusterName1 = "cluster1-" + serviceName | ||
const clusterName2 = "cluster2-" + serviceName | ||
const clusterName3 = "cluster3-" + serviceName | ||
const endpointsName1 = "endpoints1-" + serviceName | ||
const endpointsName2 = "endpoints2-" + serviceName | ||
const endpointsName3 = "endpoints3-" + serviceName | ||
listeners := []*v3listenerpb.Listener{e2e.DefaultClientListener(serviceName, routeConfigName)} | ||
// Route configuration: | ||
// - "/grpc.testing.TestService/EmptyCall" --> cluster1 | ||
// - "/grpc.testing.TestService/UnaryCall" --> cluster2 | ||
// - "/grpc.testing.TestService/FullDuplexCall" --> cluster3 | ||
routes := []*v3routepb.RouteConfiguration{{ | ||
Name: routeConfigName, | ||
VirtualHosts: []*v3routepb.VirtualHost{{ | ||
Domains: []string{serviceName}, | ||
Routes: []*v3routepb.Route{ | ||
{ | ||
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/EmptyCall"}}, | ||
Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ | ||
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName1}, | ||
}}, | ||
}, | ||
{ | ||
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/UnaryCall"}}, | ||
Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ | ||
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName2}, | ||
}}, | ||
}, | ||
{ | ||
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/grpc.testing.TestService/FullDuplexCall"}}, | ||
Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{ | ||
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName3}, | ||
}}, | ||
}, | ||
}, | ||
}}, | ||
}} | ||
// Clusters: | ||
// - cluster1 with cert provider name e2e.ClientSideCertProviderInstance. | ||
// - cluster2 with no security configuration. | ||
// - cluster3 with non-existent cert provider name. | ||
clusters := []*v3clusterpb.Cluster{ | ||
e2e.DefaultCluster(clusterName1, endpointsName1, e2e.SecurityLevelMTLS), | ||
e2e.DefaultCluster(clusterName2, endpointsName2, e2e.SecurityLevelNone), | ||
func() *v3clusterpb.Cluster { | ||
cluster3 := e2e.DefaultCluster(clusterName3, endpointsName3, e2e.SecurityLevelMTLS) | ||
cluster3.TransportSocket = &v3corepb.TransportSocket{ | ||
Name: "envoy.transport_sockets.tls", | ||
ConfigType: &v3corepb.TransportSocket_TypedConfig{ | ||
TypedConfig: testutils.MarshalAny(t, &v3tlspb.UpstreamTlsContext{ | ||
CommonTlsContext: &v3tlspb.CommonTlsContext{ | ||
ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{ | ||
ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ | ||
InstanceName: "non-existent-certificate-provider-instance-name", | ||
}, | ||
}, | ||
TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{ | ||
InstanceName: "non-existent-certificate-provider-instance-name", | ||
}, | ||
}, | ||
}), | ||
}, | ||
} | ||
return cluster3 | ||
}(), | ||
} | ||
// Endpoints for each of the above clusters with backends created earlier. | ||
endpoints := []*v3endpointpb.ClusterLoadAssignment{ | ||
e2e.DefaultEndpoint(endpointsName1, "localhost", []uint32{testutils.ParsePort(t, server1.Address)}), | ||
e2e.DefaultEndpoint(endpointsName2, "localhost", []uint32{testutils.ParsePort(t, server2.Address)}), | ||
} | ||
resources := e2e.UpdateOptions{ | ||
NodeID: nodeID, | ||
Listeners: listeners, | ||
Routes: routes, | ||
Clusters: clusters, | ||
Endpoints: endpoints, | ||
SkipValidation: true, | ||
} | ||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) | ||
defer cancel() | ||
if err := mgmtServer.Update(ctx, resources); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Create client-side xDS credentials with an insecure fallback. | ||
creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Create a ClientConn. | ||
cc, err := grpc.Dial(fmt.Sprintf("xds:///%s", serviceName), grpc.WithTransportCredentials(creds), grpc.WithResolvers(resolver)) | ||
if err != nil { | ||
t.Fatalf("failed to dial local test server: %v", err) | ||
} | ||
defer cc.Close() | ||
|
||
// Make an RPC to be routed to cluster1 and verify that it succeeds. | ||
client := testgrpc.NewTestServiceClient(cc) | ||
peer := &peer.Peer{} | ||
if _, err := client.EmptyCall(ctx, &testpb.Empty{}, grpc.WaitForReady(true), grpc.Peer(peer)); err != nil { | ||
t.Fatalf("EmptyCall() failed: %v", err) | ||
} | ||
if got, want := peer.Addr.String(), server1.Address; got != want { | ||
t.Errorf("EmptyCall() routed to %q, want to be routed to: %q", got, want) | ||
|
||
} | ||
|
||
// Make an RPC to be routed to cluster2 and verify that it succeeds. | ||
if _, err := client.UnaryCall(ctx, &testpb.SimpleRequest{}, grpc.Peer(peer)); err != nil { | ||
t.Fatalf("UnaryCall() failed: %v", err) | ||
} | ||
if got, want := peer.Addr.String(), server2.Address; got != want { | ||
t.Errorf("EmptyCall() routed to %q, want to be routed to: %q", got, want) | ||
} | ||
|
||
// Make an RPC to be routed to cluster3 and verify that it fails. | ||
const wantErr = `identitiy certificate provider instance name "non-existent-certificate-provider-instance-name" missing in bootstrap configuration` | ||
if _, err := client.FullDuplexCall(ctx); status.Code(err) != codes.Unavailable || !strings.Contains(err.Error(), wantErr) { | ||
t.Fatalf("FullDuplexCall failed: %v, wantCode: %s, wantErr: %s", err, codes.Unavailable, wantErr) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.