Error level log for configuration-related TLS errors with backends

Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
Romain 2025-03-21 11:00:06 +01:00 committed by GitHub
parent b02946147d
commit 8ba99adc50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 72 additions and 2 deletions

View File

@ -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

View File

@ -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)
})
}
}