Merge branch 'v2.2' into master

This commit is contained in:
Fernandez Ludovic 2020-07-15 09:37:32 +02:00
commit 6e4f5821dc
22 changed files with 98 additions and 517 deletions

View file

@ -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) ## [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) [All Commits](https://github.com/containous/traefik/compare/v2.2.2...v2.2.3)

View file

@ -25,7 +25,6 @@ import (
"github.com/containous/traefik/v2/pkg/provider/acme" "github.com/containous/traefik/v2/pkg/provider/acme"
"github.com/containous/traefik/v2/pkg/provider/aggregator" "github.com/containous/traefik/v2/pkg/provider/aggregator"
"github.com/containous/traefik/v2/pkg/provider/traefik" "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/safe"
"github.com/containous/traefik/v2/pkg/server" "github.com/containous/traefik/v2/pkg/server"
"github.com/containous/traefik/v2/pkg/server/middleware" "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) { func setupServer(staticConfiguration *static.Configuration) (*server.Server, error) {
rules.EnableDomainFronting(staticConfiguration.Global.InsecureSNI)
providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers) providerAggregator := aggregator.NewProviderAggregator(*staticConfiguration.Providers)
// adds internal provider // adds internal provider

View file

@ -130,20 +130,6 @@ tls:
If no default certificate is provided, Traefik generates and uses a self-signed certificate. 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 ## TLS Options
The TLS options allow one to configure some parameters of the TLS connection. The TLS options allow one to configure some parameters of the TLS connection.

View file

