grpc/grpc-go · error

xDS TLS credentials only support one mode

Error message

xDS TLS credentials only support one mode

What it means

Returned by NewWithMode on the xDS TLS credentials bundle (bundle.go:111). The bundle intentionally implements a single TLS mode and never supports mode switching, as noted in gRFC A65. Any call to NewWithMode is treated as a programming error because the bundle's transport credentials are fixed at construction time.

Solutions

  1. Do not call NewWithMode on the tlscreds bundle; build a fresh bundle via NewBundle(configJSON) for each desired configuration.
  2. If you need two credential configurations, keep two separate *bundle instances and select between them at the call site.
  3. Audit any code path that treats credentials.Bundle polymorphically and special-case or skip NewWithMode for this bundle.

Example fix

// before
b, _, _ := tlscreds.NewBundle(cfg)
newB, err := b.NewWithMode("mtls") // always errors

// after
b, _, _ := tlscreds.NewBundle(cfg) // already the only mode
// use b.TransportCredentials() directly
Defensive patterns

Strategy: validation

Validate before calling

// NewWithMode is unsupported; never call it on a tlscreds bundle.
// If you hold a credentials.Bundle and must branch:
if _, ok := b.(*tlscreds.Bundle); ok { /* skip NewWithMode */ }

Prevention

When it happens

Trigger: Calling bundle.NewWithMode("some-mode") on the bundle returned by tlscreds.NewBundle. The method body is unconditional: it always returns this error.

Common situations: Code that generically iterates credentials.Bundle implementations and invokes NewWithMode (e.g. adapting a bundle meant for xDS fallback or old xdstpb-style mode-based credentials). Also seen when porting examples that assumed a multi-mode bundle.

Understand the failure class

Related errors


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

Appendix: source

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

	return &bundle{
		transportCredentials: &reloadingCreds{provider: provider},
	}, sync.OnceFunc(func() { provider.Close() }), nil
}

func (t *bundle) TransportCredentials() credentials.TransportCredentials {
	return t.transportCredentials
}

func (t *bundle) PerRPCCredentials() credentials.PerRPCCredentials {
	// mTLS provides transport credentials only. There are no per-RPC
	// credentials.
	return nil
}

func (t *bundle) NewWithMode(string) (credentials.Bundle, error) {
	// This bundle has a single mode which only uses TLS transport credentials,
	// so there is no legitimate case where callers would call NewWithMode.
	return nil, fmt.Errorf("xDS TLS credentials only support one mode")
}

// reloadingCreds is a credentials.TransportCredentials for client
// side mTLS that reloads the server root CA certificate and the client
// certificates from the provider on every client handshake. This is necessary
// because the standard TLS credentials do not support reloading CA
// certificates.
type reloadingCreds struct {
	provider certprovider.Provider
}

func (c *reloadingCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
	km, err := c.provider.KeyMaterial(ctx)
	if err != nil {
		return nil, nil, err
	}
	var config *tls.Config
	if km.SPIFFEBundleMap != nil {

View on GitHub (pinned to 0c51461d27)