Merge v3.6 into master
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Build and Publish Documentation / Doc Process (push) Has been cancelled
Build experimental image on branch / build-webui (push) Has been cancelled
Build experimental image on branch / Build experimental image on branch (push) Has been cancelled

This commit is contained in:
mmatur 2026-02-10 09:07:34 +01:00
commit 4a4be524bb
No known key found for this signature in database
GPG key ID: 2FFE42FC256CFF8E
24 changed files with 93 additions and 94 deletions

View file

@ -10,7 +10,7 @@ on:
- 'script/gcg/**'
env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'
CGO_ENABLED: 0
jobs:

View file

@ -7,7 +7,7 @@ on:
- v*
env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'
CGO_ENABLED: 0
jobs:

View file

@ -6,7 +6,7 @@ on:
- 'v*.*.*'
env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'
CGO_ENABLED: 0
VERSION: ${{ github.ref_name }}
TRAEFIKER_EMAIL: "traefiker@traefik.io"
@ -130,8 +130,8 @@ jobs:
--exclude .idea \
--exclude .github \
--exclude dist .
chown -R "$(id -u)":"$(id -g)" dist/
gh release create ${VERSION} ./dist/**/traefik*.{zip,tar.gz} ./dist/traefik*.{tar.gz,txt} --repo traefik/traefik --title ${VERSION} --notes ${VERSION} --latest=false
./script/deploy.sh

View file

@ -12,7 +12,7 @@ on:
- 'integration/integration_test.go'
env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'
CGO_ENABLED: 0
jobs:

View file

@ -10,7 +10,7 @@ on:
- 'script/gcg/**'
env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'
CGO_ENABLED: 0
jobs:

View file

@ -12,7 +12,7 @@ on:
- 'integration/integration_test.go'
env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'
CGO_ENABLED: 0
jobs:

View file

@ -10,7 +10,7 @@ on:
- 'script/gcg/**'
env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'
jobs:
generate-packages:

View file

@ -6,7 +6,7 @@ on:
- '*'
env:
GO_VERSION: '1.24'
GO_VERSION: '1.25'
GOLANGCI_LINT_VERSION: v2.8.0
MISSPELL_VERSION: v0.7.0

View file

