1
0
Fork 0

Merge current v3.4 into master

This commit is contained in:
mmatur 2025-05-13 09:33:27 +02:00
commit c5f7381c80
No known key found for this signature in database
GPG key ID: 2FFE42FC256CFF8E
13 changed files with 196 additions and 40 deletions

View file

@ -3,11 +3,11 @@ PLEASE READ THIS MESSAGE.
Documentation fixes or enhancements:
- for Traefik v2: use branch v2.11
- for Traefik v3: use branch v3.3
- for Traefik v3: use branch v3.4
Bug fixes:
- for Traefik v2: use branch v2.11
- for Traefik v3: use branch v3.3
- for Traefik v3: use branch v3.4
Enhancements:
- for Traefik v2: we only accept bug fixes

View file

@ -1,3 +1,47 @@
## [v3.4.0](https://github.com/traefik/traefik/tree/v3.4.0) (2025-05-05)
[All Commits](https://github.com/traefik/traefik/compare/v3.3.0-rc1...v3.4.0)
**Enhancements:**
- **[acme]** Add acme.profile and acme.emailAddresses options ([#11597](https://github.com/traefik/traefik/pull/11597) by [ldez](https://github.com/ldez))
- **[docker,ecs,docker/swarm,consulcatalog,nomad]** Allow configuring server URLs with label providers ([#11374](https://github.com/traefik/traefik/pull/11374) by [yelvert](https://github.com/yelvert))
- **[k8s/crd]** Improve CEL validation on Ingress CRD resources ([#11311](https://github.com/traefik/traefik/pull/11311) by [mloiseleur](https://github.com/mloiseleur))
- **[k8s/crd]** Remove default load-balancing strategy from CRD ([#11701](https://github.com/traefik/traefik/pull/11701) by [kevinpollet](https://github.com/kevinpollet))
- **[k8s/crd]** Restrict regex validation of HTTP status codes for Ingress CRD resources ([#11670](https://github.com/traefik/traefik/pull/11670) by [jnoordsij](https://github.com/jnoordsij))
- **[k8s/gatewayapi]** Set rule priority in Gateway API TLSRoute ([#11443](https://github.com/traefik/traefik/pull/11443) by [augustozanellato](https://github.com/augustozanellato))
- **[k8s/ingress]** Add ingress status for ClusterIP and NodePort Service Type ([#11100](https://github.com/traefik/traefik/pull/11100) by [mlec1](https://github.com/mlec1))
- **[middleware,authentication]** Add option to preserve request method in forwardAuth ([#11473](https://github.com/traefik/traefik/pull/11473) by [an09mous](https://github.com/an09mous))
- **[middleware]** Support rewriting status codes in error page middleware ([#11520](https://github.com/traefik/traefik/pull/11520) by [sevensolutions](https://github.com/sevensolutions))
- **[middleware]** Add Redis rate limiter ([#10211](https://github.com/traefik/traefik/pull/10211) by [longquan0104](https://github.com/longquan0104))
- **[service]** Add p2c load-balancing strategy for servers load-balancer ([#11547](https://github.com/traefik/traefik/pull/11547) by [rtribotte](https://github.com/rtribotte))
- **[sticky-session]** Support domain configuration for sticky cookies ([#11556](https://github.com/traefik/traefik/pull/11556) by [jleal52](https://github.com/jleal52))
- **[tls,k8s/crd,service]** Allow root CA to be added through config maps ([#11475](https://github.com/traefik/traefik/pull/11475) by [Nelwhix](https://github.com/Nelwhix))
- **[tls]** Add support to disable session ticket ([#11609](https://github.com/traefik/traefik/pull/11609) by [avdhoot](https://github.com/avdhoot))
- **[udp]** Add support for UDP routing in systemd socket activation ([#11022](https://github.com/traefik/traefik/pull/11022) by [tsiid](https://github.com/tsiid))
- **[webui]** Add auto webui theme option and default to it ([#11455](https://github.com/traefik/traefik/pull/11455) by [zizzfizzix](https://github.com/zizzfizzix))
- Replace experimental maps and slices with stdlib ([#11350](https://github.com/traefik/traefik/pull/11350) by [Juneezee](https://github.com/Juneezee))
- Bump github.com/redis/go-redis/v9 to v9.7.3 ([#11687](https://github.com/traefik/traefik/pull/11687) by [kevinpollet](https://github.com/kevinpollet))
**Documentation:**
- Prepare release v3.4.0-rc1 ([#11654](https://github.com/traefik/traefik/pull/11654) by [kevinpollet](https://github.com/kevinpollet))
- Prepare release v3.4.0-rc2 ([#11707](https://github.com/traefik/traefik/pull/11707) by [rtribotte](https://github.com/rtribotte))
- Deprecate defaultRuleSyntax and ruleSyntax options ([#11619](https://github.com/traefik/traefik/pull/11619) by [rtribotte](https://github.com/rtribotte))
**Misc:**
- Merge branch v3.3 into master ([#11653](https://github.com/traefik/traefik/pull/11653) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v3.3 into master ([#11595](https://github.com/traefik/traefik/pull/11595) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v3.3 into master ([#11541](https://github.com/traefik/traefik/pull/11541) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v3.3 into master ([#11504](https://github.com/traefik/traefik/pull/11504) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v3.3 into master ([#11420](https://github.com/traefik/traefik/pull/11420) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v3.3 into master ([#11394](https://github.com/traefik/traefik/pull/11394) by [mmatur](https://github.com/mmatur))
- Merge branch v3.3 into v3.4 ([#11736](https://github.com/traefik/traefik/pull/11736) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v3.3 into v3.4 ([#11705](https://github.com/traefik/traefik/pull/11705) by [kevinpollet](https://github.com/kevinpollet))
## [v3.3.7](https://github.com/traefik/traefik/tree/v3.3.7) (2025-05-05)
[All Commits](https://github.com/traefik/traefik/compare/v3.3.6...v3.3.7)
**Bug fixes:**
- **[logs,middleware,accesslogs]** Add SpanID and TraceID accessLogs fields only when tracing is enabled ([#11715](https://github.com/traefik/traefik/pull/11715) by [rtribotte](https://github.com/rtribotte))
## [v3.4.0-rc2](https://github.com/traefik/traefik/tree/v3.4.0-rc2) (2025-04-18)
[All Commits](https://github.com/traefik/traefik/compare/v3.4.0-rc1...v3.4.0-rc2)

View file

@ -267,4 +267,4 @@ The retry expression is defined as a logical combination of the functions below
### Content-Length
See [Best Practices: ContentLength](../../security/best-practices/content-length.md)
See [Best Practices: ContentLength](../../security/content-length.md)

View file

@ -327,6 +327,11 @@ serversTransport:
--serversTransport.maxIdleConnsPerHost=7
```
!!! info "Disable connection reuse"
The default value of `maxIdleConnsPerHost` is 2, and the zero value is the fallback to the default (2).
If you want to disable connection reuse, set `maxIdleConnsPerHost` to -1.
#### `spiffe`
Please note that [SPIFFE](../https/spiffe.md) must be enabled in the static configuration

View file

@ -0,0 +1,38 @@
---
title: "TLS Certificates in MultiTenant Kubernetes"
description: "Isolate TLS certificates in multitenant clusters by keeping Secrets and routes in the same namespace and disabling crossnamespace lookups in Traefik. Read the technical guidelines."
---
# TLS Certificates in MultiTenant Kubernetes
In a shared cluster, different teams can create `Ingress` or `IngressRoute` objects that Traefik consumes.
Traefik does not support multi-tenancy when using the Kubernetes `Ingress` or `IngressRoute` specifications due to the way TLS certificate management is handled.
At the core of this limitation is the TLS Store, which holds all the TLS certificates used by Traefik.
As this Store is global in Traefik, it is shared across all namespaces, meaning any `Ingress` or `IngressRoute` in the cluster can potentially reference or affect TLS configurations intended for other tenants.
This lack of isolation poses a risk in multi-tenant environments where different teams or applications require strict boundaries between resources, especially around sensitive data like TLS certificates.
In contrast, the [Kubernetes Gateway API](../providers/kubernetes-gateway.md) provides better primitives for secure multi-tenancy.
Specifically, the `Listener` resource in the Gateway API allows administrators to explicitly define which Route resources (e.g., `HTTPRoute`) are permitted to bind to which domain names or ports.
This capability enforces stricter ownership and isolation, making it a safer choice for multi-tenant use cases.
## Recommended setup
When strict boundaries are required between resources and teams, we recommend using one Traefik instance per tenant.
In Kubernetes one way to isolate a tenant is to restrict it to a namespace.
In that case, the namespace options from the Kubernetes [CRD](../providers/kubernetes-crd.md#namespaces) and [Ingress](../providers/kubernetes-ingress.md#namespaces) providers can be leveraged.
!!! tip "Dedicate one Traefik instance per tenant using the Helm Chart"
```yaml
providers:
kubernetesCRD:
namespaces:
- tenant
kubernetesIngress:
namespaces:
- tenant
```

View file

@ -164,8 +164,8 @@ nav:
- 'Overview': 'observability/tracing/overview.md'
- 'OpenTelemetry': 'observability/tracing/opentelemetry.md'
- 'Security':
- 'Best Practices':
- 'security/best-practices/content-length.md'
- 'Content-Length': 'security/content-length.md'
- 'TLS in Multi-Tenant Kubernetes': 'security/tls-certs-in-multi-tenant-kubernetes.md'
- 'User Guides':
- 'FastProxy': 'user-guides/fastproxy.md'
- 'Kubernetes and Let''s Encrypt': 'user-guides/crd-acme/index.md'

View file

@ -92,6 +92,9 @@ func (s *RedisSentinelSuite) setupSentinelConfiguration(ports []string) {
require.NoError(s.T(), err)
defer tmpFile.Close()
err = tmpFile.Chmod(0o666)
require.NoError(s.T(), err)
model := structs.Map(templateValue)
model["SelfFilename"] = tmpFile.Name()

View file

@ -212,8 +212,10 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
if span := trace.SpanFromContext(req.Context()); span != nil {
spanContext := span.SpanContext()
logDataTable.Core[TraceID] = spanContext.TraceID().String()
logDataTable.Core[SpanID] = spanContext.SpanID().String()
if spanContext.HasTraceID() && spanContext.HasSpanID() {
logDataTable.Core[TraceID] = spanContext.TraceID().String()
logDataTable.Core[SpanID] = spanContext.SpanID().String()
}
}
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))

View file

@ -27,8 +27,10 @@ import (
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
"github.com/traefik/traefik/v3/pkg/types"
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
"go.opentelemetry.io/otel/trace/embedded"
)
const delta float64 = 1e-10
@ -310,7 +312,7 @@ func TestLoggerHeaderFields(t *testing.T) {
func TestLoggerCLF(t *testing.T) {
logFilePath := filepath.Join(t.TempDir(), logFileNameSuffix)
config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat}
doLogging(t, config)
doLogging(t, config, false)
logData, err := os.ReadFile(logFilePath)
require.NoError(t, err)
@ -322,7 +324,7 @@ func TestLoggerCLF(t *testing.T) {
func TestLoggerCLFWithBufferingSize(t *testing.T) {
logFilePath := filepath.Join(t.TempDir(), logFileNameSuffix)
config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat, BufferingSize: 1024}
doLogging(t, config)
doLogging(t, config, false)
// wait a bit for the buffer to be written in the file.
time.Sleep(50 * time.Millisecond)
@ -371,10 +373,11 @@ func TestLoggerJSON(t *testing.T) {
desc string
config *types.AccessLog
tls bool
tracing bool
expected map[string]func(t *testing.T, value interface{})
}{
{
desc: "default config",
desc: "default config without tracing",
config: &types.AccessLog{
FilePath: "",
Format: JSONFormat,
@ -410,8 +413,48 @@ func TestLoggerJSON(t *testing.T) {
"time": assertNotEmpty(),
"StartLocal": assertNotEmpty(),
"StartUTC": assertNotEmpty(),
TraceID: assertNotEmpty(),
SpanID: assertNotEmpty(),
},
},
{
desc: "default config with tracing",
config: &types.AccessLog{
FilePath: "",
Format: JSONFormat,
},
tracing: true,
expected: map[string]func(t *testing.T, value interface{}){
RequestContentSize: assertFloat64(0),
RequestHost: assertString(testHostname),
RequestAddr: assertString(testHostname),
RequestMethod: assertString(testMethod),
RequestPath: assertString(testPath),
RequestProtocol: assertString(testProto),
RequestScheme: assertString(testScheme),
RequestPort: assertString("-"),
DownstreamStatus: assertFloat64(float64(testStatus)),
DownstreamContentSize: assertFloat64(float64(len(testContent))),
OriginContentSize: assertFloat64(float64(len(testContent))),
OriginStatus: assertFloat64(float64(testStatus)),
RequestRefererHeader: assertString(testReferer),
RequestUserAgentHeader: assertString(testUserAgent),
RouterName: assertString(testRouterName),
ServiceURL: assertString(testServiceName),
ClientUsername: assertString(testUsername),
ClientHost: assertString(testHostname),
ClientPort: assertString(strconv.Itoa(testPort)),
ClientAddr: assertString(fmt.Sprintf("%s:%d", testHostname, testPort)),
"level": assertString("info"),
"msg": assertString(""),
"downstream_Content-Type": assertString("text/plain; charset=utf-8"),
RequestCount: assertFloat64NotZero(),
Duration: assertFloat64NotZero(),
Overhead: assertFloat64NotZero(),
RetryAttempts: assertFloat64(float64(testRetryAttempts)),
"time": assertNotEmpty(),
"StartLocal": assertNotEmpty(),
"StartUTC": assertNotEmpty(),
TraceID: assertString("01000000000000000000000000000000"),
SpanID: assertString("0100000000000000"),
},
},
{
@ -455,8 +498,6 @@ func TestLoggerJSON(t *testing.T) {
"time": assertNotEmpty(),
StartLocal: assertNotEmpty(),
StartUTC: assertNotEmpty(),
TraceID: assertNotEmpty(),
SpanID: assertNotEmpty(),
},
},
{
@ -578,9 +619,9 @@ func TestLoggerJSON(t *testing.T) {
test.config.FilePath = logFilePath
if test.tls {
doLoggingTLS(t, test.config)
doLoggingTLS(t, test.config, test.tracing)
} else {
doLogging(t, test.config)
doLogging(t, test.config, test.tracing)
}
logData, err := os.ReadFile(logFilePath)
@ -632,8 +673,6 @@ func TestLogger_AbortedRequest(t *testing.T) {
"downstream_Content-Type": assertString("text/plain"),
"downstream_Transfer-Encoding": assertString("chunked"),
"downstream_Cache-Control": assertString("no-cache"),
TraceID: assertNotEmpty(),
SpanID: assertNotEmpty(),
}
config := &types.AccessLog{
@ -854,7 +893,7 @@ func TestNewLogHandlerOutputStdout(t *testing.T) {
file, restoreStdout := captureStdout(t)
defer restoreStdout()
doLogging(t, test.config)
doLogging(t, test.config, false)
written, err := os.ReadFile(file.Name())
require.NoError(t, err, "unable to read captured stdout from file")
@ -913,7 +952,7 @@ func captureStdout(t *testing.T) (out *os.File, restoreStdout func()) {
return file, restoreStdout
}
func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS bool) {
func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS, tracing bool) {
t.Helper()
logger, err := NewHandler(config)
require.NoError(t, err)
@ -952,9 +991,10 @@ func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS bool) {
}
}
tracer := noop.Tracer{}
spanCtx, _ := tracer.Start(req.Context(), "test")
req = req.WithContext(spanCtx)
if tracing {
contextWithSpan := trace.ContextWithSpan(req.Context(), &mockSpan{})
req = req.WithContext(contextWithSpan)
}
chain := alice.New()
chain = chain.Append(capture.Wrap)
@ -965,16 +1005,16 @@ func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS bool) {
handler.ServeHTTP(httptest.NewRecorder(), req)
}
func doLoggingTLS(t *testing.T, config *types.AccessLog) {
func doLoggingTLS(t *testing.T, config *types.AccessLog, tracing bool) {
t.Helper()
doLoggingTLSOpt(t, config, true)
doLoggingTLSOpt(t, config, true, tracing)
}
func doLogging(t *testing.T, config *types.AccessLog) {
func doLogging(t *testing.T, config *types.AccessLog, tracing bool) {
t.Helper()
doLoggingTLSOpt(t, config, false)
doLoggingTLSOpt(t, config, false, tracing)
}
func logWriterTestHandlerFunc(rw http.ResponseWriter, r *http.Request) {
@ -1091,3 +1131,27 @@ func streamBackend(rw http.ResponseWriter, r *http.Request) {
}
}
}
// mockSpan is an implementation of Span that preforms no operations.
type mockSpan struct {
embedded.Span
}
var _ trace.Span = &mockSpan{}
func (*mockSpan) SpanContext() trace.SpanContext {
return trace.NewSpanContext(trace.SpanContextConfig{TraceID: trace.TraceID{1}, SpanID: trace.SpanID{1}})
}
func (*mockSpan) IsRecording() bool { return true }
func (s *mockSpan) SetStatus(_ codes.Code, _ string) {}
func (s *mockSpan) SetAttributes(...attribute.KeyValue) {}
func (s *mockSpan) End(...trace.SpanEndOption) {}
func (s *mockSpan) RecordError(_ error, _ ...trace.EventOption) {}
func (s *mockSpan) AddEvent(_ string, _ ...trace.EventOption) {}
func (s *mockSpan) AddLink(_ trace.Link) {}
func (s *mockSpan) SetName(_ string) {}
func (s *mockSpan) TracerProvider() trace.TracerProvider {
return nil
}

View file

@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
# example new bugfix v3.3.6
# example new bugfix v3.3.7
CurrentRef = "v3.3"
PreviousRef = "v3.3.5"
PreviousRef = "v3.3.6"
BaseBranch = "v3.3"
FutureCurrentRefName = "v3.3.6"
FutureCurrentRefName = "v3.3.7"
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10

View file

@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
# example final release of v3.3.0
CurrentRef = "v3.3"
PreviousRef = "v3.3.0-rc1"
BaseBranch = "v3.3"
FutureCurrentRefName = "v3.3.0"
# example final release of v3.4.0
CurrentRef = "v3.4"
PreviousRef = "v3.4.0-rc1"
BaseBranch = "v3.4"
FutureCurrentRefName = "v3.4.0"
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10

View file

@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
# example final release of v3.3.0
CurrentRef = "v3.3.0-rc1"
PreviousRef = "v3.2.0-rc1"
# example final release of v3.4.0
CurrentRef = "v3.4.0-rc1"
PreviousRef = "v3.3.0-rc1"
BaseBranch = "master"
FutureCurrentRefName = "v3.3.0"
FutureCurrentRefName = "v3.4.0"
ThresholdPreviousRef = 10
ThresholdCurrentRef = 10