thanos-io/thanos · error

could not get required certificate field from client cert

Error message

could not get required certificate field from client cert

What it means

getTenantFromCertificate extracts the tenant ID from the client's mTLS certificate (OU or CN depending on configured field). If the TLS handshake produced no peer certificates (r.TLS.PeerCertificates empty), the tenant cannot be extracted and this error is returned.

Solutions

  1. Require client certificates: set tls.Config ClientAuth to tls.RequireAndVerifyClientCert and reconfigure the client to present its cert.
  2. If a proxy/LB terminates TLS, enable client-cert passthrough (PROXY protocol / header injection) or move tenant extraction to the proxy.
  3. Confirm the request is HTTPS; GetTenantFromHTTP only inspects r.TLS, which is nil for plain HTTP.
  4. If certs are not your tenant mechanism, switch Options.TenantField to the header-based option instead.

Example fix

// before
tlsConfig := &tls.Config{} // no client auth
// after
tlsConfig := &tls.Config{
    ClientAuth: tls.RequireAndVerifyClientCert,
    ClientCAs:  caPool,
}
Defensive patterns

Strategy: validation

Validate before calling

if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
    http.Error(w, "client certificate required", http.StatusUnauthorized)
    return
}

Type guard

func hasPeerCert(r *http.Request) bool { return r.TLS != nil && len(r.TLS.PeerCertificates) > 0 }

Try / catch

tenant, err := tenancy.GetTenantFromHTTP(r, header, def, field)
if err != nil {
    http.Error(w, err.Error(), http.StatusUnauthorized)
    return
}

Prevention

When it happens

Trigger: getTenantFromCertificate is called by GetTenantFromHTTP with certTenantField set to a certificate field, but the request was not authenticated with a client certificate — r.TLS is nil-capable or PeerCertificates has length 0 (e.g. request over plain HTTP or server not requesting client certs).

Common situations: Client connects without mTLS (plain HTTP or TLS without client auth); server's tls.Config ClientAuth set to NoClientCert/RequestClientCert and client sent no cert; a load balancer terminates TLS so the server never sees peer certs; TenantField option set to certificate while users authenticate via header.

Understand the failure class

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/ddb4e347199c1385. Report an issue: GitHub.

Appendix: source

Thrown at pkg/tenancy/tenancy.go:102

	return roundTripperFunc(func(r *http.Request) (*http.Response, error) {
		tenant, _ := GetTenantFromHTTP(r, customTenantHeader, DefaultTenant, certTenantField)
		r.Header.Set(DefaultTenantHeader, tenant)
		// If the custom tenant header is not the same as the default internal header, we want to exclude the custom
		// one from the request to keep things simple.
		if customTenantHeader != DefaultTenantHeader {
			r.Header.Del(customTenantHeader)
		}
		return next.RoundTrip(r)
	})
}

// getTenantFromCertificate extracts the tenant value from a client's presented certificate. The x509 field to use as
// value can be configured with Options.TenantField. An error is returned when the extraction has not succeeded.
func getTenantFromCertificate(r *http.Request, certTenantField string) (string, error) {
	var tenant string

	if len(r.TLS.PeerCertificates) == 0 {
		return "", errors.New("could not get required certificate field from client cert")
	}

	// First cert is the leaf authenticated against.
	cert := r.TLS.PeerCertificates[0]

	switch certTenantField {

	case CertificateFieldOrganization:
		if len(cert.Subject.Organization) == 0 {
			return "", errors.New("could not get organization field from client cert")
		}
		tenant = cert.Subject.Organization[0]

	case CertificateFieldOrganizationalUnit:
		if len(cert.Subject.OrganizationalUnit) == 0 {
			return "", errors.New("could not get organizationalUnit field from client cert")
		}
		tenant = cert.Subject.OrganizationalUnit[0]

View on GitHub (pinned to 35b8b99117)