grpc/grpc-go · error

xds: CertificateProvider to fetch trusted roots is missing…

Error message

xds: CertificateProvider to fetch trusted roots is missing, cannot perform TLS handshake. Please check configuration on the management server

What it means

Returned by clientSideTLSConfigInternal when hi.rootProvider is nil. On the client side, the root certificate provider is mandatory—it supplies the trusted CA certificates needed to validate the server's certificate chain. Without it, the TLS handshake cannot verify the peer, so gRPC refuses to proceed. The error message directs you to the management server because root provider configuration originates from the xDS DownstreamTLSContext's validation context.

Solutions

  1. On the xDS management server, add a validation context (CertificateValidationContext with trusted_ca) to the DownstreamTLSContext for the client cluster.
  2. Verify the xDS LDS resource includes both CommonTlsContext.certificates (identity) and CommonTlsContext.validation_context (trusted roots).
  3. Use grpc.WithTransportCredentials with fallback credentials if xDS TLS is not strictly required, so the connection falls back when xDS config is incomplete.
  4. Check xDS config dump (e.g., istioctl proxy-config cluster) to confirm the security policy has a validation context.

Example fix

// On the xDS management server (e.g., Istio DestinationRule):
// before: missing validation context
// after:
tls:
  mode: MUTUAL
  clientCertificate: /etc/certs/client.pem
  privateKey: /etc/certs/client.key
  caCertificates: /etc/certs/ca-cert.pem  # <-- this provides the root provider
Defensive patterns

Strategy: validation

Validate before calling

// Validate xDS security config has a validation context before relying on xDS TLS.
// This is config-side validation on the management server.
// Ensure DownstreamTLSContext has a CertificateValidationContext with trusted_ca.
// Use istioctl proxy-config to dump and inspect the LDS resource:
//   istioctl proxy-config listener <pod> -o json | jq '...validation_context...'

Try / catch

// Check the RPC error and log whether root provider is the issue.
err := client.Call(ctx, req)
if err != nil && strings.Contains(err.Error(), "fetch trusted roots is missing") {
    log.Error("xDS security config is missing root cert provider; check management server")
}

Prevention

When it happens

Trigger: Client-side xDS TLS handshake where the HandshakeInfo exists and is not fallback (UseFallbackCreds() is false) but rootProvider was never set—i.e., NewHandshakeInfo was called with a nil rootProvider. This happens when the xDS security config has an identity provider but no validation/trusted-roots context.

Common situations: xDS DownstreamTLSContext is configured with a certificate provider for client identity (mTLS) but omits the validation context (trusted CA); xDS management server sends a partially-populated security policy; misconfigured Istio/Envoy PeerAuthentication or DestinationRule that sets client cert but not CA.

Understand the failure class

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/dd9e5ad818f5f0e4. Report an issue: GitHub.

Appendix: source

Thrown at internal/credentials/xds/handshake_info.go:209

// GetSANMatchersForTesting returns the SAN matchers stored in HandshakeInfo.
// To be used only for testing purposes.
func (hi *HandshakeInfo) GetSANMatchersForTesting() []matcher.StringMatcher {
	return append([]matcher.StringMatcher{}, hi.sanMatchers...)
}

// clientSideTLSConfigInternal constructs a tls.Config to be used in a
// client-side handshake based on the contents of the HandshakeInfo.
//
// hostname is passed as a parameter here instead of being part of the
// HandshakeInfo because HandshakeInfo contains cluster-level security
// configuration that applies to all endpoints in the cluster, while hostname is
// specific to each endpoint. This allows sharing a single HandshakeInfo
// instance across multiple endpoints in the same cluster.
func (hi *HandshakeInfo) clientSideTLSConfigInternal(ctx context.Context, hostname string) (*tls.Config, error) {
	// On the client side, rootProvider is mandatory. IdentityProvider is
	// optional based on whether the client is doing TLS or mTLS.
	if hi.rootProvider == nil {
		return nil, errors.New("xds: CertificateProvider to fetch trusted roots is missing, cannot perform TLS handshake. Please check configuration on the management server")
	}

	// InsecureSkipVerify needs to be set to true because we need to perform
	// custom verification to check the SAN on the received certificate.
	// Currently the Go stdlib does complete verification of the cert (which
	// includes hostname verification) or none. We are forced to go with the
	// latter and perform the normal cert validation ourselves.
	cfg := &tls.Config{
		InsecureSkipVerify: true,
		NextProtos:         []string{"h2"},
	}

	km, err := hi.rootProvider.KeyMaterial(ctx)
	if err != nil {
		return nil, fmt.Errorf("xds: fetching trusted roots from CertificateProvider failed: %v", err)
	}
	cfg.RootCAs = km.Roots

View on GitHub (pinned to 0c51461d27)