@ -41,7 +41,7 @@ tracing: {}
| <a id="opt-tracing-addInternals" href="#opt-tracing-addInternals" title="#opt-tracing-addInternals">`tracing.addInternals`</a> | Enables tracing for internal resources (e.g.: `ping@internal`). | false | No |
| <a id="opt-tracing-serviceName" href="#opt-tracing-serviceName" title="#opt-tracing-serviceName">`tracing.serviceName`</a> | Defines the service name resource attribute. | "traefik" | No |
| <a id="opt-tracing-resourceAttributes" href="#opt-tracing-resourceAttributes" title="#opt-tracing-resourceAttributes">`tracing.resourceAttributes`</a> | Defines additional resource attributes to be sent to the collector. See [resourceAttributes](#resourceattributes) for details. | [] | No |
| <a id="opt-tracing-sampleRate" href="#opt-tracing-sampleRate" title="#opt-tracing-sampleRate">`tracing.sampleRate`</a> | The proportion of requests to trace, specified between 0.0 and 1.0. | 1.0 | No |
| <a id="opt-tracing-sampleRate" href="#opt-tracing-sampleRate" title="#opt-tracing-sampleRate">`tracing.sampleRate`</a> | The proportion of requests to trace, specified between 0.0 and 1.0.<br /> Since Traefik supports parent-based sampling ratios, root spans (i.e., spans initiated by Traefik) are sampled according to this rate, while child spans inherit the sampling decision of their parent (i.e., the tracing context from incoming requests). See [sampleRate](#samplerate) for details. | 1.0 | No |
| <a id="opt-tracing-capturedRequestHeaders" href="#opt-tracing-capturedRequestHeaders" title="#opt-tracing-capturedRequestHeaders">`tracing.capturedRequestHeaders`</a> | Defines the list of request headers to add as attributes.<br />It applies to client and server kind spans. | [] | No |
| <a id="opt-tracing-capturedResponseHeaders" href="#opt-tracing-capturedResponseHeaders" title="#opt-tracing-capturedResponseHeaders">`tracing.capturedResponseHeaders`</a> | Defines the list of response headers to add as attributes.<br />It applies to client and server kind spans. | [] | False |
| <a id="opt-tracing-safeQueryParams" href="#opt-tracing-safeQueryParams" title="#opt-tracing-safeQueryParams">`tracing.safeQueryParams`</a> | By default, all query parameters are redacted.<br />Defines the list of query parameters to not redact. | [] | No |
@ -61,6 +61,17 @@ tracing: {}
| <a id="opt-tracing-otlp-grpc-tls-key" href="#opt-tracing-otlp-grpc-tls-key" title="#opt-tracing-otlp-grpc-tls-key">`tracing.otlp.grpc.tls.key`</a> | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.<br /> Setting the sub-options with their default values. | ""null/false "" | No |
| <a id="opt-tracing-otlp-grpc-tls-insecureskipverify" href="#opt-tracing-otlp-grpc-tls-insecureskipverify" title="#opt-tracing-otlp-grpc-tls-insecureskipverify">`tracing.otlp.grpc.tls.insecureskipverify`</a> | If `insecureSkipVerify` is `true`, the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes |
## sampleRate
The `sampleRate` option controls trace sampling using a `ParentBased(TraceIDRatioBased)` strategy.
!!! info "Sampling Strategy Behavior"
- **Root spans** (trace originating at Traefik): Sampled according to the configured `sampleRate` using trace ID ratio-based sampling.
- **Child spans** (requests with existing trace context): Inherit the sampling decision from the parent span, regardless of the local `sampleRate`.
This ensures consistent sampling decisions across distributed traces: once a trace is sampled, all spans in that trace are sampled, providing complete end-to-end visibility.
## resourceAttributes
The `resourceAttributes` option allows setting the resource attributes sent along the traces.

View file

@ -19,8 +19,8 @@ http:
- "503"
- "505-599"
statusRewrites:
"418": "404"
"502-504": "500"
"418": 404
"502-504": 500
service: error-handler-service
query: "/{status}.html"

View file

@ -41,6 +41,20 @@ tls:
It is the only available method to configure the certificates (as well as the options and the stores).
However, in [Kubernetes](../../../install-configuration/providers/kubernetes/kubernetes-crd.md), the certificates can and must be provided by [secrets](https://kubernetes.io/docs/concepts/configuration/secret/).
#### Certificate selection (SNI)
Traefik selects the certificate to present during the TLS handshake, based on the Server Name Indication (SNI) sent by the client.
However, HTTP router rules (e.g., `Host()`) are evaluated after TLS has been established, so they do not influence certificate selection.
##### Strict SNI Checking
By default, if the client does not send SNI, or if no certificate matches the requested server name,
Traefik falls back to the [default certificate](#default-certificate) from the TLS store (if configured).
To reject connections without SNI (or with an unknown server name) instead of falling back to the default certificate,
enable `sniStrict` in [TLS Options](./tls-options.md#strict-sni-checking).
## Certificates Stores
In Traefik, certificates are grouped together in certificates stores.
@ -82,6 +96,12 @@ tls:
The `stores` list will actually be ignored and automatically set to `["default"]`.
!!! tip "Per provider examples"
- [Docker: Enable TLS](../../../../expose/docker/basic.md#enable-tls)
- [Swarm: Enable TLS](../../../../expose/swarm/basic.md#enable-tls)
- [Kubernetes: Enable TLS](../../../../expose/kubernetes/basic.md#enable-tls)
### Default Certificate
Traefik can use a default certificate for connections without a SNI, or without a matching domain.

View file

@ -9,7 +9,7 @@ description: 'Traefik Hub API Gateway - Learn how to configure the JWT Authentic
This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro).
JSON Web Token (JWT) (defined in the [RFC 7519](https://tools.ietf.org/html/rfc7519)) allows
Traefik Hub API Gateway to secure the API access using a token signed using either a private signing secret or a plublic/private key.
Traefik Hub API Gateway to secure the API access using a token signed using either a private signing secret or a public/private key.
Traefik Hub API Gateway provides many kinds of sources to perform the token validation:

6
go.mod
View file

@ -1,6 +1,6 @@
module github.com/traefik/traefik/v3
go 1.24.0
go 1.25.0
require (
github.com/BurntSushi/toml v1.6.0
@ -217,7 +217,7 @@ require (
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.2 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
@ -392,7 +392,7 @@ require (
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.4.0 // indirect
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/term v0.39.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect

7
go.sum
View file

@ -464,8 +464,9 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.21.2 h1:AqQaNADVwq/VnkCmQg6ogE+M3FOsKTytwges0JdwVuA=
github.com/go-openapi/jsonpointer v0.21.2/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
@ -1524,8 +1525,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=

View file

@ -398,8 +398,7 @@ func (l *ServersLoadBalancer) Merge(other *ServersLoadBalancer) bool {
// SetDefaults Default values for a ServersLoadBalancer.
func (l *ServersLoadBalancer) SetDefaults() {
defaultPassHostHeader := DefaultPassHostHeader
l.PassHostHeader = &defaultPassHostHeader
l.PassHostHeader = ptr.To(DefaultPassHostHeader)
l.Strategy = BalancerStrategyWRR
l.ResponseForwarding = &ResponseForwarding{}
@ -473,8 +472,7 @@ type ServerHealthCheck struct {
// SetDefaults Default values for a HealthCheck.
func (h *ServerHealthCheck) SetDefaults() {
fr := true
h.FollowRedirects = &fr
h.FollowRedirects = ptr.To(true)
h.Mode = "http"
h.Interval = DefaultHealthCheckInterval
h.Timeout = DefaultHealthCheckTimeout

View file

@ -9,6 +9,7 @@ import (
ptypes "github.com/traefik/paerser/types"
otypes "github.com/traefik/traefik/v3/pkg/observability/types"
"github.com/traefik/traefik/v3/pkg/types"
"k8s.io/utils/ptr"
)
// EntryPoint holds the entry point configuration.
@ -76,8 +77,7 @@ type HTTPConfig struct {
// SetDefaults sets the default values.
func (c *HTTPConfig) SetDefaults() {
sanitizePath := true
c.SanitizePath = &sanitizePath
c.SanitizePath = ptr.To(true)
c.MaxHeaderBytes = http.DefaultMaxHeaderBytes
}
@ -201,9 +201,8 @@ type ObservabilityConfig struct {
// SetDefaults sets the default values.
func (o *ObservabilityConfig) SetDefaults() {
defaultValue := true
o.AccessLogs = &defaultValue
o.Metrics = &defaultValue
o.Tracing = &defaultValue
o.AccessLogs = ptr.To(true)
o.Metrics = ptr.To(true)
o.Tracing = ptr.To(true)
o.TraceVerbosity = otypes.MinimalVerbosity
}

View file

@ -571,12 +571,9 @@ func TestServiceTCPHealthChecker_differentIntervals(t *testing.T) {
hc := NewServiceTCPHealthChecker(ctx, config, lb, serviceInfo, targets, "test-service")
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
wg.Go(func() {
hc.Launch(ctx)
wg.Done()
}()
})
// Let it run for 2 seconds to see the different check frequencies
select {

View file

@ -439,12 +439,9 @@ func TestServiceHealthChecker_Launch(t *testing.T) {
hc := NewServiceHealthChecker(ctx, &MetricsMock{gauge}, config, lb, serviceInfo, http.DefaultTransport, map[string]*url.URL{"test": targetURL}, "foobar")
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
wg.Go(func() {
hc.Launch(ctx)
wg.Done()
}()
})
// Wait for expected health check events using channel synchronization.
for i := range expectedEvents {
@ -508,12 +505,10 @@ func TestDifferentIntervals(t *testing.T) {
hc := NewServiceHealthChecker(ctx, &MetricsMock{gauge}, config, lb, serviceInfo, http.DefaultTransport, map[string]*url.URL{"healthy": healthyURL, "unhealthy": unhealthyURL}, "foobar")
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
wg.Go(func() {
hc.Launch(ctx)
wg.Done()
}()
})
select {
case <-time.After(2 * time.Second):

View file

@ -159,13 +159,11 @@ func NewHandler(ctx context.Context, config *otypes.AccessLog) (*Handler, error)
}
if config.BufferingSize > 0 {
logHandler.wg.Add(1)
go func() {
defer logHandler.wg.Done()
logHandler.wg.Go(func() {
for handlerParams := range logHandler.logHandlerChan {
logHandler.logTheRoundTrip(handlerParams.ctx, handlerParams.logDataTable)
}
}()
})
}
return logHandler, nil

View file

@ -98,16 +98,14 @@ func isWebsocketRequest(req *http.Request) bool {
containsHeader := func(name, value string) bool {
h := unsafeHeader(req.Header).Get(name)
for {
pos := strings.Index(h, ",")
if pos == -1 {
return strings.EqualFold(value, strings.TrimSpace(h))
}
if strings.EqualFold(value, strings.TrimSpace(h[:pos])) {
before, after, found := strings.Cut(h, ",")
if strings.EqualFold(value, strings.TrimSpace(before)) {
return true
}
h = h[pos+1:]
if !found {
return false
}
h = after
}
}

View file

@ -97,7 +97,7 @@ func (c *OTelTracing) Setup(ctx context.Context, serviceName string, sampleRate
// span processor to aggregate spans before export.
bsp := sdktrace.NewBatchSpanProcessor(exporter)
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(sampleRate)),
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(sampleRate))),
sdktrace.WithResource(res),
sdktrace.WithSpanProcessor(bsp),
)

View file

@ -147,16 +147,12 @@ func (eps TCPEntryPoints) Stop() {
var wg sync.WaitGroup
for epn, ep := range eps {
wg.Add(1)
go func(entryPointName string, entryPoint *TCPEntryPoint) {
defer wg.Done()
logger := log.With().Str(logs.EntryPointName, entryPointName).Logger()
entryPoint.Shutdown(logger.WithContext(context.Background()))
wg.Go(func() {
logger := log.With().Str(logs.EntryPointName, epn).Logger()
ep.Shutdown(logger.WithContext(context.Background()))
logger.Debug().Msg("Entrypoint closed")
}(epn, ep)
})
}
wg.Wait()
@ -313,7 +309,6 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) {
var wg sync.WaitGroup
shutdownServer := func(server stoppable) {
defer wg.Done()
err := server.Shutdown(ctx)
if err == nil {
return
@ -334,24 +329,19 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) {
}
if e.httpServer.Server != nil {
wg.Add(1)
go shutdownServer(e.httpServer.Server)
wg.Go(func() { shutdownServer(e.httpServer.Server) })
}
if e.httpsServer.Server != nil {
wg.Add(1)
go shutdownServer(e.httpsServer.Server)
wg.Go(func() { shutdownServer(e.httpsServer.Server) })
if e.http3Server != nil {
wg.Add(1)
go shutdownServer(e.http3Server)
wg.Go(func() { shutdownServer(e.http3Server) })
}
}
if e.tracker != nil {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
err := e.tracker.Shutdown(ctx)
if err == nil {
return
@ -360,7 +350,7 @@ func (e *TCPEntryPoint) Shutdown(ctx context.Context) {
logger.Debug().Err(err).Msg("Server failed to shutdown before deadline")
}
e.tracker.Close()
}()
})
}
wg.Wait()

View file

@ -50,16 +50,14 @@ func (eps UDPEntryPoints) Stop() {
var wg sync.WaitGroup
for epn, ep := range eps {
wg.Add(1)
go func(entryPointName string, entryPoint *UDPEntryPoint) {
wg.Go(func() {
defer wg.Done()
logger := log.With().Str(logs.EntryPointName, entryPointName).Logger()
entryPoint.Shutdown(logger.WithContext(context.Background()))
logger := log.With().Str(logs.EntryPointName, epn).Logger()
ep.Shutdown(logger.WithContext(context.Background()))
logger.Debug().Msg("Entry point closed")
}(epn, ep)
})
}
wg.Wait()

View file

@ -592,9 +592,7 @@ func TestConcurrentInflightTracking(t *testing.T) {
numRequests := 50
for range numRequests {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
handler.inflightCount.Add(1)
defer handler.inflightCount.Add(-1)
@ -608,7 +606,7 @@ func TestConcurrentInflightTracking(t *testing.T) {
}
time.Sleep(1 * time.Millisecond)
}()
})
}
wg.Wait()
@ -661,12 +659,10 @@ func TestConcurrentRequestsRespectInflight(t *testing.T) {
inflightRequests := 5
for range inflightRequests {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
recorder := httptest.NewRecorder()
balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil))
}()
})
}
// Wait for goroutines to start and increment inflight counters.
@ -687,9 +683,7 @@ func TestConcurrentRequestsRespectInflight(t *testing.T) {
// Launch new requests in background so they don't block.
var newWg sync.WaitGroup
for range newRequests {
newWg.Add(1)
go func() {
defer newWg.Done()
newWg.Go(func() {
rec := httptest.NewRecorder()
balancer.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
server := rec.Header().Get("server")
@ -698,7 +692,7 @@ func TestConcurrentRequestsRespectInflight(t *testing.T) {
save[server]++
saveMu.Unlock()
}
}()
})
}
// Wait for new requests to start and see the inflight counts.