From e16a7ad8efc300d33dfbef45ca0d6bedf0ae0bb0 Mon Sep 17 00:00:00 2001 From: Chris Thach Date: Mon, 10 Nov 2025 13:36:40 -0500 Subject: [PATCH] Add debug logging to `tsh` when there is a SAN mismatch connecting to Auth via Proxy (#61059) * Add debug logging to `tsh login` when there is a DNS SAN mismatch Signed-off-by: Chris Thach * Fix formatting so it is consistent Signed-off-by: Chris Thach * Filter for only proxy env vars. Improve message. Signed-off-by: Chris Thach * Add unit tests for formatCertError. Signed-off-by: Chris Thach * Revert copyright update * Pretty print proxy env and certificate Signed-off-by: Chris Thach * Print NotBefore Signed-off-by: Chris Thach * Add case where there is a network intermediary Signed-off-by: Chris Thach * Revert general HostnameError case. Add message for just the case of connecting to Auth. Signed-off-by: Chris Thach * Apply suggestions from code review Co-authored-by: Zac Bergquist * Fix missing deref. Only build in the special case. Signed-off-by: Chris Thach * Remove internal cluster domain and move hostname to debug info. Signed-off-by: Chris Thach * Add vertical space. Signed-off-by: Chris Thach --------- Signed-off-by: Chris Thach Co-authored-by: Zac Bergquist --- lib/utils/cli.go | 45 +++++++++++++++++++++++++++++++++++++++++++ lib/utils/cli_test.go | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/lib/utils/cli.go b/lib/utils/cli.go index 70087b93a05..8c9dcca812a 100644 --- a/lib/utils/cli.go +++ b/lib/utils/cli.go @@ -238,6 +238,51 @@ func formatCertError(err error) string { var hostnameErr x509.HostnameError if errors.As(err, &hostnameErr) { + // Special case for connecting to Auth via Proxy using internal cluster domain. + if strings.HasSuffix(hostnameErr.Host, ".teleport.cluster.local") { + var proxyEnvBuilder strings.Builder + for _, key := range []string{ + "https_proxy", "http_proxy", "no_proxy", + "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", + } { + if val, ok := os.LookupEnv(key); ok { + fmt.Fprintf(&proxyEnvBuilder, " %s: %s\n", key, val) + } + } + + return fmt.Sprintf(`Cannot connect to the Auth service via the Teleport Proxy. + + There might be one or more network intermediaries (like a proxy or VPN) that are modifying your connection before it + reaches the Teleport Proxy. These intermediaries can alter how your connection is seen by the Teleport Proxy and + routed, leading to certificate mismatches. + + To fix this, ensure that any network intermediaries are properly configured and not interfering with your connection. + +DEBUG INFO: + Host: %s + + Proxy Environment Variables: +%s + Server Certificate Details: + Subject: %s + Issuer: %s + Serial Number: %s + Not Before: %s + Not After: %s + DNS Names: %v + IP Addresses: %v`, + hostnameErr.Host, + proxyEnvBuilder.String(), + hostnameErr.Certificate.Subject, + hostnameErr.Certificate.Issuer, + hostnameErr.Certificate.SerialNumber, + hostnameErr.Certificate.NotBefore, + hostnameErr.Certificate.NotAfter, + hostnameErr.Certificate.DNSNames, + hostnameErr.Certificate.IPAddresses, + ) + } + return fmt.Sprintf("Cannot establish https connection to %s:\n%s\n%s\n", hostnameErr.Host, hostnameErr.Error(), diff --git a/lib/utils/cli_test.go b/lib/utils/cli_test.go index 7e299c410d2..d9b2b04609a 100644 --- a/lib/utils/cli_test.go +++ b/lib/utils/cli_test.go @@ -20,6 +20,7 @@ package utils import ( "crypto/x509" + "errors" "fmt" "log/slog" "testing" @@ -212,3 +213,45 @@ func TestFilterArguments(t *testing.T) { require.Equal(t, tt.expected, FilterArguments(tt.args, app.Model()), fmt.Sprintf("test case %v", i)) } } + +// TestFormatCertError tests the formatCertError function for various x509 error types and messages. +func TestFormatCertError(t *testing.T) { + t.Run("UnknownAuthorityError", func(t *testing.T) { + err := x509.UnknownAuthorityError{} + msg := formatCertError(err) + require.Contains(t, msg, "The proxy you are connecting to has presented a certificate signed by a") + }) + + t.Run("HostnameErrorConnectingToAuth", func(t *testing.T) { + cert := &x509.Certificate{Raw: []byte("dummy")} + err := x509.HostnameError{Certificate: cert, Host: "99999999999999999999999999999999.teleport.cluster.local"} + msg := formatCertError(err) + require.Contains(t, msg, "Cannot connect to the Auth service via the Teleport Proxy.") + require.Contains(t, msg, "Host: 99999999999999999999999999999999.teleport.cluster.local") + }) + + t.Run("HostnameError", func(t *testing.T) { + cert := &x509.Certificate{Raw: []byte("dummy")} + err := x509.HostnameError{Certificate: cert, Host: "example.com"} + msg := formatCertError(err) + require.Contains(t, msg, "Cannot establish https connection to example.com") + }) + + t.Run("CertificateInvalidError", func(t *testing.T) { + err := x509.CertificateInvalidError{Reason: x509.Expired, Cert: &x509.Certificate{}} + msg := formatCertError(err) + require.Contains(t, msg, "The certificate presented by the proxy is invalid") + }) + + t.Run("CertificateNotTrustedError", func(t *testing.T) { + err := errors.New("certificate is not trusted") + msg := formatCertError(err) + require.Contains(t, msg, "The proxy you are connecting to has presented a certificate signed by") + }) + + t.Run("NoMatch", func(t *testing.T) { + err := errors.New("some other error") + msg := formatCertError(err) + require.Empty(t, msg) + }) +}