Apply the same approach as the rules system on the TLS configuration choice

Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com>
This commit is contained in:
Ludovic Fernandez 2022-02-10 10:42:07 +01:00 committed by GitHub
parent 4da33c2bc2
commit 0c83ee736c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 132 additions and 38 deletions

View file

@ -177,8 +177,8 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
// Domain Fronting // Domain Fronting
if !strings.EqualFold(host, serverName) { if !strings.EqualFold(host, serverName) {
tlsOptionSNI := findTLSOptionName(tlsOptionsForHost, serverName) tlsOptionHeader := findTLSOptionName(tlsOptionsForHost, host, true)
tlsOptionHeader := findTLSOptionName(tlsOptionsForHost, host) tlsOptionSNI := findTLSOptionName(tlsOptionsForHost, serverName, false)
if tlsOptionHeader != tlsOptionSNI { if tlsOptionHeader != tlsOptionSNI {
log.WithoutContext(). log.WithoutContext().
@ -322,16 +322,43 @@ func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouter
return tcp.NewChain().Extend(*mHandler).Then(sHandler) 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] tlsOptions, ok := tlsOptionsForHost[host]
if ok { if ok {
return tlsOptions 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 { if ok {
return tlsOptions return tlsOptions
} }
return traefiktls.DefaultTLSConfigName return ""
} }

View file

@ -59,7 +59,6 @@ func TestRuntimeConfiguration(t *testing.T) {
}, },
"bar": { "bar": {
TCPRouter: &dynamic.TCPRouter{ TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"}, EntryPoints: []string{"web"},
Service: "foo-service", Service: "foo-service",
Rule: "HostSNI(`foo.bar`)", Rule: "HostSNI(`foo.bar`)",
@ -136,7 +135,6 @@ func TestRuntimeConfiguration(t *testing.T) {
}, },
"bar": { "bar": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: []string{"web"},
Service: "foo-service", Service: "foo-service",
Rule: "Host(`bar.foo`) && PathPrefix(`/path`)", Rule: "Host(`bar.foo`) && PathPrefix(`/path`)",
@ -240,7 +238,6 @@ func TestRuntimeConfiguration(t *testing.T) {
}, },
"bar": { "bar": {
TCPRouter: &dynamic.TCPRouter{ TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"}, EntryPoints: []string{"web"},
Service: "foo-service", Service: "foo-service",
Rule: "HostSNI(`foo.bar`)", Rule: "HostSNI(`foo.bar`)",
@ -340,9 +337,26 @@ func TestRuntimeConfiguration(t *testing.T) {
} }
func TestDomainFronting(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 { tests := []struct {
desc string desc string
routers map[string]*runtime.RouterInfo routers map[string]*runtime.RouterInfo
tlsOptions map[string]traefiktls.Options
host string
ServerName string
expectedStatus int expectedStatus int
}{ }{
{ {
@ -350,7 +364,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -359,12 +373,15 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@file": { "router-2@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{}, TLS: &dynamic.RouterTLSConfig{},
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusMisdirectedRequest, expectedStatus: http.StatusMisdirectedRequest,
}, },
{ {
@ -372,7 +389,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -381,7 +398,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@file": { "router-2@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -389,6 +406,9 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusOK, expectedStatus: http.StatusOK,
}, },
{ {
@ -396,7 +416,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -405,7 +425,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@file": { "router-2@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`) && PathPrefix(`/foo`)", Rule: "Host(`host1.local`) && PathPrefix(`/foo`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "default", Options: "default",
@ -414,7 +434,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-3@file": { "router-3@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -422,6 +442,9 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusMisdirectedRequest, expectedStatus: http.StatusMisdirectedRequest,
}, },
{ {
@ -429,7 +452,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -438,7 +461,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@file": { "router-2@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`) && PathPrefix(`/bar`)", Rule: "Host(`host1.local`) && PathPrefix(`/bar`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -447,7 +470,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-3@file": { "router-3@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -455,6 +478,9 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusOK, expectedStatus: http.StatusOK,
}, },
{ {
@ -462,7 +488,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -471,7 +497,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@crd": { "router-2@crd": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1", Options: "host1",
@ -479,6 +505,9 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusMisdirectedRequest, expectedStatus: http.StatusMisdirectedRequest,
}, },
{ {
@ -486,7 +515,7 @@ func TestDomainFronting(t *testing.T) {
routers: map[string]*runtime.RouterInfo{ routers: map[string]*runtime.RouterInfo{
"router-1@file": { "router-1@file": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host1.local`)", Rule: "Host(`host1.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1@crd", Options: "host1@crd",
@ -495,7 +524,7 @@ func TestDomainFronting(t *testing.T) {
}, },
"router-2@crd": { "router-2@crd": {
Router: &dynamic.Router{ Router: &dynamic.Router{
EntryPoints: []string{"web"}, EntryPoints: entryPoints,
Rule: "Host(`host2.local`)", Rule: "Host(`host2.local`)",
TLS: &dynamic.RouterTLSConfig{ TLS: &dynamic.RouterTLSConfig{
Options: "host1@crd", Options: "host1@crd",
@ -503,25 +532,63 @@ func TestDomainFronting(t *testing.T) {
}, },
}, },
}, },
tlsOptions: tlsOptionsBase,
host: "host1.local",
ServerName: "host2.local",
expectedStatus: http.StatusOK, 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 { for _, test := range tests {
t.Run(test.desc, func(t *testing.T) { 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{ conf := &runtime.Configuration{
Routers: test.routers, Routers: test.routers,
} }
@ -529,7 +596,7 @@ func TestDomainFronting(t *testing.T) {
serviceManager := tcp.NewManager(conf) serviceManager := tcp.NewManager(conf)
tlsManager := traefiktls.NewManager() 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{ httpsHandler := map[string]http.Handler{
"web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}), "web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}),
@ -545,9 +612,9 @@ func TestDomainFronting(t *testing.T) {
require.True(t, ok) require.True(t, ok)
req := httptest.NewRequest(http.MethodGet, "/", nil) req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Host = "host1.local" req.Host = test.host
req.TLS = &tls.ConnectionState{ req.TLS = &tls.ConnectionState{
ServerName: "host2.local", ServerName: test.ServerName,
} }
rw := httptest.NewRecorder() rw := httptest.NewRecorder()