cilium/cilium · error

found additional non-CA certificate in chain

Error message

found additional non-CA certificate in chain

What it means

All certificates after the leaf are expected to be CA (intermediate) certificates. If any certificate at position >= 1 is a non-CA end-entity certificate, the chain structure is invalid — a leaf cannot appear mid-chain — so verification aborts rather than attempting a nonsensical path build.

Source

Thrown at pkg/auth/mutual_authhandler.go:293

	for _, chain := range certChains {
		opts := x509.VerifyOptions{
			Roots:         caBundle,
			Intermediates: x509.NewCertPool(),
		}

		if len(chain) == 0 {
			return nil, fmt.Errorf("no certificate chains found")
		}
		leaf := chain[0]
		if leaf.IsCA {
			return nil, fmt.Errorf("leaf certificate cannot be a CA")
		}
		for i := 1; i < len(chain); i++ {
			cert := chain[i]
			if cert.IsCA {
				opts.Intermediates.AddCert(cert)
			} else {
				return nil, fmt.Errorf("found additional non-CA certificate in chain")
			}
		}
		if leaf == nil {
			return nil, fmt.Errorf("no leaf certificate found")
		}
		if _, err := leaf.Verify(opts); err != nil {
			return nil, fmt.Errorf("failed to verify certificate: %w", err)
		}

		if id != nil { // this will be empty in the peer connection
			m.log.Debug("Validating Server SNI", logfields.SNIID, id)
			if valid, err := m.cert.ValidateIdentity(*id, leaf); err != nil {
				return nil, fmt.Errorf("failed to validate SAN: %w", err)
			} else if !valid {
				return nil, fmt.Errorf("unable to validate SAN")
			}
		}

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Rebuild the certificate bundle so it contains only the leaf followed by its CA intermediates
  2. Remove any duplicate or stale leaf certificates from the mounted secret/PEM file
  3. Check the cert-issuing tooling (e.g. cert-manager, mkcert) configuration to see why an extra end-entity cert is included
  4. Restart/redeploy the peer so it picks up the corrected bundle

Example fix

// before: two leaves concatenated
// cert.pem = leaf1 + leaf2 + intermediate
// after: single leaf + intermediates
// cert.pem = leaf1 + intermediate
Defensive patterns

Strategy: validation

Validate before calling

// Reject bundles containing more than one end-entity cert
func validateBundle(pemBytes []byte) error {
    leaves := 0
    for _, c := range parseCerts(pemBytes) {
        if !c.IsCA { leaves++ }
    }
    if leaves > 1 { return fmt.Errorf("bundle has %d leaf certs; keep exactly one", leaves) }
    return nil
}

Type guard

func isLeafFirstChain(chain []*x509.Certificate) bool {
    return len(chain) > 0 && !chain[0].IsCA &&
        func() bool { for _, c := range chain[1:] { if !c.IsCA { return false } }; return true }()
}

Try / catch

exp, err := handler.verifyPeerCertificate(id, caBundle, chains)
if err != nil {
    if strings.Contains(err.Error(), "additional non-CA certificate") {
        return nil, fmt.Errorf("peer's cert bundle contains multiple leaves; rebuild it: %w", err)
    }
    return nil, err
}

Prevention

When it happens

Trigger: The peer concatenated multiple leaf certificates into one chain file (e.g. two serving certs in the same PEM), or rotation tooling appended the old leaf after the new one instead of intermediates.

Common situations: cert-manager or manual `cat` of certs producing a PEM with two end-entity certs; misordered renewal bundles; copy/paste assembly of cert files; upstream cert provider returning extra leaf certs.

Understand the failure class

Related errors


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/39f5be0f71680fad. Report an issue: GitHub.