grpc/grpc-go · error

server handshake is not supported by xDS client TLS credenti

Error message

server handshake is not supported by xDS client TLS credentials

What it means

The `reloadingCreds` in internal/xds/bootstrap/tlscreds/bundle.go are client-side-only mTLS credentials (gRFC A65). They implement ClientHandshake but explicitly reject ServerHandshake at bundle.go:157, returning this error. The bundle is designed to authenticate a gRPC client to an xDS management server, not to terminate TLS on an incoming connection.

Source

Thrown at internal/xds/bootstrap/tlscreds/bundle.go:157

		}
	}
	return credentials.NewTLS(config).ClientHandshake(ctx, authority, rawConn)
}

func (c *reloadingCreds) Info() credentials.ProtocolInfo {
	return credentials.ProtocolInfo{SecurityProtocol: "tls"}
}

func (c *reloadingCreds) Clone() credentials.TransportCredentials {
	return &reloadingCreds{provider: c.provider}
}

func (c *reloadingCreds) OverrideServerName(string) error {
	return errors.New("overriding server name is not supported by xDS client TLS credentials")
}

func (c *reloadingCreds) ServerHandshake(net.Conn) (net.Conn, credentials.AuthInfo, error) {
	return nil, nil, errors.New("server handshake is not supported by xDS client TLS credentials")
}

func buildSPIFFEVerifyFunc(spiffeBundleMap map[string]*spiffebundle.Bundle) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
	return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
		rawCertList := make([]*x509.Certificate, len(rawCerts))
		for i, asn1Data := range rawCerts {
			cert, err := x509.ParseCertificate(asn1Data)
			if err != nil {
				return fmt.Errorf("spiffe: verify function could not parse input certificate: %v", err)
			}
			rawCertList[i] = cert
		}
		if len(rawCertList) == 0 {
			return fmt.Errorf("spiffe: verify function has no valid input certificates")
		}
		leafCert := rawCertList[0]
		roots, err := spiffe.GetRootsFromSPIFFEBundleMap(spiffeBundleMap, leafCert)
		if err != nil {

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Use separate, server-appropriate transport credentials (e.g. `credentials.NewTLS` with a `tls.Config` holding `Certificates`) for any grpc.Server.
  2. Do not pass the xDS-bootstrap bundle's TransportCredentials to grpc.NewServer — it is client-only.
  3. Audit code paths that hand the bootstrap bundle to any server constructor or to a Listener-wrapping helper.

Example fix

// before (wrong)
bundle, _, _ := tlscreds.NewBundle(cfg)
srv := grpc.NewServer(grpc.Creds(bundle.TransportCredentials()))
// -> server handshake is not supported ...

// after
serverTLS := credentials.NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}})
srv := grpc.NewServer(grpc.Creds(serverTLS))
Defensive patterns

Strategy: validation

Validate before calling

// Reject bootstrap creds before they reach a server constructor.
func isClientOnlyCreds(c credentials.TransportCredentials) bool {
    return strings.Contains(fmt.Sprintf("%T", c), "reloadingCreds")
}

func newServer(creds credentials.TransportCredentials) (*grpc.Server, error) {
    if isClientOnlyCreds(creds) {
        return nil, errors.New("xDS bootstrap creds cannot be used on a grpc.Server")
    }
    return grpc.NewServer(grpc.Creds(creds)), nil
}

Prevention

When it happens

Trigger: Triggered when the xDS-bootstrap TLS credentials are mistakenly used as server-side transport credentials (e.g. passed to `grpc.Creds()` on a `grpc.NewServer`). When the server attempts to accept a connection it calls ServerHandshake, which hits bundle.go:156-158 and returns the error.

Common situations: Accidentally passing the same `credentials.Bundle` returned by `tlscreds.NewBundle` to both the gRPC client and a gRPC server; copy-paste of credentials wiring from a client into a server constructor; using xDS bootstrap creds for an inbound listener.

Understand the failure class

Related errors


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