diff --git a/.golangci.yml b/.golangci.yml index 5201820dd6..59a02974e3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -346,6 +346,9 @@ linters: text: 'appendAssign: append result not assigned to the same slice' linters: - gocritic + - path: pkg/server/conncontext.go + linters: + - fatcontext paths: - pkg/provider/kubernetes/crd/generated/ diff --git a/CHANGELOG.md b/CHANGELOG.md index c56bc2c3b3..4058426560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [v2.11.49](https://github.com/traefik/traefik/tree/v2.11.49) (2026-06-05) +[All Commits](https://github.com/traefik/traefik/compare/v2.11.48...v2.11.49) + +**Bug fixes:** +- **[http3]** Bump github.com/quic-go/quic-go to v0.59.1 ([#13300](https://github.com/traefik/traefik/pull/13300) @rtribotte) +- **[webui]** Bump axios to v1.17.0 ([#13299](https://github.com/traefik/traefik/pull/13299) @gndz07) +- **[tls]** Fix snicheck with keepalive ([#13305](https://github.com/traefik/traefik/pull/13305) @juliens) + ## [v3.6.19](https://github.com/traefik/traefik/tree/v3.6.19) (2026-06-04) [All Commits](https://github.com/traefik/traefik/compare/v3.6.17...v3.6.19) diff --git a/go.mod b/go.mod index e49c510be1..3b3ad05d83 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // No tag on the repo. github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_model v0.6.2 - github.com/quic-go/quic-go v0.59.0 + github.com/quic-go/quic-go v0.59.1 github.com/redis/go-redis/v9 v9.8.0 github.com/rs/zerolog v1.33.0 github.com/sirupsen/logrus v1.9.4 diff --git a/go.sum b/go.sum index 4b600004ff..ae928f81f5 100644 --- a/go.sum +++ b/go.sum @@ -1819,8 +1819,8 @@ github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= -github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= +github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= diff --git a/pkg/server/conncontext.go b/pkg/server/conncontext.go new file mode 100644 index 0000000000..a93290c7a2 --- /dev/null +++ b/pkg/server/conncontext.go @@ -0,0 +1,29 @@ +package server + +import ( + "context" + "net" +) + +type connContextFunc func(context.Context, net.Conn) context.Context + +type multipleConnContext struct { + fns []connContextFunc +} + +func (m *multipleConnContext) AddConnContextFunc(fn connContextFunc) { + m.fns = append(m.fns, fn) +} + +func (m *multipleConnContext) Build() connContextFunc { + if len(m.fns) == 0 { + return nil + } + + return func(ctx context.Context, c net.Conn) context.Context { + for _, contextFunc := range m.fns { + ctx = contextFunc(ctx, c) + } + return ctx + } +} diff --git a/pkg/server/conncontext_test.go b/pkg/server/conncontext_test.go new file mode 100644 index 0000000000..6160ea358e --- /dev/null +++ b/pkg/server/conncontext_test.go @@ -0,0 +1,26 @@ +package server + +import ( + "context" + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +type keyTest string + +func TestConnContext(t *testing.T) { + var connContext multipleConnContext + connContext.AddConnContextFunc(func(ctx context.Context, _ net.Conn) context.Context { + return context.WithValue(ctx, keyTest("test"), "test") + }) + connContext.AddConnContextFunc(func(ctx context.Context, _ net.Conn) context.Context { + return context.WithValue(ctx, keyTest("test2"), "test2") + }) + + ctx := connContext.Build()(context.Background(), nil) + + require.Equal(t, "test", ctx.Value(keyTest("test"))) + require.Equal(t, "test2", ctx.Value(keyTest("test2"))) +} diff --git a/pkg/server/router/tcp/manager.go b/pkg/server/router/tcp/manager.go index cfaf96e34b..8e5ce9a350 100644 --- a/pkg/server/router/tcp/manager.go +++ b/pkg/server/router/tcp/manager.go @@ -116,6 +116,13 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string logger := log.Ctx(ctx).With().Str(logs.RouterName, routerHTTPName).Logger() ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerHTTPName)) + // Even if the TLS options mismatch between the configured and the resolved one is handled in the aggregator + // we also have to handle it here to be able to mark the router in error. + tlsOptionsName := traefiktls.DefaultTLSConfigName + if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != traefiktls.DefaultTLSConfigName { + tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options) + } + domains, err := httpmuxer.ParseDomains(routerHTTPConfig.Rule) if err != nil { routerErr := fmt.Errorf("invalid rule %s, error: %w", routerHTTPConfig.Rule, err) @@ -155,17 +162,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string // # When a request for "/foo" comes, even though it won't be routed by httpRouter2, // # if its SNI is set to foo.com, myTLSOptions will be used for the TLS connection. // # Otherwise, it will fallback to the default TLS config. - logger.Warn().Msgf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule) + if tlsOptionsName != traefiktls.DefaultTLSConfigName { + logger.Warn().Msgf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule) + routerHTTPConfig.AddError(fmt.Errorf("no domain found in rule %v, the TLS option %s cannot be applied", routerHTTPConfig.Rule, tlsOptionsName), false) + } } - // Even if the TLS options mismatch between the configured and the resolved one is handled in the aggregator - // we also have to handle it here to be able to mark the router in error. - tlsOptionsName := traefiktls.DefaultTLSConfigName - if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != traefiktls.DefaultTLSConfigName { - tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options) - } - - if routerHTTPConfig.TLS.ResolvedOptions != tlsOptionsName { + if len(domains) > 0 && routerHTTPConfig.TLS.ResolvedOptions != tlsOptionsName { + logger.Warn().Msg("Found different TLS options for routers on the same host, so using the default TLS options instead.") routerHTTPConfig.AddError(errors.New("found different TLS options for routers on the same host, so using the default TLS options instead"), false) } diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index d62a37f518..5db9e633e8 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -677,6 +677,44 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E handler = denyFragment(handler) + var connContext multipleConnContext + connContext.AddConnContextFunc(func(ctx context.Context, c net.Conn) context.Context { + // This adds an empty struct in order to store a RoundTripper in the ConnContext in case of Kerberos or NTLM. + ctx = service.AddTransportOnContext(ctx) + + if tlsConn, ok := c.(*tls.Conn); ok { + if tlsConnWithOptionsName, ok := tlsConn.NetConn().(tcp.TLSConn); ok { + return tcp.AddTLSOptionsNameInContext(ctx, tlsConnWithOptionsName.TLSOptionsName) + } + } + + return ctx + }) + + if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { + connContext.AddConnContextFunc(func(ctx context.Context, c net.Conn) context.Context { + cState := &connState{Start: time.Now()} + if debugConnection { + clientConnectionStatesMu.Lock() + clientConnectionStates[getConnKey(c)] = cState + clientConnectionStatesMu.Unlock() + } + + return context.WithValue(ctx, connStateKey, cState) + }) + } + + var connState func(c net.Conn, state http.ConnState) + if debugConnection { + connState = func(c net.Conn, state http.ConnState) { + clientConnectionStatesMu.Lock() + if clientConnectionStates[getConnKey(c)] != nil { + clientConnectionStates[getConnKey(c)].State = state.String() + } + clientConnectionStatesMu.Unlock() + } + } + serverHTTP := &http.Server{ Protocols: &protocols, Handler: handler, @@ -690,46 +728,8 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E MaxDecoderHeaderTableSize: int(configuration.HTTP2.MaxDecoderHeaderTableSize), MaxEncoderHeaderTableSize: int(configuration.HTTP2.MaxEncoderHeaderTableSize), }, - ConnContext: func(ctx context.Context, c net.Conn) context.Context { - if tlsConn, ok := c.(*tls.Conn); ok { - if tlsConnWithOptionsName, ok := tlsConn.NetConn().(tcp.TLSConn); ok { - return tcp.AddTLSOptionsNameInContext(ctx, tlsConnWithOptionsName.TLSOptionsName) - } - } - - return ctx - }, - } - if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { - serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context { - cState := &connState{Start: time.Now()} - if debugConnection { - clientConnectionStatesMu.Lock() - clientConnectionStates[getConnKey(c)] = cState - clientConnectionStatesMu.Unlock() - } - return context.WithValue(ctx, connStateKey, cState) - } - - if debugConnection { - serverHTTP.ConnState = func(c net.Conn, state http.ConnState) { - clientConnectionStatesMu.Lock() - if clientConnectionStates[getConnKey(c)] != nil { - clientConnectionStates[getConnKey(c)].State = state.String() - } - clientConnectionStatesMu.Unlock() - } - } - } - - prevConnContext := serverHTTP.ConnContext - serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context { - // This adds an empty struct in order to store a RoundTripper in the ConnContext in case of Kerberos or NTLM. - ctx = service.AddTransportOnContext(ctx) - if prevConnContext != nil { - return prevConnContext(ctx, c) - } - return ctx + ConnContext: connContext.Build(), + ConnState: connState, } listener := newHTTPForwarder(ln)