1
0
Fork 0

Fix HTTP headers not being canonicalized in tracing

This commit is contained in:
Maurus Cuelenaere 2025-08-26 15:55:05 +02:00 committed by GitHub
parent 0bf6442c5d
commit 90702d93ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 67 additions and 2 deletions

View file

@ -127,8 +127,8 @@ func NewTracer(tracer trace.Tracer, capturedRequestHeaders, capturedResponseHead
return &Tracer{
Tracer: tracer,
safeQueryParams: safeQueryParams,
capturedRequestHeaders: capturedRequestHeaders,
capturedResponseHeaders: capturedResponseHeaders,
capturedRequestHeaders: canonicalizeHeaders(capturedRequestHeaders),
capturedResponseHeaders: canonicalizeHeaders(capturedResponseHeaders),
}
}
@ -346,3 +346,18 @@ func defaultStatus(code int) (codes.Code, string) {
}
return codes.Unset, ""
}
// canonicalizeHeaders converts a slice of header keys to their canonical form.
// It uses http.CanonicalHeaderKey to ensure that the headers are in a consistent format.
func canonicalizeHeaders(headers []string) []string {
if headers == nil {
return nil
}
canonicalHeaders := make([]string, len(headers))
for i, header := range headers {
canonicalHeaders[i] = http.CanonicalHeaderKey(header)
}
return canonicalHeaders
}

View file

@ -17,6 +17,7 @@ import (
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
func Test_safeFullURL(t *testing.T) {
@ -414,3 +415,52 @@ func TestTracerProvider(t *testing.T) {
span.TracerProvider().Tracer("github.com/traefik/traefik")
span.TracerProvider().Tracer("other")
}
// TestNewTracer_HeadersCanonicalization tests that NewTracer properly canonicalizes headers.
func TestNewTracer_HeadersCanonicalization(t *testing.T) {
testCases := []struct {
desc string
inputHeaders []string
expectedCanonicalHeaders []string
}{
{
desc: "Empty headers",
inputHeaders: []string{},
expectedCanonicalHeaders: []string{},
},
{
desc: "Already canonical headers",
inputHeaders: []string{"Content-Type", "User-Agent", "Accept-Encoding"},
expectedCanonicalHeaders: []string{"Content-Type", "User-Agent", "Accept-Encoding"},
},
{
desc: "Lowercase headers",
inputHeaders: []string{"content-type", "user-agent", "accept-encoding"},
expectedCanonicalHeaders: []string{"Content-Type", "User-Agent", "Accept-Encoding"},
},
{
desc: "Mixed case headers",
inputHeaders: []string{"CoNtEnT-tYpE", "uSeR-aGeNt", "aCcEpT-eNcOdInG"},
expectedCanonicalHeaders: []string{"Content-Type", "User-Agent", "Accept-Encoding"},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
// Create a mock tracer using a no-op tracer from OpenTelemetry
mockTracer := noop.NewTracerProvider().Tracer("test")
// Test capturedRequestHeaders
tracer := NewTracer(mockTracer, test.inputHeaders, nil, nil)
assert.Equal(t, test.expectedCanonicalHeaders, tracer.capturedRequestHeaders)
assert.Nil(t, tracer.capturedResponseHeaders)
// Test capturedResponseHeaders
tracer = NewTracer(mockTracer, nil, test.inputHeaders, nil)
assert.Equal(t, test.expectedCanonicalHeaders, tracer.capturedResponseHeaders)
assert.Nil(t, tracer.capturedRequestHeaders)
})
}
}