grpc/grpc-go · error

xds: received SANs do not match any of the accepted SANs

Error message

xds: received SANs {DNSNames: %v, EmailAddresses: %v, IPAddresses: %v, URIs: %v} do not match any of the accepted SANs

What it means

During an xDS-managed TLS handshake, the gRPC client/server compares the Subject Alternative Names (SANs) on the peer's leaf certificate against the SAN matchers that the xDS control plane delivered via the security policy. If none of the peer certificate's DNSNames, EmailAddresses, IPAddresses, or URIs satisfy any configured matcher, the handshake is aborted at handshake_info.go:318 inside the custom verification callback. This is a trust/SAN-mismatch failure specific to xDS mTLS (not standard Go x509 verification).

Solutions

  1. Inspect the received SANs printed in the error (DNSNames, EmailAddresses, IPAddresses, URIs) and compare them against the SAN matchers defined in your xDS security policy (e.g. Istio PeerAuthentication / RequestAuthentication / DestinationRule).
  2. Update the security policy on the management server so at least one matcher accepts an identity the peer actually presents (e.g. add the SPIFFE URI spiffe://<trust-domain>/<ns>/<sa> or the correct DNS name).
  3. Verify the workload's certificate was issued by the expected CA and contains the expected SANs (e.g. istioctl proxy-config secret <pod>.<ns> --validate or openssl x509 -text on the leaf).
  4. Confirm the control plane has propagated the updated security policy to the client (check xDS ACK/NACK and config version) and retry the RPC.

Example fix

// before: xDS security policy only allows
//   spiffe://example.org/old-ns/old-sa
// but the pod now runs as ns=payments, sa=worker

// after (Istio PeerAuthentication / DestinationRule):
apiVersion: security.istio.io/v1
kind: DestinationRule
metadata:
  name: allow-new-identity
spec:
  host: my-svc
  trafficPolicy:
    tls:
      mode: MUTUAL
      subjectAltNames:
        - spiffe://example.org/payments/worker
Defensive patterns

Strategy: validation

Validate before calling

// Before relying on xDS security, verify the expected peer identity
// matches a configured SAN matcher.
package main

import (
	"crypto/x509"
	"fmt"
	"strings"
)

// acceptedSANPatterns are the identities your xDS policy permits.
var acceptedSANPatterns = []string{
	"spiffe://example.org/payments/worker",
	"payments.svc.cluster.local",
}

func peerMatchesAcceptedSAN(cert *x509.Certificate) error {
	for _, dns := range cert.DNSNames {
		for _, p := range acceptedSANPatterns {
			if strings.EqualFold(dns, p) {
				return nil
			}
		}
	}
	for _, u := range cert.URIs {
		for _, p := range acceptedSANPatterns {
			if u.String() == p {
				return nil
			}
		}
	}
	return fmt.Errorf("peer SANs %v / %v do not match accepted %v",
		cert.DNSNames, cert.URIs, acceptedSANPatterns)
}

// func main() { _ = peerMatchesAcceptedSAN }

Type guard

// Narrowing helper for verifying a peer cert before trusting it.
func isValidLeafCert(c *x509.Certificate) bool {
	return c != nil && !c.IsCA && len(c.UnhandledCriticalExtensions) == 0
}

Try / catch

// gRPC surfaces xDS handshake failures as the RPC error.
// Inspect status.Code and status.Message; treat SAN mismatch as non-retryable
// until policy/certs are corrected.
//
//   err := conn.Invoke(ctx, method, req, resp)
//   if err != nil {
//       if strings.Contains(status.Message(err), "do not match any of the accepted SANs") {
//           // Do NOT retry blindly; fix the SAN matchers / certificate.
//       }
//   }

Prevention

When it happens

Trigger: Triggered when HandshakeInfo contains one or more sanMatchers (received from an xDS security policy) and MatchingSANExists(cert) returns false for the peer's leaf cert at handshake_info.go:315. The XDSSNIEnabled / validateSANUsingSNI fast-path at line 304 must be inactive or the SNI must be empty, so execution falls through to the SAN-matcher comparison at line 315.

Common situations: The xDS control plane (Istio, Anthos, Envoy-based service mesh) is configured with a security policy whose SAN matchers reference identities/spiffe URIs or DNS names that do not match the certificate actually presented by the workload. Common causes: rotating/cert renewal changed SANs but the policy was not updated, workload identity (K8s service account, SPIFFE ID) drifted from the policy, cluster migration changed DNS names, or the matcher uses a regex/wildcard that does not cover the presented SAN.

Related errors


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

Appendix: source

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

		// If XDSSNIEnabled and AutoSNISANValidation are both true and the SNI is
		// non-empty, validate only DNS SANs against the SNI. Otherwise, fallback to
		// validating all received SANs against the control plane provided SAN
		// matchers.
		if envconfig.XDSSNIEnabled && hi.validateSANUsingSNI && sni != "" {
			// Verify SAN of leaf certificate with SNI using exact DNS matcher.
			for _, san := range certs[0].DNSNames {
				if dnsMatch(sni, san) {
					return nil
				}
			}
			return fmt.Errorf("xds: received DNS SANs: %v do not match the SNI: %s", certs[0].DNSNames, sni)
		}
		// The SANs sent by the xDS control plane are encoded as SPIFFE IDs. We need to
		// only look at the SANs on the leaf cert.
		if cert := certs[0]; !hi.MatchingSANExists(cert) {
			// TODO: Print the complete certificate once the x509 package
			// supports a String() method on the Certificate type.
			return fmt.Errorf("xds: received SANs {DNSNames: %v, EmailAddresses: %v, IPAddresses: %v, URIs: %v} do not match any of the accepted SANs", cert.DNSNames, cert.EmailAddresses, cert.IPAddresses, cert.URIs)
		}
		return nil
	}
}

// serverSideTLSConfigInternal constructs a tls.Config to be used in a
// server-side handshake based on the contents of the HandshakeInfo.
func (hi *HandshakeInfo) serverSideTLSConfigInternal(ctx context.Context) (*tls.Config, error) {
	cfg := &tls.Config{
		ClientAuth: tls.NoClientCert,
		NextProtos: []string{"h2"},
	}
	// On the server side, identityProvider is mandatory. RootProvider is
	// optional based on whether the server is doing TLS or mTLS.
	if hi.identityProvider == nil {
		return nil, errors.New("xds: CertificateProvider to fetch identity certificate is missing, cannot perform TLS handshake. Please check configuration on the management server")
	}
	if hi.requireClientCert {

View on GitHub (pinned to 0c51461d27)