kubernetes/kops · error

no TLS connection

Error message

no TLS connection

What it means

AuthenticateClientToUniverse extracts identity from the client's mTLS certificate, but requires the request to be TLS-terminated by Go's http server (r.TLS populated). If the request arrived over plain HTTP, or TLS was terminated upstream without propagating connection state, r.TLS is nil and authentication cannot proceed.

Source

Thrown at discovery/pkg/discovery/auth.go:38

	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"fmt"
	"net/http"
)

type UserInfo struct {
	UniverseID string
	ClientID   string
}

// AuthenticateClientToUniverse extracts the Universe ID and Client ID from the mTLS connection.
// The Universe ID is defined as the SHA256 hash of the root CA certificate (DER bytes)
// presented in the client's certificate chain.
// The Client ID is taken from the Common Name (CN) of the leaf certificate.
func AuthenticateClientToUniverse(r *http.Request, universeID string) (*UserInfo, error) {
	if r.TLS == nil {
		return nil, fmt.Errorf("no TLS connection")
	}
	if len(r.TLS.PeerCertificates) == 0 {
		return nil, fmt.Errorf("no client certificate presented")
	}

	// Verify the chain is valid, though we don't validate that the CA certificate is trusted.
	var verifiedChains [][]*x509.Certificate
	{
		peerCertificates := r.TLS.PeerCertificates

		opts := x509.VerifyOptions{
			Roots:         x509.NewCertPool(),
			Intermediates: x509.NewCertPool(),
			KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
		}

		for i := 1; i < len(peerCertificates); i++ {
			if i == len(peerCertificates)-1 {

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Send the request over HTTPS with a client certificate so net/http populates r.TLS.
  2. Configure the server with tls.Listen / ListenAndServeTLS using the CA that issued client certs.
  3. If TLS terminates at a proxy, either pass through TLS or configure the proxy to inject the client cert (e.g. X-Forwarded-Client-Cert) and adapt the handler accordingly.
  4. Point probes/tests at the TLS port, not the plain-HTTP one.

Example fix

// before
client.Get("http://discovery.internal/validate")
// after
cert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
client.Transport = &http.Transport{TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{cert}}}
client.Get("https://discovery.internal/validate")
Defensive patterns

Strategy: type-guard

Validate before calling

// caller-side pre-check before auth logic
if r.URL.Scheme != "https" {
    return nil, fmt.Errorf("request must use HTTPS")
}

Type guard

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

Try / catch

// Go: guard before authenticating
u, err := AuthenticateClientToUniverse(r, universeID)
if err != nil {
    http.Error(w, "mTLS required", http.StatusUnauthorized)
    return
}

Prevention

When it happens

Trigger: An HTTP request reaches a handler calling AuthenticateClientToUniverse with r.TLS == nil — i.e. served over plain HTTP instead of HTTPS/mTLS, or a reverse proxy terminated TLS without setting X-Forwarded/Forwarded cert headers and the code expects *tls.ConnectionState.

Common situations: Misconfigured ingress/load balancer forwarding to the backend as plain HTTP; developer testing against http://localhost; health probes hitting an mTLS-only endpoint over HTTP.

Understand the failure class

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/538490c51ff17a46. Report an issue: GitHub.