Merge branch v2.11 into v3.6

This commit is contained in:
kevinpollet
2026-06-10 15:34:13 +02:00
8 changed files with 475 additions and 58 deletions
+7
View File
@@ -1,3 +1,10 @@
## [v2.11.50](https://github.com/traefik/traefik/tree/v2.11.50) (2026-06-10)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.49...v2.11.50)
**Bug fixes:**
- **[tls]** Fix routers with same host, different tlsoptions on different entryPoint ([#13329](https://github.com/traefik/traefik/pull/13329) @juliens)
- **[tls]** Fix snicheck for routers with no hosts ([#13333](https://github.com/traefik/traefik/pull/13333) @rtribotte)
## [v3.6.20](https://github.com/traefik/traefik/tree/v3.6.20) (2026-06-05)
[All Commits](https://github.com/traefik/traefik/compare/v3.6.19...v3.6.20)
@@ -0,0 +1,101 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.websecure]
address = ":4443"
[entryPoints.websecure2]
address = ":4444"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
# --- Same host, same options, same entryPoint: no conflict, the options are applied. ---
[http.routers.same-1]
rule = "Host(`same.www.snitest.com`)"
entryPoints = ["websecure"]
service = "service1"
[http.routers.same-1.tls]
options = "tls12"
[http.routers.same-2]
rule = "Host(`same.www.snitest.com`) && PathPrefix(`/same`)"
entryPoints = ["websecure"]
service = "service1"
[http.routers.same-2.tls]
options = "tls12"
# --- Same host, different options, same entryPoint: conflict, fallback to default options. ---
[http.routers.conflict-1]
rule = "Host(`conflict.www.snitest.com`)"
entryPoints = ["websecure"]
service = "service1"
[http.routers.conflict-1.tls]
options = "tls12"
[http.routers.conflict-2]
rule = "Host(`conflict.www.snitest.com`) && PathPrefix(`/conflict`)"
entryPoints = ["websecure"]
service = "service1"
[http.routers.conflict-2.tls]
options = "tls13"
# --- Same host, different options, different entryPoints: no conflict, each entryPoint keeps its own options. ---
[http.routers.cross-ep1]
rule = "Host(`cross.www.snitest.com`)"
entryPoints = ["websecure"]
service = "service1"
[http.routers.cross-ep1.tls]
options = "tls12"
[http.routers.cross-ep2]
rule = "Host(`cross.www.snitest.com`)"
entryPoints = ["websecure2"]
service = "service1"
[http.routers.cross-ep2.tls]
options = "tls13"
# --- Domain fronting (Host header != SNI): same options follow the header, different options are rejected. ---
[http.routers.df-a]
rule = "Host(`df-a.www.snitest.com`)"
entryPoints = ["websecure"]
service = "service1"
[http.routers.df-a.tls]
options = "tls12"
[http.routers.df-b]
rule = "Host(`df-b.www.snitest.com`)"
entryPoints = ["websecure"]
service = "service1"
[http.routers.df-b.tls]
options = "tls12"
[http.routers.df-c]
rule = "Host(`df-c.www.snitest.com`)"
entryPoints = ["websecure"]
service = "service1"
[http.routers.df-c.tls]
options = "tls13"
[http.services.service1]
[[http.services.service1.loadBalancer.servers]]
url = "http://127.0.0.1:9010"
[[tls.certificates]]
certFile = "fixtures/https/wildcard.www.snitest.com.cert"
keyFile = "fixtures/https/wildcard.www.snitest.com.key"
[tls.options]
[tls.options.tls12]
maxVersion = "VersionTLS12"
[tls.options.tls13]
minVersion = "VersionTLS13"
+148 -2
View File
@@ -259,7 +259,6 @@ func (s *HTTPSSuite) TestWithTLSOptions() {
}
// TestWithConflictingTLSOptions checks that routers with same SNI but different TLS options get fallbacked to the default TLS options.
func (s *HTTPSSuite) TestWithConflictingTLSOptions() {
file := s.adaptFile("fixtures/https/https_tls_options.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
@@ -317,7 +316,7 @@ func (s *HTTPSSuite) TestWithConflictingTLSOptions() {
assert.ErrorContains(s.T(), err, "tls: no supported versions satisfy MinVersion and MaxVersion")
// with unknown tls option
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("found different TLS options for routers on the same host, so using the default TLS options instead"))
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("router's TLSOptions configuration is conflicting with other routers on the same entrypoint and host, default TLS options will be used instead"))
require.NoError(s.T(), err)
}
@@ -1174,6 +1173,153 @@ func (s *HTTPSSuite) TestWithDomainFronting() {
}
}
// TestWithTLSOptionsConflict checks how TLS options are resolved when several routers
// target the same host (SNI), across the different conflict situations:
// - same options on the same entryPoint: no conflict, the options are applied;
// - different options on the same entryPoint: conflict, fallback to the default options;
// - different options on different entryPoints: no conflict, each entryPoint keeps its
// own options (they are selected independently on each listener);
// - domain fronting (Host header != SNI): allowed when both resolve to the same options,
// rejected with a 421 otherwise.
//
// The effective TLS options are probed through the negotiated TLS version: the "tls12"
// options cap the version to TLS 1.2, while the "tls13" options require at least TLS 1.3.
func (s *HTTPSSuite) TestWithTLSOptionsConflict() {
backend := startTestServer("9010", http.StatusOK, "server1")
defer backend.Close()
file := s.adaptFile("fixtures/https/https_tls_options_conflict.toml", struct{}{})
s.traefikCmd(withConfigFile(file))
// wait for Traefik
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`cross.www.snitest.com`)"))
require.NoError(s.T(), err)
testCases := []struct {
desc string
addr string // entryPoint address to reach
hostHeader string
serverName string // SNI
minVersion uint16 // 0 means the crypto/tls library default
maxVersion uint16 // 0 means the crypto/tls library default
// expectHandshakeError is set when the TLS handshake itself is expected to fail
// (i.e. the probed options reject the client's TLS version). Otherwise
// expectedStatusCode is asserted on the HTTP response.
expectHandshakeError bool
expectedStatusCode int
}{
// Same host, same options, same entryPoint: no conflict, the "tls12" options are applied.
{
desc: "same options / same entryPoint: TLS 1.2 client is accepted",
addr: "127.0.0.1:4443",
hostHeader: "same.www.snitest.com",
serverName: "same.www.snitest.com",
maxVersion: tls.VersionTLS12,
expectedStatusCode: http.StatusOK,
},
{
desc: "same options / same entryPoint: TLS 1.3 client is rejected (maxVersion TLS1.2 enforced)",
addr: "127.0.0.1:4443",
hostHeader: "same.www.snitest.com",
serverName: "same.www.snitest.com",
minVersion: tls.VersionTLS13,
expectHandshakeError: true,
},
// Same host, different options, same entryPoint: conflict, both routers fall back to the default options.
{
desc: "conflicting options / same entryPoint: TLS 1.3 client is accepted (default options used)",
addr: "127.0.0.1:4443",
hostHeader: "conflict.www.snitest.com",
serverName: "conflict.www.snitest.com",
minVersion: tls.VersionTLS13,
expectedStatusCode: http.StatusOK,
},
{
desc: "conflicting options / same entryPoint: TLS 1.2 client is accepted (default options used)",
addr: "127.0.0.1:4443",
hostHeader: "conflict.www.snitest.com",
serverName: "conflict.www.snitest.com",
maxVersion: tls.VersionTLS12,
expectedStatusCode: http.StatusOK,
},
// Same host, different options, different entryPoints: no conflict, each entryPoint keeps its own options.
{
desc: "different entryPoints: websecure keeps tls12, TLS 1.2 client is accepted",
addr: "127.0.0.1:4443",
hostHeader: "cross.www.snitest.com",
serverName: "cross.www.snitest.com",
maxVersion: tls.VersionTLS12,
expectedStatusCode: http.StatusOK,
},
{
desc: "different entryPoints: websecure keeps tls12, TLS 1.3 client is rejected",
addr: "127.0.0.1:4443",
hostHeader: "cross.www.snitest.com",
serverName: "cross.www.snitest.com",
minVersion: tls.VersionTLS13,
expectHandshakeError: true,
},
{
desc: "different entryPoints: websecure2 keeps tls13, TLS 1.3 client is accepted",
addr: "127.0.0.1:4444",
hostHeader: "cross.www.snitest.com",
serverName: "cross.www.snitest.com",
minVersion: tls.VersionTLS13,
expectedStatusCode: http.StatusOK,
},
{
desc: "different entryPoints: websecure2 keeps tls13, TLS 1.2 client is rejected",
addr: "127.0.0.1:4444",
hostHeader: "cross.www.snitest.com",
serverName: "cross.www.snitest.com",
maxVersion: tls.VersionTLS12,
expectHandshakeError: true,
},
// Domain fronting (Host header != SNI) on the same entryPoint.
{
desc: "domain fronting / same options: request follows the Host header (200)",
addr: "127.0.0.1:4443",
hostHeader: "df-a.www.snitest.com",
serverName: "df-b.www.snitest.com",
maxVersion: tls.VersionTLS12,
expectedStatusCode: http.StatusOK,
},
{
desc: "domain fronting / different options: request is misdirected (421)",
addr: "127.0.0.1:4443",
hostHeader: "df-a.www.snitest.com",
serverName: "df-c.www.snitest.com",
minVersion: tls.VersionTLS13,
expectedStatusCode: http.StatusMisdirectedRequest,
},
}
for _, test := range testCases {
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: test.serverName,
MinVersion: test.minVersion,
MaxVersion: test.maxVersion,
}
req, err := http.NewRequest(http.MethodGet, "https://"+test.addr+"/", nil)
require.NoError(s.T(), err)
req.Host = test.hostHeader
if test.expectHandshakeError {
_, err = (&http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}).Do(req)
assert.ErrorContains(s.T(), err, "tls:", "test %q should fail the TLS handshake", test.desc)
continue
}
err = try.RequestWithTransport(req, 2*time.Second, &http.Transport{TLSClientConfig: tlsConfig}, try.StatusCodeIs(test.expectedStatusCode))
assert.NoError(s.T(), err, "test %q failed with: %v", test.desc, err)
}
}
// TestWithInvalidTLSOption verifies the behavior when using an invalid tlsOption configuration.
func (s *HTTPSSuite) TestWithInvalidTLSOption() {
backend := startTestServer("9010", http.StatusOK, "server1")
+3 -3
View File
@@ -758,7 +758,7 @@ func (s *SimpleSuite) TestRouterConfigErrors() {
s.traefikCmd(withConfigFile(file))
// All errors
err := try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1000*time.Millisecond, try.BodyContains(`["middleware \"unknown@file\" does not exist","found different TLS options for routers on the same host, so using the default TLS options instead"]`))
err := try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1000*time.Millisecond, try.BodyContains(`["middleware \"unknown@file\" does not exist","router's TLSOptions configuration is conflicting with other routers on the same entrypoint and host, default TLS options will be used instead"]`))
require.NoError(s.T(), err)
// router3 has an error because it uses an unknown entrypoint
@@ -766,11 +766,11 @@ func (s *SimpleSuite) TestRouterConfigErrors() {
require.NoError(s.T(), err)
// router4 is enabled, but in warning state because its tls options conf was messed up
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router4@file", 1000*time.Millisecond, try.BodyContains(`"status":"warning"`))
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/websecure-conflicted-router4@file", 1000*time.Millisecond, try.BodyContains(`"status":"warning"`))
require.NoError(s.T(), err)
// router5 is disabled because its middleware conf is broken
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router5@file", 1000*time.Millisecond, try.BodyContains())
err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/websecure-conflicted-router5@file", 1000*time.Millisecond, try.BodyContains())
require.NoError(s.T(), err)
}
+101 -50
View File
@@ -55,7 +55,7 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
Str(logs.RouterName, routerName).
Strs(logs.EntryPointName, defaultEntryPoints).
Msg("No entryPoint defined for this router, using the default one(s) instead")
router.EntryPoints = defaultEntryPoints
router.EntryPoints = slices.Clone(defaultEntryPoints)
}
// The `ruleSyntax` option is deprecated.
@@ -99,7 +99,7 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
log.Debug().
Str(logs.RouterName, routerName).
Msgf("No entryPoint defined for this TCP router, using the default one(s) instead: %+v", defaultEntryPoints)
router.EntryPoints = defaultEntryPoints
router.EntryPoints = slices.Clone(defaultEntryPoints)
}
conf.TCP.Routers[provider.MakeQualifiedName(pvd, routerName)] = router
}
@@ -173,80 +173,131 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
return conf
}
func resolveHTTPTLSOptions(cfg dynamic.Configuration) dynamic.Configuration {
if cfg.HTTP == nil || len(cfg.HTTP.Routers) == 0 {
return cfg
// resolveHTTPTLSOptions resolves the TLS options for the given routers, on a per
// entryPoint basis.
//
// TLS options conflicts (i.e. the same host served with different TLS options) can
// only be detected and arbitrated within a single TLS listener, that is to say within
// a single entryPoint. To honor that, routers are grouped per entryPoint and the
// conflict detection is run independently for each entryPoint.
//
// A router keeps its original name, and its resolved TLS options, for the entryPoints
// on which it does not conflict. For each entryPoint on which it conflicts, that
// entryPoint is removed from the router and a dedicated copy is emitted, with its
// TLSOptions reset to the default one, named following the "ep-conflicted-name@provider" pattern.
func resolveHTTPTLSOptions(routers map[string]*dynamic.Router) map[string]*dynamic.Router {
if len(routers) == 0 {
return routers
}
rts := make(map[string]*dynamic.Router)
newRouters := make(map[string]*dynamic.Router)
// Keyed by domain, then by options reference.
// The actual source of truth for what TLS options will actually be used for the connection.
// As opposed to tlsOptionsForHost, it keeps track of all the (different) TLS
// options that occur for a given host name, so that later on we can set relevant
// errors and logging for all the routers concerned (i.e. wrongly configured).
tlsOptionsForHostSNI := map[string]map[string][]string{}
for routerHTTPName, routerHTTPConfig := range cfg.HTTP.Routers {
rts[routerHTTPName] = routerHTTPConfig.DeepCopy()
if routerHTTPConfig.TLS == nil {
// Split every router per entryPoint.
// Routers always have at least one entryPoint at this stage, as they are
// defaulted in mergeConfiguration before applyModel and this resolution run.
routersByEntryPoint := map[string]map[string]*dynamic.Router{}
for name, router := range routers {
if router.TLS == nil {
newRouters[name] = router
continue
}
ctxRouter := provider.AddInContext(context.Background(), routerHTTPName)
logger := log.Ctx(ctxRouter).With().Str(logs.RouterName, routerHTTPName).Logger()
tlsOptionsName := traefiktls.DefaultTLSConfigName
if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != traefiktls.DefaultTLSConfigName {
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
router.TLS.ResolvedOptions = traefiktls.DefaultTLSConfigName
if len(router.TLS.Options) > 0 && router.TLS.Options != traefiktls.DefaultTLSConfigName {
router.TLS.ResolvedOptions = provider.GetQualifiedName(provider.AddInContext(context.Background(), name), router.TLS.Options)
}
domains, err := httpmuxer.ParseDomains(routerHTTPConfig.Rule)
for _, ep := range router.EntryPoints {
if routersByEntryPoint[ep] == nil {
routersByEntryPoint[ep] = map[string]*dynamic.Router{}
}
routersByEntryPoint[ep][name] = router
}
}
// Resolve the TLS options independently for each entryPoint.
conflictingRouters := make(map[string][]string, len(routersByEntryPoint))
for ep, epRouters := range routersByEntryPoint {
conflictingRouters[ep] = findConflictingRouters(ep, epRouters)
}
for name, router := range routers {
router.EntryPoints = slices.DeleteFunc(router.EntryPoints, func(ep string) bool {
deleted := slices.Contains(conflictingRouters[ep], name)
if deleted {
rt := router.DeepCopy()
rt.TLS.ResolvedOptions = traefiktls.DefaultTLSConfigName
rt.EntryPoints = []string{ep}
// The new name is not collision free but has very small possibility to collide.
// TODO: rework this naming whenever we'll introduce a resource reference mechanism not based on a string.
newRouters[ep+"-conflicted-"+name] = rt
}
return deleted
})
if len(router.EntryPoints) > 0 {
newRouters[name] = router
}
}
return newRouters
}
// findConflictingRouters returns the names of the routers, among the given
// single-entryPoint routers, that serve a host (SNI) also served by another router
// with a different resolved TLS option. Such routers are arbitrated by falling back
// to the default TLS options.
func findConflictingRouters(ep string, routers map[string]*dynamic.Router) []string {
var conflicting []string
// For each host (SNI, already lower-cased by the domain parsing), the routers
// serving it grouped by their resolved TLS option. A host with more than one
// group is served with conflicting TLS options.
routersByHostAndOption := map[string]map[string][]string{}
for name, router := range routers {
if router.TLS == nil {
continue
}
domains, err := httpmuxer.ParseDomains(router.Rule)
if err != nil {
logger.Error().Err(err).Msgf("Invalid rule %s", routerHTTPConfig.Rule)
continue
}
if len(domains) == 0 {
rts[routerHTTPName].TLS.ResolvedOptions = "default"
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)
// The configured TLSOptions on a router without a domain in its rule cannot be selected when evaluating the SNI,
// so if it is not the default one, it is a conflict.
if len(domains) == 0 && router.TLS.ResolvedOptions != traefiktls.DefaultTLSConfigName {
conflicting = append(conflicting, name)
continue
}
for _, domain := range domains {
// domain is already in lower case thanks to the domain parsing
if tlsOptionsForHostSNI[domain] == nil {
tlsOptionsForHostSNI[domain] = make(map[string][]string)
if routersByHostAndOption[domain] == nil {
routersByHostAndOption[domain] = map[string][]string{}
}
tlsOptionsForHostSNI[domain][tlsOptionsName] = append(tlsOptionsForHostSNI[domain][tlsOptionsName], routerHTTPName)
option := router.TLS.ResolvedOptions
routersByHostAndOption[domain][option] = append(routersByHostAndOption[domain][option], name)
}
}
for hostSNI, tlsConfigs := range tlsOptionsForHostSNI {
if len(tlsConfigs) == 1 {
for optionsName, v := range tlsConfigs {
log.Debug().Msgf("Adding route for %s with TLS options %s", hostSNI, optionsName)
for _, s := range v {
rts[s].TLS.ResolvedOptions = optionsName
}
}
for domain, routersByOption := range routersByHostAndOption {
if len(routersByOption) == 1 {
continue
}
// multiple tlsConfigs
routers := make([]string, 0, len(tlsConfigs))
for _, v := range tlsConfigs {
for _, s := range v {
rts[s].TLS.ResolvedOptions = traefiktls.DefaultTLSConfigName
routers = append(routers, s)
}
var routersInConflict []string
for _, names := range routersByOption {
conflicting = append(conflicting, names...)
routersInConflict = append(routersInConflict, names...)
}
log.Warn().Msgf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
log.Error().Msgf("On EntryPoint %q, Host %q is served by multiple routers with different TLS options, default TLSOptions will be applied for the following routers: %v", ep, domain, routersInConflict)
}
cfg.HTTP.Routers = rts
return cfg
return conflicting
}
func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
+111
View File
@@ -5,6 +5,7 @@ import (
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
otypes "github.com/traefik/traefik/v3/pkg/observability/types"
"github.com/traefik/traefik/v3/pkg/tls"
@@ -1230,3 +1231,113 @@ func Test_applyModel(t *testing.T) {
})
}
}
func Test_resolveHTTPTLSOptions(t *testing.T) {
testCases := []struct {
desc string
routers map[string]*dynamic.Router
expected map[string]string // router name -> ResolvedOptions
unexpectedRouters []string
}{
{
desc: "same host, different options, different entryPoints: no conflict",
routers: map[string]*dynamic.Router{
"router-a@file": {EntryPoints: []string{"ep-a"}, Rule: "Host(`example.com`)", TLS: &dynamic.RouterTLSConfig{Options: "optsA"}},
"router-b@file": {EntryPoints: []string{"ep-b"}, Rule: "Host(`example.com`)", TLS: &dynamic.RouterTLSConfig{Options: "optsB"}},
},
expected: map[string]string{
"router-a@file": "optsA@file",
"router-b@file": "optsB@file",
},
},
{
desc: "same host, different options, same entryPoint: conflict falls back to default",
routers: map[string]*dynamic.Router{
"router-a@file": {EntryPoints: []string{"ep-a"}, Rule: "Host(`example.com`)", TLS: &dynamic.RouterTLSConfig{Options: "optsA"}},
"router-b@file": {EntryPoints: []string{"ep-a"}, Rule: "Host(`example.com`)", TLS: &dynamic.RouterTLSConfig{Options: "optsB"}},
},
expected: map[string]string{
"ep-a-conflicted-router-a@file": "default",
"ep-a-conflicted-router-b@file": "default",
},
unexpectedRouters: []string{"router-a@file", "router-b@file"},
},
{
desc: "same host, same options, same entryPoint: keeps the configured options",
routers: map[string]*dynamic.Router{
"router-a@file": {EntryPoints: []string{"ep-a"}, Rule: "Host(`example.com`)", TLS: &dynamic.RouterTLSConfig{Options: "optsA"}},
"router-b@file": {EntryPoints: []string{"ep-a"}, Rule: "Host(`example.com`) && PathPrefix(`/foo`)", TLS: &dynamic.RouterTLSConfig{Options: "optsA"}},
},
expected: map[string]string{
"router-a@file": "optsA@file",
"router-b@file": "optsA@file",
},
},
{
desc: "router spanning two entryPoints, conflict on one only: router is duplicated",
routers: map[string]*dynamic.Router{
"shared@file": {EntryPoints: []string{"ep-a", "ep-b"}, Rule: "Host(`example.com`)", TLS: &dynamic.RouterTLSConfig{Options: "optsX"}},
"other@file": {EntryPoints: []string{"ep-a"}, Rule: "Host(`example.com`)", TLS: &dynamic.RouterTLSConfig{Options: "optsY"}},
},
expected: map[string]string{
"ep-a-conflicted-shared@file": "default", // conflicts with other@file on ep-a
"shared@file": "optsX@file", // alone on ep-b
"ep-a-conflicted-other@file": "default",
},
unexpectedRouters: []string{"other@file"},
},
{
desc: "no domain in rule, non-default options: forced to default and renamed",
routers: map[string]*dynamic.Router{
"router-a@file": {EntryPoints: []string{"ep-a"}, Rule: "PathPrefix(`/foo`)", TLS: &dynamic.RouterTLSConfig{Options: "optsA"}},
},
expected: map[string]string{
"ep-a-conflicted-router-a@file": "default",
},
unexpectedRouters: []string{"router-a@file"},
},
{
desc: "no domain in rule, implicit default options: not conflicting, keeps its name",
routers: map[string]*dynamic.Router{
"router-a@file": {EntryPoints: []string{"ep-a"}, Rule: "PathPrefix(`/foo`)", TLS: &dynamic.RouterTLSConfig{}},
},
expected: map[string]string{
"router-a@file": "default",
},
unexpectedRouters: []string{"ep-a-conflicted-router-a@file"},
},
{
desc: "no domain in rule, explicit default options: not conflicting, keeps its name",
routers: map[string]*dynamic.Router{
"router-a@file": {EntryPoints: []string{"ep-a"}, Rule: "PathPrefix(`/foo`)", TLS: &dynamic.RouterTLSConfig{
Options: "default",
}},
},
expected: map[string]string{
"router-a@file": "default",
},
unexpectedRouters: []string{"ep-a-conflicted-router-a@file"},
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
got := resolveHTTPTLSOptions(test.routers)
for name, want := range test.expected {
rt, ok := got[name]
require.True(t, ok, "router %q is missing", name)
require.NotNil(t, rt.TLS, "router %q has no TLS config", name)
assert.Equal(t, want, rt.TLS.ResolvedOptions, "router %q %v", name, rt.EntryPoints)
}
for _, name := range test.unexpectedRouters {
_, ok := got[name]
require.False(t, ok, "router %q is present", name)
}
})
}
}
+3 -1
View File
@@ -167,7 +167,9 @@ func (c *ConfigurationWatcher) applyConfigurations(ctx context.Context) {
conf := mergeConfiguration(newConfigs.DeepCopy(), c.defaultEntryPoints)
conf = applyModel(conf)
conf = resolveHTTPTLSOptions(conf)
if conf.HTTP != nil {
conf.HTTP.Routers = resolveHTTPTLSOptions(conf.HTTP.Routers)
}
for _, listener := range c.configurationListeners {
listener(conf)
+1 -2
View File
@@ -169,8 +169,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
}
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)
routerHTTPConfig.AddError(errors.New("router's TLSOptions configuration is conflicting with other routers on the same entrypoint and host, default TLS options will be used instead"), false)
}
// Even though the error is seemingly ignored (aside from logging it),