Merge branch v3.6 into master

This commit is contained in:
mmatur
2026-03-06 16:01:24 +01:00
76 changed files with 1121 additions and 712 deletions
@@ -0,0 +1,38 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
noColor = true
[entryPoints]
[entryPoints.web]
address = ":8888"
allowACMEByPass = true
[entryPoints.web.http.redirections.entryPoint]
to = ":8443"
scheme = "https"
[entryPoints.websecure]
address = ":8443"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers]
[http.routers.acme-challenge]
entryPoints = ["web"]
rule = "PathPrefix(`/.well-known/acme-challenge/`)"
service = "acme-solver"
[http.services]
[http.services.acme-solver]
[http.services.acme-solver.loadBalancer]
[[http.services.acme-solver.loadBalancer.servers]]
url = "{{ .AcmeSolverURL }}"
+10 -8
View File
@@ -232,15 +232,17 @@ func (s *BaseSuite) createComposeProject(name string) {
func (s *BaseSuite) createContainer(ctx context.Context, containerConfig composeService, id string, mounts []mount.Mount) (testcontainers.Container, error) {
req := testcontainers.ContainerRequest{
Image: containerConfig.Image,
Env: containerConfig.Environment,
Cmd: containerConfig.Command,
Labels: containerConfig.Labels,
Name: id,
Hostname: containerConfig.Hostname,
Privileged: containerConfig.Privileged,
Networks: []string{s.network.Name},
Image: containerConfig.Image,
Env: containerConfig.Environment,
Cmd: containerConfig.Command,
Labels: containerConfig.Labels,
Name: id,
Networks: []string{s.network.Name},
ConfigModifier: func(config *container.Config) {
config.Hostname = containerConfig.Hostname
},
HostConfigModifier: func(config *container.HostConfig) {
config.Privileged = containerConfig.Privileged
if containerConfig.CapAdd != nil {
config.CapAdd = containerConfig.CapAdd
}
+30 -28
View File
@@ -7,12 +7,10 @@ import (
"flag"
"fmt"
"io"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
@@ -20,6 +18,8 @@ import (
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/k3s"
"github.com/traefik/traefik/v3/integration/try"
"github.com/traefik/traefik/v3/pkg/api"
)
@@ -27,7 +27,11 @@ import (
var updateExpected = flag.Bool("update_expected", false, "Update expected files in testdata")
// K8sSuite tests suite.
type K8sSuite struct{ BaseSuite }
type K8sSuite struct {
BaseSuite
k3sContainer *k3s.K3sContainer
}
func TestK8sSuite(t *testing.T) {
suite.Run(t, new(K8sSuite))
@@ -36,47 +40,45 @@ func TestK8sSuite(t *testing.T) {
func (s *K8sSuite) SetupSuite() {
s.BaseSuite.SetupSuite()
s.createComposeProject("k8s")
s.composeUp()
abs, err := filepath.Abs("./fixtures/k8s/config.skip/kubeconfig.yaml")
manifests, err := filepath.Glob("./fixtures/k8s/*.yml")
require.NoError(s.T(), err)
err = try.Do(60*time.Second, func() error {
_, err := os.Stat(abs)
return err
})
opts := make([]testcontainers.ContainerCustomizer, 0, len(manifests))
for _, m := range manifests {
opts = append(opts, k3s.WithManifest(m))
}
s.k3sContainer, err = k3s.Run(s.T().Context(), k3sImage, opts...)
require.NoError(s.T(), err)
data, err := os.ReadFile(abs)
kubeConfigYaml, err := s.k3sContainer.GetKubeConfig(s.T().Context())
require.NoError(s.T(), err)
content := strings.ReplaceAll(string(data), "https://server:6443", fmt.Sprintf("https://%s", net.JoinHostPort(s.getComposeServiceIP("server"), "6443")))
err = os.WriteFile(abs, []byte(content), 0o644)
kubeconfigPath := filepath.Join(s.T().TempDir(), "kubeconfig.yaml")
err = os.WriteFile(kubeconfigPath, kubeConfigYaml, 0o644)
require.NoError(s.T(), err)
err = os.Setenv("KUBECONFIG", abs)
err = os.Setenv("KUBECONFIG", kubeconfigPath)
require.NoError(s.T(), err)
}
func (s *K8sSuite) TearDownSuite() {
s.BaseSuite.TearDownSuite()
if s.k3sContainer != nil {
if s.T().Failed() || *showLog {
k3sLogs, err := s.k3sContainer.Logs(s.T().Context())
if err == nil {
if res, err := io.ReadAll(k3sLogs); err == nil {
s.T().Log(string(res))
}
}
}
generatedFiles := []string{
"./fixtures/k8s/config.skip/kubeconfig.yaml",
"./fixtures/k8s/config.skip/k3s.log",
"./fixtures/k8s/coredns.yaml",
"./fixtures/k8s/rolebindings.yaml",
"./fixtures/k8s/traefik.yaml",
"./fixtures/k8s/ccm.yaml",
}
for _, filename := range generatedFiles {
if err := os.Remove(filename); err != nil {
if err := s.k3sContainer.Terminate(s.T().Context()); err != nil {
log.Warn().Err(err).Send()
}
}
s.BaseSuite.TearDownSuite()
}
func (s *K8sSuite) TestIngressConfiguration() {
-33
View File
@@ -1,33 +0,0 @@
services:
server:
image: rancher/k3s:v1.34.2-k3s1
privileged: true
command:
- server
- --disable-agent
- --disable=coredns
- --disable=servicelb
- --disable=traefik
- --disable=local-storage
- --disable=metrics-server
- --log=/output/k3s.log
- --bind-address=server
- --tls-san=server
- --tls-san=172.31.42.3
- --tls-san=172.31.42.4
environment:
K3S_CLUSTER_SECRET: somethingtotallyrandom
K3S_TOKEN: somethingtotallyrandom
K3S_KUBECONFIG_OUTPUT: /output/kubeconfig.yaml
K3S_KUBECONFIG_MODE: 666
volumes:
- ./fixtures/k8s/config.skip:/output
- ./fixtures/k8s:/var/lib/rancher/k3s/server/manifests
node:
image: rancher/k3s:v1.34.2-k3s1
privileged: true
environment:
K3S_TOKEN: somethingtotallyrandom
K3S_URL: https://server:6443
K3S_CLUSTER_SECRET: somethingtotallyrandom
+37
View File
@@ -2419,6 +2419,43 @@ func waitForWritePartial(t *testing.T, conn net.Conn) {
}
}
func (s *SimpleSuite) TestAllowACMEByPassRedirect() {
// Start a local server that simulates an ACME challenge solver.
acmeSolver := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
_, _ = rw.Write([]byte("acme-challenge-token"))
}))
defer acmeSolver.Close()
file := s.adaptFile("fixtures/simple_acme_bypass_redirect.toml", struct {
AcmeSolverURL string
}{
AcmeSolverURL: acmeSolver.URL,
})
s.traefikCmd(withConfigFile(file))
// Wait for Traefik to be ready with the user-defined ACME challenge router.
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 5*time.Second, try.BodyContains("acme-challenge@file"))
require.NoError(s.T(), err)
noRedirectClient := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
// ACME challenge path should NOT be redirected — it should reach the solver.
resp, err := noRedirectClient.Get("http://127.0.0.1:8888/.well-known/acme-challenge/test-token")
require.NoError(s.T(), err)
assert.Equal(s.T(), http.StatusOK, resp.StatusCode)
// Normal path should be redirected to HTTPS.
resp, err = noRedirectClient.Get("http://127.0.0.1:8888/other-path")
require.NoError(s.T(), err)
assert.Equal(s.T(), http.StatusMovedPermanently, resp.StatusCode)
}
func (s *SimpleSuite) TestFailoverService() {
s.createComposeProject("base")
+15 -15
View File
@@ -5,7 +5,7 @@
"traefik"
],
"service": "api@internal",
"rule": "PathPrefix(`/api`)",
"rule": "PathPrefix(\"/api\")",
"ruleSyntax": "default",
"priority": 9223372036854775806,
"observability": {
@@ -28,7 +28,7 @@
"dashboard_stripprefix@internal"
],
"service": "dashboard@internal",
"rule": "PathPrefix(`/`)",
"rule": "PathPrefix(\"/\")",
"ruleSyntax": "default",
"priority": 9223372036854775805,
"observability": {
@@ -42,12 +42,12 @@
"traefik"
]
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06@kubernetesgateway": {
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-af329269dd38031b03e3@kubernetesgateway": {
"entryPoints": [
"web"
],
"service": "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06-wrr",
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
"service": "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-af329269dd38031b03e3-wrr",
"rule": "Host(\"foo.com\") \u0026\u0026 Path(\"/bar\")",
"ruleSyntax": "default",
"priority": 100008,
"observability": {
@@ -61,12 +61,12 @@
"web"
]
},
"httproute-default-http-app-1-gw-default-my-https-gateway-ep-websecure-0-1c0cf64bde37d9d0df06@kubernetesgateway": {
"httproute-default-http-app-1-gw-default-my-https-gateway-ep-websecure-0-af329269dd38031b03e3@kubernetesgateway": {
"entryPoints": [
"websecure"
],
"service": "httproute-default-http-app-1-gw-default-my-https-gateway-ep-websecure-0-1c0cf64bde37d9d0df06-wrr",
"rule": "Host(`foo.com`) \u0026\u0026 Path(`/bar`)",
"service": "httproute-default-http-app-1-gw-default-my-https-gateway-ep-websecure-0-af329269dd38031b03e3-wrr",
"rule": "Host(\"foo.com\") \u0026\u0026 Path(\"/bar\")",
"ruleSyntax": "default",
"priority": 100008,
"tls": {},
@@ -142,7 +142,7 @@
"http://10.42.0.6:80": "UP"
}
},
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06-wrr@kubernetesgateway": {
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-af329269dd38031b03e3-wrr@kubernetesgateway": {
"weighted": {
"services": [
{
@@ -153,10 +153,10 @@
},
"status": "enabled",
"usedBy": [
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06@kubernetesgateway"
"httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-af329269dd38031b03e3@kubernetesgateway"
]
},
"httproute-default-http-app-1-gw-default-my-https-gateway-ep-websecure-0-1c0cf64bde37d9d0df06-wrr@kubernetesgateway": {
"httproute-default-http-app-1-gw-default-my-https-gateway-ep-websecure-0-af329269dd38031b03e3-wrr@kubernetesgateway": {
"weighted": {
"services": [
{
@@ -167,7 +167,7 @@
},
"status": "enabled",
"usedBy": [
"httproute-default-http-app-1-gw-default-my-https-gateway-ep-websecure-0-1c0cf64bde37d9d0df06@kubernetesgateway"
"httproute-default-http-app-1-gw-default-my-https-gateway-ep-websecure-0-af329269dd38031b03e3@kubernetesgateway"
]
},
"noop@internal": {
@@ -180,7 +180,7 @@
"footcp"
],
"service": "tcproute-default-tcp-app-1-gw-default-my-tcp-gateway-ep-footcp-0-e3b0c44298fc1c149afb-wrr",
"rule": "HostSNI(`*`)",
"rule": "HostSNI(\"*\")",
"ruleSyntax": "default",
"priority": -1,
"status": "enabled",
@@ -193,7 +193,7 @@
"footlsterminate"
],
"service": "tcproute-default-tcp-app-1-gw-default-my-tls-gateway-ep-footlsterminate-0-e3b0c44298fc1c149afb-wrr",
"rule": "HostSNI(`*`)",
"rule": "HostSNI(\"*\")",
"ruleSyntax": "default",
"priority": -1,
"tls": {
@@ -209,7 +209,7 @@
"footlspassthrough"
],
"service": "tlsroute-default-tls-app-1-gw-default-my-tls-gateway-ep-footlspassthrough-0-e3b0c44298fc1c149afb-wrr",
"rule": "HostSNI(`foo.bar`)",
"rule": "HostSNI(\"foo.bar\")",
"ruleSyntax": "default",
"priority": 7,
"tls": {