grpc/grpc-go · error

failed to unmarshal config

Error message

failed to unmarshal config: %v

What it means

Returned by tlscreds.NewBundle when json.Unmarshal of the mTLS channel-creds config fails. The expected shape is a JSON object with optional string fields certificate_file, ca_certificate_file, private_key_file, spiffe_trust_bundle_map_file.

Solutions

  1. Make the channel_creds config a JSON object with string-valued file fields.
  2. Use only the documented field names: certificate_file, private_key_file, ca_certificate_file, spiffe_trust_bundle_map_file.
  3. Validate the surrounding channel_creds entry shape {type, config}.
  4. Run the bootstrap through jq to catch syntax errors.

Example fix

// before
{"type":"tlscreds_mtls","config":["/etc/certs/client.crt"]}

// after
{"type":"tlscreds_mtls","config":{"certificate_file":"/etc/certs/client.crt","private_key_file":"/etc/certs/client.key","ca_certificate_file":"/etc/certs/ca.crt"}}
Defensive patterns

Strategy: validation

Validate before calling

// Validate the tlscreds_mtls config object shape.
func validateTLSCredsConfig(raw json.RawMessage) error {
    var cfg struct {
        CertificateFile          string `json:"certificate_file"`
        CACertificateFile        string `json:"ca_certificate_file"`
        PrivateKeyFile           string `json:"private_key_file"`
        SPIFFETrustBundleMapFile string `json:"spiffe_trust_bundle_map_file"`
    }
    if err := json.Unmarshal(raw, &cfg); err != nil {
        return fmt.Errorf("tlscreds config must be a JSON object: %w", err)
    }
    return nil
}

Try / catch

if _, _, err := tlscreds.NewBundle(jd); err != nil {
    if strings.Contains(err.Error(), "failed to unmarshal config") {
        // reshape the channel_creds config to a JSON object.
    }
}

Prevention

When it happens

Trigger: Triggered at bundle.go:64 when json.Unmarshal(jd, cfg) errors. Typically the channel_creds config value is not a JSON object, or one of the file-path fields has a non-string type.

Common situations: config set to a bare string or array instead of an object; a path field set to a number/boolean; typo in a field name leaving a malformed value; trailing comma inside the config block.

Related errors


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

Appendix: source

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

}

// NewBundle returns a credentials.Bundle which implements mTLS Credentials in xDS
// Bootstrap File. It delegates certificate loading to a file_watcher provider
// if either client certificates or server root CA is specified. The second
// return value is a close func that should be called when the caller no longer
// needs this bundle.
// See gRFC A65: github.com/grpc/proposal/blob/master/A65-xds-mtls-creds-in-bootstrap.md
func NewBundle(jd json.RawMessage) (credentials.Bundle, func(), error) {
	cfg := &struct {
		CertificateFile          string `json:"certificate_file"`
		CACertificateFile        string `json:"ca_certificate_file"`
		PrivateKeyFile           string `json:"private_key_file"`
		SPIFFETrustBundleMapFile string `json:"spiffe_trust_bundle_map_file"`
	}{}

	if jd != nil {
		if err := json.Unmarshal(jd, cfg); err != nil {
			return nil, nil, fmt.Errorf("failed to unmarshal config: %v", err)
		}
	} // Else the config field is absent. Treat it as an empty config.

	if !envconfig.XDSSPIFFEEnabled {
		cfg.SPIFFETrustBundleMapFile = ""
	}
	if cfg.CACertificateFile == "" && cfg.CertificateFile == "" && cfg.PrivateKeyFile == "" && cfg.SPIFFETrustBundleMapFile == "" {
		// We cannot use (and do not need) a file_watcher provider in this case,
		// and can simply directly use the TLS transport credentials.
		// Quoting A65:
		//
		// > The only difference between the file-watcher certificate provider
		// > config and this one is that in the file-watcher certificate
		// > provider, at least one of the "certificate_file" or
		// > "ca_certificate_file" fields must be specified, whereas in this
		// > configuration, it is acceptable to specify neither one.
		// Further, with the introduction of SPIFFE Trust Map support, we also
		// check for this value.

View on GitHub (pinned to 0c51461d27)