Skip to content

Commit

Permalink
Add support for using system root certs in xds
Browse files Browse the repository at this point in the history
  • Loading branch information
arjan-bal committed Jan 16, 2025
1 parent 130c1d7 commit 67db837
Show file tree
Hide file tree
Showing 9 changed files with 570 additions and 25 deletions.
5 changes: 5 additions & 0 deletions internal/envconfig/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ var (
// TODO: https://github.com/grpc/grpc-go/issues/7866 - Control this using
// an env variable when all LB policies handle endpoints.
XDSDualstackEndpointsEnabled = false

// XDSSystemRootCertsEnabled when xDs clients can use use the system's
// default root certificates for TLS certificate validation. Form more
// details, see gRFC A82.
XDSSystemRootCertsEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_SYSTEM_ROOT_CERTS", false)
)
18 changes: 18 additions & 0 deletions internal/testutils/xds/e2e/clientresources.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ const (
// mTLS is required. Both client and server present identity certificates in
// this configuration.
SecurityLevelMTLS
// SecurityLevelMTLSWithSystemRootCerts is used when security configuration
// corresponding to mTLS is required and the client needs to use system root
// certs to validate the server certificate.. Both client and server present
// identity certificates in this configuration.
SecurityLevelMTLSWithSystemRootCerts
)

// ResourceParams wraps the arguments to be passed to DefaultClientResources.
Expand Down Expand Up @@ -593,6 +598,19 @@ func ClusterResourceWithOptions(opts ClusterOptions) *v3clusterpb.Cluster {
},
},
}
case SecurityLevelMTLSWithSystemRootCerts:
tlsContext = &v3tlspb.UpstreamTlsContext{
CommonTlsContext: &v3tlspb.CommonTlsContext{
ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
ValidationContext: &v3tlspb.CertificateValidationContext{
SystemRootCerts: &v3tlspb.CertificateValidationContext_SystemRootCerts{},
},
},
TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
InstanceName: ClientSideCertProviderInstance,
},
},
}
}

var lbPolicy v3clusterpb.Cluster_LbPolicy
Expand Down
30 changes: 27 additions & 3 deletions xds/internal/balancer/cdsbalancer/cdsbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package cdsbalancer

import (
"context"
"crypto/x509"
"encoding/json"
"fmt"
"sync/atomic"
Expand Down Expand Up @@ -63,6 +64,9 @@ var (
return builder.Build(cc, opts), nil
}
buildProvider = buildProviderFunc

// systemRootCertsFunc is used for mocking the system cert pool for tests.
systemRootCertsFunc = x509.SystemCertPool
)

func init() {
Expand Down Expand Up @@ -208,9 +212,15 @@ func (b *cdsBalancer) handleSecurityConfig(config *xdsresource.SecurityConfig) e

// A root provider is required whether we are using TLS or mTLS.
cpc := b.xdsClient.BootstrapConfig().CertProviderConfigs()
rootProvider, err := buildProvider(cpc, config.RootInstanceName, config.RootCertName, false, true)
if err != nil {
return err
var rootProvider certprovider.Provider
if config.UseSystemRootCerts {
rootProvider = systemRootCertsProvider{}
} else {
rp, err := buildProvider(cpc, config.RootInstanceName, config.RootCertName, false, true)
if err != nil {
return err
}
rootProvider = rp
}

// The identity provider is only present when using mTLS.
Expand Down Expand Up @@ -669,3 +679,17 @@ func (ccw *ccWrapper) UpdateAddresses(sc balancer.SubConn, addrs []resolver.Addr
}
ccw.ClientConn.UpdateAddresses(sc, newAddrs)
}

type systemRootCertsProvider struct{}

func (systemRootCertsProvider) Close() {}

func (systemRootCertsProvider) KeyMaterial(_ context.Context) (*certprovider.KeyMaterial, error) {
rootCAs, err := systemRootCertsFunc()
if err != nil {
return nil, err
}
return &certprovider.KeyMaterial{
Roots: rootCAs,
}, nil
}
67 changes: 67 additions & 0 deletions xds/internal/balancer/cdsbalancer/cdsbalancer_security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/balancer/stub"
xdscredsinternal "google.golang.org/grpc/internal/credentials/xds"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/stubserver"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/internal/testutils/xds/e2e"
Expand Down Expand Up @@ -748,3 +749,69 @@ func (s) TestSecurityConfigUpdate_GoodToBad(t *testing.T) {
}
verifySecurityInformationFromPeer(t, peer, e2e.SecurityLevelMTLS)
}

// Tests the case where the cds LB policy receives security configuration as
// part of the Cluster resource that specifies the use system root certs.
// Verifies that the connection between the client and the server is secure.
func (s) TestSystemRootCertsSecurityConfig(t *testing.T) {
origFlag := envconfig.XDSSystemRootCertsEnabled
origSRCF := systemRootCertsFunc
defer func() {
envconfig.XDSSystemRootCertsEnabled = origFlag
systemRootCertsFunc = origSRCF
}()
envconfig.XDSSystemRootCertsEnabled = true
systemRootCertsFunc = origSRCF

systemRootCertsFuncCalled := false
systemRootCertsFunc = func() (*x509.CertPool, error) {
certData, err := os.ReadFile(testdata.Path("x509/server_ca_cert.pem"))
if err != nil {
return nil, fmt.Errorf("failed to read certificate file: %w", err)
}
certPool := x509.NewCertPool()

if ok := certPool.AppendCertsFromPEM(certData); !ok {
return nil, fmt.Errorf("failed to append certificate to cert pool")
}
systemRootCertsFuncCalled = true
return certPool, nil
}
// Spin up an xDS management server.
mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{})

// Create bootstrap configuration pointing to the above management server
// and one that includes certificate providers configuration.
nodeID := uuid.New().String()
bc := e2e.DefaultBootstrapContents(t, nodeID, mgmtServer.Address)

// Create a grpc channel with xDS creds talking to a test server with TLS
// credentials.
cc, serverAddress := setupForSecurityTests(t, bc, xdsClientCredsWithInsecureFallback(t), tlsServerCreds(t))

// Configure cluster and endpoints resources in the management server. The
// cluster resource is configured to return security configuration.
resources := e2e.UpdateOptions{
NodeID: nodeID,
Clusters: []*v3clusterpb.Cluster{e2e.DefaultCluster(clusterName, serviceName, e2e.SecurityLevelMTLSWithSystemRootCerts)},
Endpoints: []*v3endpointpb.ClusterLoadAssignment{e2e.DefaultEndpoint(serviceName, "localhost", []uint32{testutils.ParsePort(t, serverAddress)})},
SkipValidation: true,
}
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if err := mgmtServer.Update(ctx, resources); err != nil {
t.Fatal(err)
}

// Verify that a successful RPC can be made over a secure connection.
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)
}
verifySecurityInformationFromPeer(t, peer, e2e.SecurityLevelMTLS)

if systemRootCertsFuncCalled != true {
t.Errorf("systemRootCertsFuncCalled = %t, want = true", systemRootCertsFuncCalled)
}
}
Loading

0 comments on commit 67db837

Please sign in to comment.