@ -1,116 +1,16 @@
# Migration: Steps needed between the versions # 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), In `v2.2.2` we introduced a new flag (`insecureSNI`) which was available as a global option to disable domain fronting.
and enabled it by default for [https routers](../routing/routers/index.md#rule) configured with ```Host(`something`)```. 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" ### HostSNI rule matcher removal
!!! info "Before v2.2.2" 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.
```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
```
!!! 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
```
## v2.0 to v2.1 ## v2.0 to v2.1

View file

@ -162,9 +162,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I
`--global.checknewversion`: `--global.checknewversion`:
Periodically check if a new version has been released. (Default: ```false```) 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`: `--global.sendanonymoususage`:
Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default. (Default: ```false```) Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default. (Default: ```false```)

View file

@ -162,9 +162,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I
`TRAEFIK_GLOBAL_CHECKNEWVERSION`: `TRAEFIK_GLOBAL_CHECKNEWVERSION`:
Periodically check if a new version has been released. (Default: ```false```) 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`: `TRAEFIK_GLOBAL_SENDANONYMOUSUSAGE`:
Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default. (Default: ```false```) Periodically send anonymous usage statistics. If the option is not specified, it will be enabled by default. (Default: ```false```)

View file

@ -1,7 +1,6 @@
[global] [global]
checkNewVersion = true checkNewVersion = true
sendAnonymousUsage = true sendAnonymousUsage = true
insecureSNI = false
[serversTransport] [serversTransport]
insecureSkipVerify = true insecureSkipVerify = true
@ -153,7 +152,7 @@
username = "foobar" username = "foobar"
password = "foobar" password = "foobar"
[providers.consul] [providers.consul]
rootKey = "traefik" rootKey = "foobar"
endpoints = ["foobar", "foobar"] endpoints = ["foobar", "foobar"]
username = "foobar" username = "foobar"
password = "foobar" password = "foobar"
@ -164,7 +163,7 @@
key = "foobar" key = "foobar"
insecureSkipVerify = true insecureSkipVerify = true
[providers.etcd] [providers.etcd]
rootKey = "traefik" rootKey = "foobar"
endpoints = ["foobar", "foobar"] endpoints = ["foobar", "foobar"]
username = "foobar" username = "foobar"
password = "foobar" password = "foobar"
@ -175,7 +174,7 @@
key = "foobar" key = "foobar"
insecureSkipVerify = true insecureSkipVerify = true
[providers.zooKeeper] [providers.zooKeeper]
rootKey = "traefik" rootKey = "foobar"
endpoints = ["foobar", "foobar"] endpoints = ["foobar", "foobar"]
username = "foobar" username = "foobar"
password = "foobar" password = "foobar"
@ -186,7 +185,7 @@
key = "foobar" key = "foobar"
insecureSkipVerify = true insecureSkipVerify = true
[providers.redis] [providers.redis]
rootKey = "traefik" rootKey = "foobar"
endpoints = ["foobar", "foobar"] endpoints = ["foobar", "foobar"]
username = "foobar" username = "foobar"
password = "foobar" password = "foobar"

View file

@ -1,8 +1,6 @@
global: global:
checkNewVersion: true checkNewVersion: true
sendAnonymousUsage: true sendAnonymousUsage: true
insecureSNI: false
serversTransport: serversTransport:
insecureSkipVerify: true insecureSkipVerify: true
rootCAs: rootCAs:
@ -127,7 +125,7 @@ providers:
- foobar - foobar
labelSelector: foobar labelSelector: foobar
ingressClass: foobar ingressClass: foobar
throttleDuration: 10s throttleDuration: 42s
rest: rest:
insecure: true insecure: true
rancher: rancher:
@ -164,7 +162,7 @@ providers:
username: foobar username: foobar
password: foobar password: foobar
consul: consul:
rootKey: traefik rootKey: foobar
endpoints: endpoints:
- foobar - foobar
- foobar - foobar
@ -177,7 +175,7 @@ providers:
key: foobar key: foobar
insecureSkipVerify: true insecureSkipVerify: true
etcd: etcd:
rootKey: traefik rootKey: foobar
endpoints: endpoints:
- foobar - foobar
- foobar - foobar
@ -190,10 +188,10 @@ providers:
key: foobar key: foobar
insecureSkipVerify: true insecureSkipVerify: true
zooKeeper: zooKeeper:
rootKey: traefik rootKey: foobar
endpoints: endpoints:
- foobar - foobar
- foobar - foobar
username: foobar username: foobar
password: foobar password: foobar
tls: tls:
@ -203,10 +201,10 @@ providers:
key: foobar key: foobar
insecureSkipVerify: true insecureSkipVerify: true
redis: redis:
rootKey: traefik rootKey: foobar
endpoints: endpoints:
- foobar - foobar
- foobar - foobar
username: foobar username: foobar
password: foobar password: foobar
tls: tls:

View file

@ -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: The table below lists all the available matchers:
| Rule | Description | | Rule | Description |
|------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
| ```Headers(`key`, `value`)``` | Check if there is a key `key`defined in the headers, with the value `value` | | ```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` | | ```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. | | ```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`. | | ```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`. |
| ```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`) |
| ```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. |
| ```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. |
| ```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. |
| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. |
!!! important "Regexp Syntax" !!! important "Regexp Syntax"

View file

@ -22,7 +22,7 @@
[http.routers] [http.routers]
[http.routers.router1] [http.routers.router1]
rule = "Host(`localhost`)" rule = "Host(`127.0.0.1`)"
service = "service1" service = "service1"
[http.routers.router1.tls] [http.routers.router1.tls]

View file

@ -19,7 +19,7 @@
[http.routers] [http.routers]
[http.routers.router1] [http.routers.router1]
rule = "Host(`localhost`)" rule = "Host(`127.0.0.1`)"
service = "service1" service = "service1"
[http.routers.router1.tls] [http.routers.router1.tls]

View file

@ -22,7 +22,7 @@
[http.routers] [http.routers]
[http.routers.router1] [http.routers.router1]
rule = "Host(`localhost`)" rule = "Host(`127.0.0.1`)"
service = "service1" service = "service1"
[http.routers.router1.tls] [http.routers.router1.tls]

View file

@ -22,7 +22,7 @@
[http.routers] [http.routers]
[http.routers.router1] [http.routers.router1]
rule = "Host(`localhost`)" rule = "Host(`127.0.0.1`)"
service = "service1" service = "service1"
middlewares = ["retryer"] middlewares = ["retryer"]
[http.routers.router1.tls] [http.routers.router1.tls]

View file

@ -86,7 +86,7 @@ func starth2cGRPCServer(lis net.Listener, server *myserver) error {
func getHelloClientGRPC() (helloworld.GreeterClient, func() error, error) { func getHelloClientGRPC() (helloworld.GreeterClient, func() error, error) {
roots := x509.NewCertPool() roots := x509.NewCertPool()
roots.AppendCertsFromPEM(LocalhostCert) roots.AppendCertsFromPEM(LocalhostCert)
credsClient := credentials.NewClientTLSFromCert(roots, "localhost") credsClient := credentials.NewClientTLSFromCert(roots, "")
conn, err := grpc.Dial("127.0.0.1:4443", grpc.WithTransportCredentials(credsClient)) conn, err := grpc.Dial("127.0.0.1:4443", grpc.WithTransportCredentials(credsClient))
if err != nil { if err != nil {
return nil, func() error { return nil }, err return nil, func() error { return nil }, err
@ -167,7 +167,7 @@ func (s *GRPCSuite) TestGRPC(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // 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) c.Assert(err, check.IsNil)
var response string var response string
@ -247,7 +247,7 @@ func (s *GRPCSuite) TestGRPCh2cTermination(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // 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) c.Assert(err, check.IsNil)
var response string var response string
@ -289,7 +289,7 @@ func (s *GRPCSuite) TestGRPCInsecure(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // 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) c.Assert(err, check.IsNil)
var response string var response string
@ -336,7 +336,7 @@ func (s *GRPCSuite) TestGRPCBuffer(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // 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) c.Assert(err, check.IsNil)
var client helloworld.Greeter_StreamExampleClient var client helloworld.Greeter_StreamExampleClient
client, closer, err := callStreamExampleClientGRPC() client, closer, err := callStreamExampleClientGRPC()
@ -395,7 +395,7 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // 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) c.Assert(err, check.IsNil)
var client helloworld.Greeter_StreamExampleClient var client helloworld.Greeter_StreamExampleClient
@ -453,7 +453,7 @@ func (s *GRPCSuite) TestGRPCWithRetry(c *check.C) {
defer cmd.Process.Kill() defer cmd.Process.Kill()
// wait for Traefik // 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) c.Assert(err, check.IsNil)
var response string var response string

View file

@ -62,7 +62,7 @@ type Redirections struct {
// RedirectEntryPoint is the definition of an entry point redirection. // RedirectEntryPoint is the definition of an entry point redirection.
type RedirectEntryPoint struct { type RedirectEntryPoint struct {
To string `description:"Targeted entry point of the redirection." json:"to,omitempty" toml:"to,omitempty" yaml:"to,omitempty"` 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"` 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"` Priority int `description:"Priority of the generated router." json:"priority,omitempty" toml:"priority,omitempty" yaml:"priority,omitempty"`
} }

