Revert domain fronting fix
* revert domain fronting changes * reintroduce HostHeader rule * add doc for removals
This commit is contained in:
parent
77a0cef9ce
commit
0e97a3becd
20 changed files with 69 additions and 430 deletions
|
@ -79,12 +79,6 @@ type CertificateResolver struct {
|
|||
type Global struct {
|
||||
CheckNewVersion bool `description:"Periodically check if a new version has been released." json:"checkNewVersion,omitempty" toml:"checkNewVersion,omitempty" yaml:"checkNewVersion,omitempty" label:"allowEmpty" export:"true"`
|
||||
SendAnonymousUsage bool `description:"Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default." json:"sendAnonymousUsage,omitempty" toml:"sendAnonymousUsage,omitempty" yaml:"sendAnonymousUsage,omitempty" label:"allowEmpty" export:"true"`
|
||||
InsecureSNI bool `description:"Allow domain fronting. If the option is not specified, it will be enabled by default." json:"insecureSNI" toml:"insecureSNI" yaml:"insecureSNI" label:"allowEmpty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (a *Global) SetDefaults() {
|
||||
a.InsecureSNI = true
|
||||
}
|
||||
|
||||
// ServersTransport options to configure communication between Traefik and the servers.
|
||||
|
|
|
@ -12,9 +12,8 @@ import (
|
|||
)
|
||||
|
||||
var funcs = map[string]func(*mux.Route, ...string) error{
|
||||
"Host": hostSecure,
|
||||
"Host": host,
|
||||
"HostHeader": host,
|
||||
"HostSNI": hostSNI,
|
||||
"HostRegexp": hostRegexp,
|
||||
"Path": path,
|
||||
"PathPrefix": pathPrefix,
|
||||
|
@ -24,18 +23,6 @@ var funcs = map[string]func(*mux.Route, ...string) error{
|
|||
"Query": query,
|
||||
}
|
||||
|
||||
// EnableDomainFronting initialize the matcher functions to used on routers.
|
||||
// InsecureSNI defines if the domain fronting is allowed.
|
||||
func EnableDomainFronting(ok bool) {
|
||||
if ok {
|
||||
log.WithoutContext().Warn("With insecureSNI enabled, router rules do not prevent domain fronting techniques. Please use `HostHeader` and `HostSNI` rules if domain fronting is not desired.")
|
||||
funcs["Host"] = host
|
||||
return
|
||||
}
|
||||
|
||||
funcs["Host"] = hostSecure
|
||||
}
|
||||
|
||||
// Router handle routing with rules.
|
||||
type Router struct {
|
||||
*mux.Router
|
||||
|
@ -112,125 +99,46 @@ func host(route *mux.Route, hosts ...string) error {
|
|||
}
|
||||
|
||||
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
|
||||
return matchHost(req, true, hosts...)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
reqHost := requestdecorator.GetCanonizedHost(req.Context())
|
||||
if len(reqHost) == 0 {
|
||||
log.FromContext(req.Context()).Warnf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
|
||||
return false
|
||||
}
|
||||
|
||||
func matchHost(req *http.Request, insecureSNI bool, hosts ...string) bool {
|
||||
logger := log.FromContext(req.Context())
|
||||
flatH := requestdecorator.GetCNAMEFlatten(req.Context())
|
||||
if len(flatH) > 0 {
|
||||
for _, host := range hosts {
|
||||
if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) {
|
||||
return true
|
||||
}
|
||||
log.FromContext(req.Context()).Debugf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
reqHost := requestdecorator.GetCanonizedHost(req.Context())
|
||||
if len(reqHost) == 0 {
|
||||
logger.Warnf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
|
||||
return false
|
||||
}
|
||||
|
||||
flatH := requestdecorator.GetCNAMEFlatten(req.Context())
|
||||
if len(flatH) > 0 {
|
||||
for _, host := range hosts {
|
||||
if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) {
|
||||
if reqHost == host {
|
||||
return true
|
||||
}
|
||||
logger.Debugf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if reqHost == host {
|
||||
logHostSNI(insecureSNI, req, reqHost)
|
||||
return true
|
||||
}
|
||||
// Check for match on trailing period on host
|
||||
if last := len(host) - 1; last >= 0 && host[last] == '.' {
|
||||
h := host[:last]
|
||||
if reqHost == h {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for match on trailing period on host
|
||||
if last := len(host) - 1; last >= 0 && host[last] == '.' {
|
||||
h := host[:last]
|
||||
if reqHost == h {
|
||||
logHostSNI(insecureSNI, req, reqHost)
|
||||
return true
|
||||
// Check for match on trailing period on request
|
||||
if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' {
|
||||
h := reqHost[:last]
|
||||
if h == host {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for match on trailing period on request
|
||||
if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' {
|
||||
h := reqHost[:last]
|
||||
if h == host {
|
||||
logHostSNI(insecureSNI, req, reqHost)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func logHostSNI(insecureSNI bool, req *http.Request, reqHost string) {
|
||||
if insecureSNI && req.TLS != nil && !strings.EqualFold(reqHost, req.TLS.ServerName) {
|
||||
log.FromContext(req.Context()).Debugf("Router reached with Host(%q) different from SNI(%q)", reqHost, req.TLS.ServerName)
|
||||
}
|
||||
}
|
||||
|
||||
func hostSNI(route *mux.Route, hosts ...string) error {
|
||||
for i, host := range hosts {
|
||||
hosts[i] = strings.ToLower(host)
|
||||
}
|
||||
|
||||
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
|
||||
return matchSNI(req, hosts...)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func matchSNI(req *http.Request, hosts ...string) bool {
|
||||
if req.TLS == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if req.TLS.ServerName == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if strings.EqualFold(req.TLS.ServerName, host) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for match on trailing period on host
|
||||
if last := len(host) - 1; last >= 0 && host[last] == '.' {
|
||||
h := host[:last]
|
||||
if strings.EqualFold(req.TLS.ServerName, h) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check for match on trailing period on request
|
||||
if last := len(req.TLS.ServerName) - 1; last >= 0 && req.TLS.ServerName[last] == '.' {
|
||||
h := req.TLS.ServerName[:last]
|
||||
if strings.EqualFold(h, host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func hostSecure(route *mux.Route, hosts ...string) error {
|
||||
for i, host := range hosts {
|
||||
hosts[i] = strings.ToLower(host)
|
||||
}
|
||||
|
||||
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
|
||||
for _, host := range hosts {
|
||||
if matchSNI(req, host) && matchHost(req, false, host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,14 @@ func Test_addRoute(t *testing.T) {
|
|||
"http://localhost/foo": http.StatusOK,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "HostHeader equivalent to Host",
|
||||
rule: "HostHeader(`localhost`)",
|
||||
expected: map[string]int{
|
||||
"http://localhost/foo": http.StatusOK,
|
||||
"http://bar/foo": http.StatusNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Host with trailing period in rule",
|
||||
rule: "Host(`localhost.`)",
|
||||
|
|
|
@ -2,7 +2,6 @@ package router
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -15,7 +14,6 @@ import (
|
|||
"github.com/containous/traefik/v2/pkg/middlewares/accesslog"
|
||||
"github.com/containous/traefik/v2/pkg/middlewares/requestdecorator"
|
||||
"github.com/containous/traefik/v2/pkg/responsemodifiers"
|
||||
"github.com/containous/traefik/v2/pkg/rules"
|
||||
"github.com/containous/traefik/v2/pkg/server/middleware"
|
||||
"github.com/containous/traefik/v2/pkg/server/service"
|
||||
"github.com/containous/traefik/v2/pkg/testhelpers"
|
||||
|
@ -314,225 +312,6 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRouterManager_SNI(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
t.Cleanup(func() { server.Close() })
|
||||
|
||||
type expectedResult struct {
|
||||
StatusCode int
|
||||
RequestHeaders map[string]string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
routersConfig map[string]*dynamic.Router
|
||||
serviceConfig map[string]*dynamic.Service
|
||||
middlewaresConfig map[string]*dynamic.Middleware
|
||||
entryPoint string
|
||||
sni string
|
||||
insecureSNI bool
|
||||
expected expectedResult
|
||||
}{
|
||||
{
|
||||
desc: "Insecure SNI without TLS",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
"foo@provider-1": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Priority: 0,
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service@provider-1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
insecureSNI: true,
|
||||
entryPoint: "web",
|
||||
expected: expectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "Secure SNI without TLS",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
"foo@provider-1": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Priority: 0,
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service@provider-1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoint: "web",
|
||||
expected: expectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "Secure SNI with TLS without sni",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
"foo@provider-1": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Priority: 0,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service@provider-1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoint: "websecure",
|
||||
expected: expectedResult{StatusCode: http.StatusNotFound},
|
||||
},
|
||||
{
|
||||
desc: "Secure SNI with TLS with sni request",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
"foo@provider-1": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Priority: 0,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service@provider-1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoint: "websecure",
|
||||
sni: "foo.bar",
|
||||
expected: expectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "Insecure SNI with TLS without sni",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
"foo@provider-1": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
Priority: 0,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service@provider-1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoint: "websecure",
|
||||
insecureSNI: true,
|
||||
expected: expectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
{
|
||||
desc: "Secure SNI with TLS with sni uppercase",
|
||||
routersConfig: map[string]*dynamic.Router{
|
||||
"foo@provider-1": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`Foo.bar`)",
|
||||
Priority: 0,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service@provider-1": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: server.URL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
entryPoint: "websecure",
|
||||
sni: "Foo.bar",
|
||||
expected: expectedResult{StatusCode: http.StatusOK},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
rtConf := runtime.NewConfig(dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Services: test.serviceConfig,
|
||||
Routers: test.routersConfig,
|
||||
Middlewares: test.middlewaresConfig,
|
||||
},
|
||||
})
|
||||
|
||||
rules.EnableDomainFronting(test.insecureSNI)
|
||||
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager)
|
||||
responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares)
|
||||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), []string{test.entryPoint}, test.entryPoint == "websecure")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "https://foo.bar/", nil)
|
||||
|
||||
if test.entryPoint == "websecure" {
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
|
||||
if test.sni != "" {
|
||||
req.TLS.ServerName = test.sni
|
||||
}
|
||||
}
|
||||
|
||||
reqHost := requestdecorator.New(nil)
|
||||
reqHost.ServeHTTP(w, req, handlers[test.entryPoint].ServeHTTP)
|
||||
|
||||
assert.Equal(t, test.expected.StatusCode, w.Code)
|
||||
|
||||
for key, value := range test.expected.RequestHeaders {
|
||||
assert.Equal(t, value, req.Header.Get(key))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccessLog(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
|
||||
|
@ -908,6 +687,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
|
||||
|
||||
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
|
||||
// even though rtConf was passed by argument to the manager builders above,
|
||||
|
|
|
@ -102,7 +102,7 @@ func (r *Router) AddRouteTLS(sniHost string, target Handler, config *tls.Config)
|
|||
})
|
||||
}
|
||||
|
||||
// AddRouteHTTPTLS defines the matching tlsConfig for a given sniHost.
|
||||
// AddRouteHTTPTLS defines a handler for a given sniHost and sets the matching tlsConfig.
|
||||
func (r *Router) AddRouteHTTPTLS(sniHost string, config *tls.Config) {
|
||||
if r.hostHTTPTLSConfig == nil {
|
||||
r.hostHTTPTLSConfig = map[string]*tls.Config{}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue