diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index abe31d180..277080835 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -177,8 +177,8 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string // Domain Fronting if !strings.EqualFold(host, serverName) { - tlsOptionSNI := findTLSOptionName(tlsOptionsForHost, serverName) - tlsOptionHeader := findTLSOptionName(tlsOptionsForHost, host) + tlsOptionHeader := findTLSOptionName(tlsOptionsForHost, host, true) + tlsOptionSNI := findTLSOptionName(tlsOptionsForHost, serverName, false) if tlsOptionHeader != tlsOptionSNI { log.WithoutContext(). @@ -322,16 +322,43 @@ func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouter return tcp.NewChain().Extend(*mHandler).Then(sHandler) } -func findTLSOptionName(tlsOptionsForHost map[string]string, host string) string { +func findTLSOptionName(tlsOptionsForHost map[string]string, host string, fqdn bool) string { + name := findTLSOptName(tlsOptionsForHost, host, fqdn) + if name != "" { + return name + } + + name = findTLSOptName(tlsOptionsForHost, strings.ToLower(host), fqdn) + if name != "" { + return name + } + + return traefiktls.DefaultTLSConfigName +} + +func findTLSOptName(tlsOptionsForHost map[string]string, host string, fqdn bool) string { tlsOptions, ok := tlsOptionsForHost[host] if ok { return tlsOptions } - tlsOptions, ok = tlsOptionsForHost[strings.ToLower(host)] + if !fqdn { + return "" + } + + if last := len(host) - 1; last >= 0 && host[last] == '.' { + tlsOptions, ok = tlsOptionsForHost[host[:last]] + if ok { + return tlsOptions + } + + return "" + } + + tlsOptions, ok = tlsOptionsForHost[host+"."] if ok { return tlsOptions } - return traefiktls.DefaultTLSConfigName + return "" } diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index 156721aa6..cdf7c3bbc 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -59,7 +59,6 @@ func TestRuntimeConfiguration(t *testing.T) { }, "bar": { TCPRouter: &dynamic.TCPRouter{ - EntryPoints: []string{"web"}, Service: "foo-service", Rule: "HostSNI(`foo.bar`)", @@ -136,7 +135,6 @@ func TestRuntimeConfiguration(t *testing.T) { }, "bar": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, Service: "foo-service", Rule: "Host(`bar.foo`) && PathPrefix(`/path`)", @@ -240,7 +238,6 @@ func TestRuntimeConfiguration(t *testing.T) { }, "bar": { TCPRouter: &dynamic.TCPRouter{ - EntryPoints: []string{"web"}, Service: "foo-service", Rule: "HostSNI(`foo.bar`)", @@ -340,9 +337,26 @@ func TestRuntimeConfiguration(t *testing.T) { } func TestDomainFronting(t *testing.T) { + tlsOptionsBase := map[string]traefiktls.Options{ + "default": { + MinVersion: "VersionTLS10", + }, + "host1@file": { + MinVersion: "VersionTLS12", + }, + "host1@crd": { + MinVersion: "VersionTLS12", + }, + } + + entryPoints := []string{"web"} + tests := []struct { desc string routers map[string]*runtime.RouterInfo + tlsOptions map[string]traefiktls.Options + host string + ServerName string expectedStatus int }{ { @@ -350,7 +364,7 @@ func TestDomainFronting(t *testing.T) { routers: map[string]*runtime.RouterInfo{ "router-1@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host1.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -359,12 +373,15 @@ func TestDomainFronting(t *testing.T) { }, "router-2@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host2.local`)", TLS: &dynamic.RouterTLSConfig{}, }, }, }, + tlsOptions: tlsOptionsBase, + host: "host1.local", + ServerName: "host2.local", expectedStatus: http.StatusMisdirectedRequest, }, { @@ -372,7 +389,7 @@ func TestDomainFronting(t *testing.T) { routers: map[string]*runtime.RouterInfo{ "router-1@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host1.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -381,7 +398,7 @@ func TestDomainFronting(t *testing.T) { }, "router-2@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host2.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -389,6 +406,9 @@ func TestDomainFronting(t *testing.T) { }, }, }, + tlsOptions: tlsOptionsBase, + host: "host1.local", + ServerName: "host2.local", expectedStatus: http.StatusOK, }, { @@ -396,7 +416,7 @@ func TestDomainFronting(t *testing.T) { routers: map[string]*runtime.RouterInfo{ "router-1@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host1.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -405,7 +425,7 @@ func TestDomainFronting(t *testing.T) { }, "router-2@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host1.local`) && PathPrefix(`/foo`)", TLS: &dynamic.RouterTLSConfig{ Options: "default", @@ -414,7 +434,7 @@ func TestDomainFronting(t *testing.T) { }, "router-3@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host2.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -422,6 +442,9 @@ func TestDomainFronting(t *testing.T) { }, }, }, + tlsOptions: tlsOptionsBase, + host: "host1.local", + ServerName: "host2.local", expectedStatus: http.StatusMisdirectedRequest, }, { @@ -429,7 +452,7 @@ func TestDomainFronting(t *testing.T) { routers: map[string]*runtime.RouterInfo{ "router-1@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host1.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -438,7 +461,7 @@ func TestDomainFronting(t *testing.T) { }, "router-2@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host1.local`) && PathPrefix(`/bar`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -447,7 +470,7 @@ func TestDomainFronting(t *testing.T) { }, "router-3@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host2.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -455,6 +478,9 @@ func TestDomainFronting(t *testing.T) { }, }, }, + tlsOptions: tlsOptionsBase, + host: "host1.local", + ServerName: "host2.local", expectedStatus: http.StatusOK, }, { @@ -462,7 +488,7 @@ func TestDomainFronting(t *testing.T) { routers: map[string]*runtime.RouterInfo{ "router-1@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host1.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -471,7 +497,7 @@ func TestDomainFronting(t *testing.T) { }, "router-2@crd": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host2.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1", @@ -479,6 +505,9 @@ func TestDomainFronting(t *testing.T) { }, }, }, + tlsOptions: tlsOptionsBase, + host: "host1.local", + ServerName: "host2.local", expectedStatus: http.StatusMisdirectedRequest, }, { @@ -486,7 +515,7 @@ func TestDomainFronting(t *testing.T) { routers: map[string]*runtime.RouterInfo{ "router-1@file": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host1.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1@crd", @@ -495,7 +524,7 @@ func TestDomainFronting(t *testing.T) { }, "router-2@crd": { Router: &dynamic.Router{ - EntryPoints: []string{"web"}, + EntryPoints: entryPoints, Rule: "Host(`host2.local`)", TLS: &dynamic.RouterTLSConfig{ Options: "host1@crd", @@ -503,25 +532,63 @@ func TestDomainFronting(t *testing.T) { }, }, }, + tlsOptions: tlsOptionsBase, + host: "host1.local", + ServerName: "host2.local", expectedStatus: http.StatusOK, }, + { + desc: "Request is misdirected when server name is empty and the host name is an FQDN, but router's rule is not", + routers: map[string]*runtime.RouterInfo{ + "router-1@file": { + Router: &dynamic.Router{ + EntryPoints: entryPoints, + Rule: "Host(`host1.local`)", + TLS: &dynamic.RouterTLSConfig{ + Options: "host1@file", + }, + }, + }, + }, + tlsOptions: map[string]traefiktls.Options{ + "default": { + MinVersion: "VersionTLS13", + }, + "host1@file": { + MinVersion: "VersionTLS12", + }, + }, + host: "host1.local.", + expectedStatus: http.StatusMisdirectedRequest, + }, + { + desc: "Request is misdirected when server name is empty and the host name is not FQDN, but router's rule is", + routers: map[string]*runtime.RouterInfo{ + "router-1@file": { + Router: &dynamic.Router{ + EntryPoints: entryPoints, + Rule: "Host(`host1.local.`)", + TLS: &dynamic.RouterTLSConfig{ + Options: "host1@file", + }, + }, + }, + }, + tlsOptions: map[string]traefiktls.Options{ + "default": { + MinVersion: "VersionTLS13", + }, + "host1@file": { + MinVersion: "VersionTLS12", + }, + }, + host: "host1.local", + expectedStatus: http.StatusMisdirectedRequest, + }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - entryPoints := []string{"web"} - tlsOptions := map[string]traefiktls.Options{ - "default": { - MinVersion: "VersionTLS10", - }, - "host1@file": { - MinVersion: "VersionTLS12", - }, - "host1@crd": { - MinVersion: "VersionTLS12", - }, - } - conf := &runtime.Configuration{ Routers: test.routers, } @@ -529,7 +596,7 @@ func TestDomainFronting(t *testing.T) { serviceManager := tcp.NewManager(conf) tlsManager := traefiktls.NewManager() - tlsManager.UpdateConfigs(context.Background(), map[string]traefiktls.Store{}, tlsOptions, []*traefiktls.CertAndStores{}) + tlsManager.UpdateConfigs(context.Background(), map[string]traefiktls.Store{}, test.tlsOptions, []*traefiktls.CertAndStores{}) httpsHandler := map[string]http.Handler{ "web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}), @@ -545,9 +612,9 @@ func TestDomainFronting(t *testing.T) { require.True(t, ok) req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Host = "host1.local" + req.Host = test.host req.TLS = &tls.ConnectionState{ - ServerName: "host2.local", + ServerName: test.ServerName, } rw := httptest.NewRecorder()