View file

@ -79,7 +79,6 @@ type CertificateResolver struct {
type Global 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"` 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"` 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. // ServersTransport options to configure communication between Traefik and the servers.

View file

@ -231,6 +231,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
Compress: middleware.Spec.Compress, Compress: middleware.Spec.Compress,
PassTLSClientCert: middleware.Spec.PassTLSClientCert, PassTLSClientCert: middleware.Spec.PassTLSClientCert,
Retry: middleware.Spec.Retry, Retry: middleware.Spec.Retry,
ContentType: middleware.Spec.ContentType,
} }
} }

View file

@ -12,9 +12,8 @@ import (
) )
var funcs = map[string]func(*mux.Route, ...string) error{ var funcs = map[string]func(*mux.Route, ...string) error{
"Host": hostSecure, "Host": host,
"HostHeader": host, "HostHeader": host,
"HostSNI": hostSNI,
"HostRegexp": hostRegexp, "HostRegexp": hostRegexp,
"Path": path, "Path": path,
"PathPrefix": pathPrefix, "PathPrefix": pathPrefix,
@ -24,18 +23,6 @@ var funcs = map[string]func(*mux.Route, ...string) error{
"Query": query, "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. // Router handle routing with rules.
type Router struct { type Router struct {
*mux.Router *mux.Router
@ -112,125 +99,46 @@ func host(route *mux.Route, hosts ...string) error {
} }
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
return matchHost(req, true, hosts...) reqHost := requestdecorator.GetCanonizedHost(req.Context())
}) if len(reqHost) == 0 {
return nil 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 { flatH := requestdecorator.GetCNAMEFlatten(req.Context())
logger := log.FromContext(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 { for _, host := range hosts {
if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) { if reqHost == host {
return true 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 { // Check for match on trailing period on host
if reqHost == host { if last := len(host) - 1; last >= 0 && host[last] == '.' {
logHostSNI(insecureSNI, req, reqHost) h := host[:last]
return true if reqHost == h {
} return true
}
}
// Check for match on trailing period on host // Check for match on trailing period on request
if last := len(host) - 1; last >= 0 && host[last] == '.' { if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' {
h := host[:last] h := reqHost[:last]
if reqHost == h { if h == host {
logHostSNI(insecureSNI, req, reqHost) return true
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 false
}) })
return nil return nil
} }

View file

@ -50,6 +50,14 @@ func Test_addRoute(t *testing.T) {
"http://localhost/foo": http.StatusOK, "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", desc: "Host with trailing period in rule",
rule: "Host(`localhost.`)", rule: "Host(`localhost.`)",

View file

@ -2,7 +2,6 @@ package router
import ( import (
"context" "context"
"crypto/tls"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -15,7 +14,6 @@ import (
"github.com/containous/traefik/v2/pkg/middlewares/accesslog" "github.com/containous/traefik/v2/pkg/middlewares/accesslog"
"github.com/containous/traefik/v2/pkg/middlewares/requestdecorator" "github.com/containous/traefik/v2/pkg/middlewares/requestdecorator"
"github.com/containous/traefik/v2/pkg/responsemodifiers" "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/middleware"
"github.com/containous/traefik/v2/pkg/server/service" "github.com/containous/traefik/v2/pkg/server/service"
"github.com/containous/traefik/v2/pkg/testhelpers" "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) { func TestAccessLog(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 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) chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder)
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
// even though rtConf was passed by argument to the manager builders above, // even though rtConf was passed by argument to the manager builders above,

View file

@ -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) { func (r *Router) AddRouteHTTPTLS(sniHost string, config *tls.Config) {
if r.hostHTTPTLSConfig == nil { if r.hostHTTPTLSConfig == nil {
r.hostHTTPTLSConfig = map[string]*tls.Config{} r.hostHTTPTLSConfig = map[string]*tls.Config{}

View file

@ -12,8 +12,6 @@
[global] [global]
checkNewVersion = true checkNewVersion = true
sendAnonymousUsage = true sendAnonymousUsage = true
# Enabling domain fronting
# insecureSNI = true
################################################################ ################################################################
# Entrypoints configuration # Entrypoints configuration