diff --git a/CHANGELOG.md b/CHANGELOG.md index e4a0ef5ce..57ee548bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## [v2.2.5](https://github.com/containous/traefik/tree/v2.2.5) (2020-07-13) +[All Commits](https://github.com/containous/traefik/compare/v2.2.4...v2.2.5) + +**Bug fixes:** +- **[k8s,k8s/crd]** fix k8s crd to read contentType middleware into dynamic config ([#7034](https://github.com/containous/traefik/pull/7034) by [johnpekcan](https://github.com/johnpekcan)) +- **[rules,server,tls]** Revert domain fronting fix ([#7039](https://github.com/containous/traefik/pull/7039) by [rtribotte](https://github.com/rtribotte)) +- **[tls]** Fix default value for InsecureSNI when global is not set ([#7037](https://github.com/containous/traefik/pull/7037) by [juliens](https://github.com/juliens)) + +## [v2.2.4](https://github.com/containous/traefik/tree/v2.2.4) (2020-07-10) +[All Commits](https://github.com/containous/traefik/compare/v2.2.3...v2.2.4) + +**Bug fixes:** +- **[tls]** Change the default value of insecureSNI ([#7027](https://github.com/containous/traefik/pull/7027) by [jbdoumenjou](https://github.com/jbdoumenjou)) + ## [v2.2.3](https://github.com/containous/traefik/tree/v2.2.3) (2020-07-09) [All Commits](https://github.com/containous/traefik/compare/v2.2.2...v2.2.3) diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index ec114361a..0551ddabe 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -25,7 +25,6 @@ import ( "github.com/containous/traefik/v2/pkg/provider/acme" "github.com/containous/traefik/v2/pkg/provider/aggregator" "github.com/containous/traefik/v2/pkg/provider/traefik" - "github.com/containous/traefik/v2/pkg/rules" "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/server" "github.com/containous/traefik/v2/pkg/server/middleware" @@ -162,8 +161,6 @@ func runCmd(staticConfiguration *static.Configuration) error { } func setupServer(staticConfiguration *static.Configuration) (*server.Server, error) { - rules.EnableDomainFronting(staticConfiguration.Global.InsecureSNI) - providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers) // adds internal provider diff --git a/docs/content/https/tls.md b/docs/content/https/tls.md index 1ba56c62b..40d0585b4 100644 --- a/docs/content/https/tls.md +++ b/docs/content/https/tls.md @@ -130,20 +130,6 @@ tls: If no default certificate is provided, Traefik generates and uses a self-signed certificate. -## Domain fronting - -Basically, [domain fronting](https://en.wikipedia.org/wiki/Domain_fronting) is a technique that allows to open a -connection with a specific domain name, thanks to the -[Server Name Indication](https://en.wikipedia.org/wiki/Server_Name_Indication), then access a service with another -domain set in the HTTP `Host` header. - -Since the `v2.2.2`, Traefik avoids (by default) using domain fronting. -As it is valid for advanced use cases, the `HostHeader` and `HostSNI` [rules](../routing/routers/index.md#rule) allow -to fine tune the routing with the `Server Name Indication` and `Host header` value. - -If you encounter routing issues with a previously working configuration, please refer to the -[migration guide](../migration/v2.md) to update your configuration. - ## TLS Options The TLS options allow one to configure some parameters of the TLS connection. diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 6e056ab37..ee0ae85d3 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -1,116 +1,16 @@ # Migration: Steps needed between the versions -## v2.x to v2.2.2 +## v2.2.2 to v2.2.5 -### Domain fronting +### InsecureSNI removal -In `v2.2.2` we introduced the ability to avoid [Domain fronting](https://en.wikipedia.org/wiki/Domain_fronting), -and enabled it by default for [https routers](../routing/routers/index.md#rule) configured with ```Host(`something`)```. +In `v2.2.2` we introduced a new flag (`insecureSNI`) which was available as a global option to disable domain fronting. +Since `v2.2.5` this global option has been removed, and you should not use it anymore. -!!! example "Allow Domain Fronting on a Specific Router" - - !!! info "Before v2.2.2" - - ```yaml tab="Docker" - labels: - - "traefik.http.routers.router0.rule=Host(`test.localhost`)" - ``` - - ```yaml tab="K8s Ingress" - apiVersion: traefik.containo.us/v1alpha1 - kind: IngressRoute - metadata: - name: ingressroutebar - - spec: - entryPoints: - - http - routes: - - match: Host(`test.localhost`) - kind: Rule - services: - - name: server0 - port: 80 - - name: server1 - port: 80 - ``` - - ```toml tab="File (TOML)" - [http.routers.router0] - rule = "Host(`test.localhost`)" - service = "my-service" - ``` - - ```toml tab="File (YAML)" - http: - routers: - router0: - rule: "Host(`test.localhost`)" - service: my-service - ``` +### HostSNI rule matcher removal - !!! info "v2.2.2" - - ```yaml tab="Docker" - labels: - - "traefik.http.routers.router0.rule=HostHeader(`test.localhost`)" - ``` - - ```yaml tab="K8s Ingress" - apiVersion: traefik.containo.us/v1alpha1 - kind: IngressRoute - metadata: - name: ingressroutebar - - spec: - entryPoints: - - http - routes: - - match: HostHeader(`test.localhost`) - kind: Rule - services: - - name: server0 - port: 80 - - name: server1 - port: 80 - ``` - - ```toml tab="File (TOML)" - [http.routers.router0] - rule = "HostHeader(`test.localhost`)" - service = "my-service" - ``` - - ```toml tab="File (YAML)" - http: - routers: - router0: - rule: "HostHeader(`test.localhost`)" - service: my-service - ``` - -As a fallback, a new flag is available as a global option: - -!!! example "Enabling Domain Fronting for All Routers" - - ```toml tab="File (TOML)" - # Static configuration - [global] - # Enabling domain fronting - insecureSNI = true - ``` - - ```yaml tab="File (YAML)" - # Static configuration - global: - # Enabling domain fronting - insecureSNI: true - ``` - - ```bash tab="CLI" - # Enabling domain fronting - --global.insecureSNI - ``` +In `v2.2.2` we introduced a new rule matcher (`HostSNI`) which was allowing to match the Server Name Indication at the router level. +Since `v2.2.5` this rule has been removed, and you should not use it anymore. ## v2.0 to v2.1 diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 9f14b83c4..7afe56d75 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -162,9 +162,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I `--global.checknewversion`: Periodically check if a new version has been released. (Default: ```false```) -`--global.insecuresni`: -Allow domain fronting. If the option is not specified, it will be disabled by default. (Default: ```false```) - `--global.sendanonymoususage`: Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index b497abd99..bafef49ad 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -162,9 +162,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I `TRAEFIK_GLOBAL_CHECKNEWVERSION`: Periodically check if a new version has been released. (Default: ```false```) -`TRAEFIK_GLOBAL_INSECURESNI`: -Allow domain fronting. If the option is not specified, it will be disabled by default. (Default: ```false```) - `TRAEFIK_GLOBAL_SENDANONYMOUSUSAGE`: Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index c90dc95c5..2e689e038 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -1,7 +1,6 @@ [global] checkNewVersion = true sendAnonymousUsage = true - insecureSNI = false [serversTransport] insecureSkipVerify = true @@ -153,7 +152,7 @@ username = "foobar" password = "foobar" [providers.consul] - rootKey = "traefik" + rootKey = "foobar" endpoints = ["foobar", "foobar"] username = "foobar" password = "foobar" @@ -164,7 +163,7 @@ key = "foobar" insecureSkipVerify = true [providers.etcd] - rootKey = "traefik" + rootKey = "foobar" endpoints = ["foobar", "foobar"] username = "foobar" password = "foobar" @@ -175,7 +174,7 @@ key = "foobar" insecureSkipVerify = true [providers.zooKeeper] - rootKey = "traefik" + rootKey = "foobar" endpoints = ["foobar", "foobar"] username = "foobar" password = "foobar" @@ -186,7 +185,7 @@ key = "foobar" insecureSkipVerify = true [providers.redis] - rootKey = "traefik" + rootKey = "foobar" endpoints = ["foobar", "foobar"] username = "foobar" password = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 85d0888f5..f8eea1ef5 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -1,8 +1,6 @@ global: checkNewVersion: true sendAnonymousUsage: true - insecureSNI: false - serversTransport: insecureSkipVerify: true rootCAs: @@ -127,7 +125,7 @@ providers: - foobar labelSelector: foobar ingressClass: foobar - throttleDuration: 10s + throttleDuration: 42s rest: insecure: true rancher: @@ -164,7 +162,7 @@ providers: username: foobar password: foobar consul: - rootKey: traefik + rootKey: foobar endpoints: - foobar - foobar @@ -177,7 +175,7 @@ providers: key: foobar insecureSkipVerify: true etcd: - rootKey: traefik + rootKey: foobar endpoints: - foobar - foobar @@ -190,10 +188,10 @@ providers: key: foobar insecureSkipVerify: true zooKeeper: - rootKey: traefik + rootKey: foobar endpoints: - - foobar - - foobar + - foobar + - foobar username: foobar password: foobar tls: @@ -203,10 +201,10 @@ providers: key: foobar insecureSkipVerify: true redis: - rootKey: traefik + rootKey: foobar endpoints: - - foobar - - foobar + - foobar + - foobar username: foobar password: foobar tls: diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index eb97bf151..81d5a4083 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -228,18 +228,17 @@ If the rule is verified, the router becomes active, calls middlewares, and then The table below lists all the available matchers: -| Rule | Description | -|------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ```Headers(`key`, `value`)``` | Check if there is a key `key`defined in the headers, with the value `value` | -| ```HeadersRegexp(`key`, `regexp`)``` | Check if there is a key `key`defined in the headers, with a value that matches the regular expression `regexp` | -| ```Host(`example.com`, ...)``` | By default, is equivalent to `HostHeader` **AND** `HostSNI` rules. See [Domain Fronting](../../https/tls.md#domain-fronting) and the [migration guide](../../migration/v2.md#domain-fronting) for more details. | -| ```HostHeader(`example.com`, ...)``` | Check if the request domain (host header value) targets one of the given `domains`. | -| ```HostSNI(`example.com`, ...)``` | Check if the [Server Name Indication](https://en.wikipedia.org/wiki/Server_Name_Indication) corresponds to the given `domains`. | -| ```HostRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Check if the request domain matches the given `regexp`. | -| ```Method(`GET`, ...)``` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) | -| ```Path(`/path`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`, ...)``` | Match exact request path. It accepts a sequence of literal and regular expression paths. | -| ```PathPrefix(`/products/`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`)``` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. | -| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. | +| Rule | Description | +|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------| +| ```Headers(`key`, `value`)``` | Check if there is a key `key`defined in the headers, with the value `value` | +| ```HeadersRegexp(`key`, `regexp`)``` | Check if there is a key `key`defined in the headers, with a value that matches the regular expression `regexp` | +| ```Host(`example.com`, ...)``` | Check if the request domain (host header value) targets one of the given `domains`. | +| ```HostHeader(`example.com`, ...)``` | Check if the request domain (host header value) targets one of the given `domains`. | +| ```HostRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Check if the request domain matches the given `regexp`. | +| ```Method(`GET`, ...)``` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) | +| ```Path(`/path`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`, ...)``` | Match exact request path. It accepts a sequence of literal and regular expression paths. | +| ```PathPrefix(`/products/`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`)``` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. | +| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. | !!! important "Regexp Syntax" diff --git a/integration/fixtures/grpc/config.toml b/integration/fixtures/grpc/config.toml index c75bbebd4..9d448ae5b 100644 --- a/integration/fixtures/grpc/config.toml +++ b/integration/fixtures/grpc/config.toml @@ -22,7 +22,7 @@ [http.routers] [http.routers.router1] - rule = "Host(`localhost`)" + rule = "Host(`127.0.0.1`)" service = "service1" [http.routers.router1.tls] diff --git a/integration/fixtures/grpc/config_h2c_termination.toml b/integration/fixtures/grpc/config_h2c_termination.toml index f9ecc99d6..a51bcc21b 100644 --- a/integration/fixtures/grpc/config_h2c_termination.toml +++ b/integration/fixtures/grpc/config_h2c_termination.toml @@ -19,7 +19,7 @@ [http.routers] [http.routers.router1] - rule = "Host(`localhost`)" + rule = "Host(`127.0.0.1`)" service = "service1" [http.routers.router1.tls] diff --git a/integration/fixtures/grpc/config_insecure.toml b/integration/fixtures/grpc/config_insecure.toml index ecd179e5a..264360ad5 100644 --- a/integration/fixtures/grpc/config_insecure.toml +++ b/integration/fixtures/grpc/config_insecure.toml @@ -22,7 +22,7 @@ [http.routers] [http.routers.router1] - rule = "Host(`localhost`)" + rule = "Host(`127.0.0.1`)" service = "service1" [http.routers.router1.tls] diff --git a/integration/fixtures/grpc/config_retry.toml b/integration/fixtures/grpc/config_retry.toml index b984f6ddf..6f7a3a96a 100644 --- a/integration/fixtures/grpc/config_retry.toml +++ b/integration/fixtures/grpc/config_retry.toml @@ -22,7 +22,7 @@ [http.routers] [http.routers.router1] - rule = "Host(`localhost`)" + rule = "Host(`127.0.0.1`)" service = "service1" middlewares = ["retryer"] [http.routers.router1.tls] diff --git a/integration/grpc_test.go b/integration/grpc_test.go index f881fb66e..3db0a2e86 100644 --- a/integration/grpc_test.go +++ b/integration/grpc_test.go @@ -86,7 +86,7 @@ func starth2cGRPCServer(lis net.Listener, server *myserver) error { func getHelloClientGRPC() (helloworld.GreeterClient, func() error, error) { roots := x509.NewCertPool() roots.AppendCertsFromPEM(LocalhostCert) - credsClient := credentials.NewClientTLSFromCert(roots, "localhost") + credsClient := credentials.NewClientTLSFromCert(roots, "") conn, err := grpc.Dial("127.0.0.1:4443", grpc.WithTransportCredentials(credsClient)) if err != nil { return nil, func() error { return nil }, err @@ -167,7 +167,7 @@ func (s *GRPCSuite) TestGRPC(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)")) + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var response string @@ -247,7 +247,7 @@ func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)")) + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var response string @@ -289,7 +289,7 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)")) + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var response string @@ -336,7 +336,7 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)")) + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var client helloworld.Greeter_StreamExampleClient client, closer, err := callStreamExampleClientGRPC() @@ -395,7 +395,7 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)")) + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var client helloworld.Greeter_StreamExampleClient @@ -453,7 +453,7 @@ func (s *GRPCSuite) TestGRPCWithRetry(c *check.C) { defer cmd.Process.Kill() // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)")) + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) c.Assert(err, check.IsNil) var response string diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 88c4ba8df..325074943 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -62,7 +62,7 @@ type Redirections struct { // RedirectEntryPoint is the definition of an entry point redirection. type RedirectEntryPoint struct { To string `description:"Targeted entry point of the redirection." json:"to,omitempty" toml:"to,omitempty" yaml:"to,omitempty"` - Scheme string `description:"Scheme used for the redirection." json:"https,omitempty" toml:"https,omitempty" yaml:"https,omitempty"` + Scheme string `description:"Scheme used for the redirection." json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty"` Permanent bool `description:"Applies a permanent redirection." json:"permanent,omitempty" toml:"permanent,omitempty" yaml:"permanent,omitempty"` Priority int `description:"Priority of the generated router." json:"priority,omitempty" toml:"priority,omitempty" yaml:"priority,omitempty"` } diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 31a0dfb50..91f5e68c6 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -79,7 +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" file:"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" file:"allowEmpty" export:"true"` - InsecureSNI bool `description:"Allow domain fronting. If the option is not specified, it will be disabled by default." json:"insecureSNI,omitempty" toml:"insecureSNI,omitempty" yaml:"insecureSNI,omitempty" label:"allowEmpty" export:"true"` } // ServersTransport options to configure communication between Traefik and the servers. diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 21ce74ea0..7a8349dd6 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -231,6 +231,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) Compress: middleware.Spec.Compress, PassTLSClientCert: middleware.Spec.PassTLSClientCert, Retry: middleware.Spec.Retry, + ContentType: middleware.Spec.ContentType, } } diff --git a/pkg/rules/rules.go b/pkg/rules/rules.go index 04e029a15..739c63113 100644 --- a/pkg/rules/rules.go +++ b/pkg/rules/rules.go @@ -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 } diff --git a/pkg/rules/rules_test.go b/pkg/rules/rules_test.go index 099779e40..68787ebf5 100644 --- a/pkg/rules/rules_test.go +++ b/pkg/rules/rules_test.go @@ -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.`)", diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 294d55fdc..113335086 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -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, diff --git a/pkg/tcp/router.go b/pkg/tcp/router.go index 90675eed6..c66e987ce 100644 --- a/pkg/tcp/router.go +++ b/pkg/tcp/router.go @@ -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{} diff --git a/traefik.sample.toml b/traefik.sample.toml index 0658a766f..cb2cb4c40 100644 --- a/traefik.sample.toml +++ b/traefik.sample.toml @@ -12,8 +12,6 @@ [global] checkNewVersion = true sendAnonymousUsage = true - # Enabling domain fronting - # insecureSNI = true ################################################################ # Entrypoints configuration