diff --git a/pkg/server/service/proxy.go b/pkg/server/service/proxy.go index 8f3c0a418..ca5f4150f 100644 --- a/pkg/server/service/proxy.go +++ b/pkg/server/service/proxy.go @@ -2,6 +2,7 @@ package service import ( "context" + "crypto/tls" "errors" "fmt" "io" @@ -91,8 +92,9 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar BufferPool: bufferPool, ErrorLog: errorLogger, ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) { - statusCode := http.StatusInternalServerError + logger := log.FromContext(request.Context()) + statusCode := http.StatusInternalServerError switch { case errors.Is(err, io.EOF): statusCode = http.StatusBadGateway @@ -109,7 +111,13 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar } } - log.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err) + // Log the error with error level if it is a TLS error related to configuration. + if isTLSConfigError(err) { + logger.Errorf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err) + } else { + logger.Debugf("'%d %s' caused by: %v", statusCode, statusText(statusCode), err) + } + w.WriteHeader(statusCode) _, werr := w.Write([]byte(statusText(statusCode))) if werr != nil { @@ -121,6 +129,22 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar return proxy, nil } +// isTLSError returns true if the error is a TLS error which is related to configuration. +// We assume that if the error is a tls.RecordHeaderError or a tls.CertificateVerificationError, +// it is related to configuration, because the client should not send a TLS request to a non-TLS server, +// and the client configuration should allow to verify the server certificate. +func isTLSConfigError(err error) bool { + // tls.RecordHeaderError is returned when the client sends a TLS request to a non-TLS server. + var recordHeaderErr tls.RecordHeaderError + if errors.As(err, &recordHeaderErr) { + return true + } + + // tls.CertificateVerificationError is returned when the server certificate cannot be verified. + var certVerificationErr *tls.CertificateVerificationError + return errors.As(err, &certVerificationErr) +} + func isWebSocketUpgrade(req *http.Request) bool { if !httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") { return false diff --git a/pkg/server/service/proxy_test.go b/pkg/server/service/proxy_test.go index 40ca69d67..cae812380 100644 --- a/pkg/server/service/proxy_test.go +++ b/pkg/server/service/proxy_test.go @@ -1,12 +1,15 @@ package service import ( + "crypto/tls" + "errors" "io" "net/http" "net/http/httptest" "strings" "testing" + "github.com/stretchr/testify/require" "github.com/traefik/traefik/v2/pkg/testhelpers" ) @@ -35,3 +38,46 @@ func BenchmarkProxy(b *testing.B) { handler.ServeHTTP(w, req) } } + +func TestIsTLSConfigError(t *testing.T) { + testCases := []struct { + desc string + err error + expected bool + }{ + { + desc: "nil", + }, + { + desc: "TLS ECHRejectionError", + err: &tls.ECHRejectionError{}, + }, + { + desc: "TLS AlertError", + err: tls.AlertError(0), + }, + { + desc: "Random error", + err: errors.New("random error"), + }, + { + desc: "TLS RecordHeaderError", + err: tls.RecordHeaderError{}, + expected: true, + }, + { + desc: "TLS CertificateVerificationError", + err: &tls.CertificateVerificationError{}, + expected: true, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := isTLSConfigError(test.err) + require.Equal(t, test.expected, actual) + }) + } +}