1
0
mirror of https://github.com/containous/traefik.git synced 2025-09-19 01:44:23 +03:00

Compare commits

..

16 Commits

Author SHA1 Message Date
Romain
bbbc18fd84 Prepare release 2.2.7 2020-07-20 18:48:04 +02:00
Ludovic Fernandez
2c7f6e4def fix: drop host port to compare with SNI. 2020-07-20 18:32:03 +02:00
Romain
ff16925f63 Prepare release v2.2.6 2020-07-17 17:54:04 +02:00
Julien Salleyron
0b7aaa3643 Fix domain fronting
Co-authored-by: Ludovic Fernandez <ldez@users.noreply.github.com>
2020-07-17 15:38:04 +02:00
Mickael Jeanroy
45f52ca29c fix: access logs header names filtering is case insensitive 2020-07-16 17:36:04 +02:00
Manuel Zapf
fae2d93525 Get Entrypoints Port Address without protocol for redirect 2020-07-16 17:18:03 +02:00
Simon Heimberg
25b74ce1f3 Add example for entrypoint on one ip address 2020-07-16 12:38:03 +02:00
Ludovic Fernandez
a3df5b9a94 fix: documentation references. 2020-07-15 09:10:03 +02:00
Romain
04f0ebf776 Prepare release v2.2.5 2020-07-13 18:18:03 +02:00
Romain
0e97a3becd Revert domain fronting fix
* revert domain fronting changes

* reintroduce HostHeader rule

* add doc for removals
2020-07-13 17:58:03 +02:00
John Pekcan
77a0cef9ce fix k8s crd to read contentType middleware into dynamic config
Co-authored-by: John Pekcan <apekcan@ea.com>
2020-07-13 12:30:03 +02:00
Julien Salleyron
143e9b6f9c Fix default value for InsecureSNI when global is not set 2020-07-13 12:06:03 +02:00
Jean-Baptiste Doumenjou
06dcf8d8aa Prepare release v2.2.4 2020-07-10 19:16:04 +02:00
Jean-Baptiste Doumenjou
c315b4e064 Change the default value of insecureSNI
* fix: allow domain fronting by default

* review: typo.

* review: doc.

Co-authored-by: Fernandez Ludovic <ludovic@containo.us>
2020-07-10 18:48:03 +02:00
Jean-Baptiste Doumenjou
d7f517fbf5 Prepare release v2.2.3 2020-07-09 17:58:03 +02:00
Julien Salleyron
b10cb84f33 Fix panic when using chain middleware. 2020-07-09 10:50:04 +02:00
36 changed files with 584 additions and 547 deletions

View File

@@ -1,3 +1,41 @@
## [v2.2.7](https://github.com/containous/traefik/tree/v2.2.7) (2020-07-20)
[All Commits](https://github.com/containous/traefik/compare/v2.2.6...v2.2.7)
**Bug fixes:**
- **[server,tls]** fix: drop host port to compare with SNI. ([#7071](https://github.com/containous/traefik/pull/7071) by [ldez](https://github.com/ldez))
## [v2.2.6](https://github.com/containous/traefik/tree/v2.2.6) (2020-07-17)
[All Commits](https://github.com/containous/traefik/compare/v2.2.5...v2.2.6)
**Bug fixes:**
- **[logs]** fix: access logs header names filtering is case insensitive ([#6900](https://github.com/containous/traefik/pull/6900) by [mjeanroy](https://github.com/mjeanroy))
- **[provider]** Get Entrypoints Port Address without protocol for redirect ([#7047](https://github.com/containous/traefik/pull/7047) by [SantoDE](https://github.com/SantoDE))
- **[tls]** Fix domain fronting ([#7064](https://github.com/containous/traefik/pull/7064) by [juliens](https://github.com/juliens))
**Documentation:**
- fix: documentation references. ([#7049](https://github.com/containous/traefik/pull/7049) by [ldez](https://github.com/ldez))
- Add example for entrypoint on one ip address ([#6483](https://github.com/containous/traefik/pull/6483) by [SimonHeimberg](https://github.com/SimonHeimberg))
## [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)
**Bug fixes:**
- **[middleware]** Fix panic when using chain middleware. ([#7016](https://github.com/containous/traefik/pull/7016) by [juliens](https://github.com/juliens))
## [v2.2.2](https://github.com/containous/traefik/tree/v2.2.2) (2020-07-08)
[All Commits](https://github.com/containous/traefik/compare/v2.2.1...v2.2.2)

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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```)

View File

@@ -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```)

View File

@@ -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"

View File

@@ -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:

View File

@@ -168,7 +168,7 @@ The format is:
If both TCP and UDP are wanted for the same port, two entryPoints definitions are needed, such as in the example below.
??? example "Both TCP and UDP on port 3179"
??? example "Both TCP and UDP on Port 3179"
```toml tab="File (TOML)"
## Static configuration
@@ -194,6 +194,30 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar
--entryPoints.udpep.address=:3179/udp
```
??? example "Listen on Specific IP Addresses Only"
```toml tab="File (TOML)"
[entryPoints.specificIPv4]
address = "192.168.2.7:8888"
[entryPoints.specificIPv6]
address = "[2001:db8::1]:8888"
```
```yaml tab="File (yaml)"
entryPoints:
specificIPv4:
address: "192.168.2.7:8888"
specificIPv6:
address: "[2001:db8::1]:8888"
```
```bash tab="CLI"
entrypoints.specificIPv4.address=192.168.2.7:8888
entrypoints.specificIPv6.address=[2001:db8::1]:8888
```
Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go.
### Forwarded Headers
You can configure Traefik to trust the forwarded headers information (`X-Forwarded-*`).

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:
| 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"
@@ -473,6 +472,11 @@ It refers to a [TLS Options](../../https/tls.md#tls-options) and will be applied
the TLS option is picked from the mapping mentioned above and based on the server name provided during the TLS handshake,
and it all happens before routing actually occurs.
!!! info "Domain Fronting"
In the case of domain fronting,
if the TLS options associated with the Host Header and the SNI are different then Traefik will respond with a status code `421`.
??? example "Configuring the TLS options"
```toml tab="File (TOML)"

View File

@@ -444,7 +444,7 @@ func (s *AcmeSuite) retrieveAcmeCertificate(c *check.C, testCase acmeTestCase) {
// A real file is needed to have the right mode on acme.json file
defer os.Remove("/tmp/acme.json")
backend := startTestServer("9010", http.StatusOK)
backend := startTestServer("9010", http.StatusOK, "")
defer backend.Close()
for _, sub := range testCase.subCases {

View File

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

View File

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

View File

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

View File

@@ -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]

View File

@@ -0,0 +1,53 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.websecure]
address = ":4443"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers.router1]
rule = "Host(`site1.www.snitest.com`)"
service = "service1"
[http.routers.router1.tls]
[http.routers.router2]
rule = "Host(`site2.www.snitest.com`)"
service = "service2"
[http.routers.router2.tls]
[http.routers.router3]
rule = "Host(`site3.www.snitest.com`)"
service = "service3"
[http.routers.router3.tls]
options = "mytls"
[http.services.service1]
[[http.services.service1.loadBalancer.servers]]
url = "http://127.0.0.1:9010"
[http.services.service2]
[[http.services.service2.loadBalancer.servers]]
url = "http://127.0.0.1:9020"
[http.services.service3]
[[http.services.service3.loadBalancer.servers]]
url = "http://127.0.0.1:9030"
[[tls.certificates]]
certFile = "fixtures/https/wildcard.www.snitest.com.cert"
keyFile = "fixtures/https/wildcard.www.snitest.com.key"
[tls.options]
[tls.options.mytls]
maxVersion = "VersionTLS12"

View File

@@ -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

View File

@@ -35,7 +35,7 @@ func (s *HeadersSuite) TestCorsResponses(c *check.C) {
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
backend := startTestServer("9000", http.StatusOK)
backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close()
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))
@@ -124,7 +124,7 @@ func (s *HeadersSuite) TestSecureHeadersResponses(c *check.C) {
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
backend := startTestServer("9000", http.StatusOK)
backend := startTestServer("9000", http.StatusOK, "")
defer backend.Close()
err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK))

View File

@@ -16,9 +16,10 @@ It has these top-level messages:
package helloworld
import (
proto "github.com/golang/protobuf/proto"
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
)
import (

View File

@@ -73,8 +73,8 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close()
defer backend2.Close()
@@ -129,8 +129,8 @@ func (s *HTTPSSuite) TestWithTLSOptions(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.org`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close()
defer backend2.Close()
@@ -215,8 +215,8 @@ func (s *HTTPSSuite) TestWithConflictingTLSOptions(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.net`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close()
defer backend2.Close()
@@ -733,9 +733,12 @@ func (s *HTTPSSuite) TestWithRootCAsFileForHTTPSOnBackend(c *check.C) {
c.Assert(err, checker.IsNil)
}
func startTestServer(port string, statusCode int) (ts *httptest.Server) {
func startTestServer(port string, statusCode int, textContent string) (ts *httptest.Server) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(statusCode)
if textContent != "" {
_, _ = w.Write([]byte(textContent))
}
})
listener, err := net.Listen("tcp", "127.0.0.1:"+port)
if err != nil {
@@ -787,8 +790,8 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithNoChange(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr1.TLSClientConfig.ServerName+"`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close()
defer backend2.Close()
@@ -856,8 +859,8 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithChange(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)"))
c.Assert(err, checker.IsNil)
backend1 := startTestServer("9010", http.StatusNoContent)
backend2 := startTestServer("9020", http.StatusResetContent)
backend1 := startTestServer("9010", http.StatusNoContent, "")
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend1.Close()
defer backend2.Close()
@@ -919,7 +922,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicConfigRouteWithTlsConfigurationDeletion(c
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`"+tr2.TLSClientConfig.ServerName+"`)"))
c.Assert(err, checker.IsNil)
backend2 := startTestServer("9020", http.StatusResetContent)
backend2 := startTestServer("9020", http.StatusResetContent, "")
defer backend2.Close()
@@ -1111,3 +1114,115 @@ func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) {
proto := conn.ConnectionState().NegotiatedProtocol
c.Assert(proto, checker.Equals, "h2")
}
// TestWithDomainFronting verify the domain fronting behavior
func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) {
backend := startTestServer("9010", http.StatusOK, "server1")
defer backend.Close()
backend2 := startTestServer("9020", http.StatusOK, "server2")
defer backend2.Close()
backend3 := startTestServer("9030", http.StatusOK, "server3")
defer backend3.Close()
file := s.adaptFile(c, "fixtures/https/https_domain_fronting.toml", struct{}{})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`site1.www.snitest.com`)"))
c.Assert(err, checker.IsNil)
testCases := []struct {
desc string
hostHeader string
serverName string
expectedContent string
expectedStatusCode int
}{
{
desc: "SimpleCase",
hostHeader: "site1.www.snitest.com",
serverName: "site1.www.snitest.com",
expectedContent: "server1",
expectedStatusCode: http.StatusOK,
},
{
desc: "Simple case with port in the Host Header",
hostHeader: "site3.www.snitest.com:4443",
serverName: "site3.www.snitest.com",
expectedContent: "server3",
expectedStatusCode: http.StatusOK,
},
{
desc: "Spaces after the host header",
hostHeader: "site3.www.snitest.com ",
serverName: "site3.www.snitest.com",
expectedContent: "server3",
expectedStatusCode: http.StatusOK,
},
{
desc: "Spaces after the servername",
hostHeader: "site3.www.snitest.com",
serverName: "site3.www.snitest.com ",
expectedContent: "server3",
expectedStatusCode: http.StatusOK,
},
{
desc: "Spaces after the servername and host header",
hostHeader: "site3.www.snitest.com ",
serverName: "site3.www.snitest.com ",
expectedContent: "server3",
expectedStatusCode: http.StatusOK,
},
{
desc: "Domain Fronting with same tlsOptions should follow header",
hostHeader: "site1.www.snitest.com",
serverName: "site2.www.snitest.com",
expectedContent: "server1",
expectedStatusCode: http.StatusOK,
},
{
desc: "Domain Fronting with same tlsOptions should follow header (2)",
hostHeader: "site2.www.snitest.com",
serverName: "site1.www.snitest.com",
expectedContent: "server2",
expectedStatusCode: http.StatusOK,
},
{
desc: "Domain Fronting with different tlsOptions should produce a 421",
hostHeader: "site2.www.snitest.com",
serverName: "site3.www.snitest.com",
expectedContent: "",
expectedStatusCode: http.StatusMisdirectedRequest,
},
{
desc: "Domain Fronting with different tlsOptions should produce a 421 (2)",
hostHeader: "site3.www.snitest.com",
serverName: "site1.www.snitest.com",
expectedContent: "",
expectedStatusCode: http.StatusMisdirectedRequest,
},
{
desc: "Case insensitive",
hostHeader: "sIte1.www.snitest.com",
serverName: "sitE1.www.snitest.com",
expectedContent: "server1",
expectedStatusCode: http.StatusOK,
},
}
for _, test := range testCases {
test := test
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443", nil)
c.Assert(err, checker.IsNil)
req.Host = test.hostHeader
err = try.RequestWithTransport(req, 500*time.Millisecond, &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true, ServerName: test.serverName}}, try.StatusCodeIs(test.expectedStatusCode), try.BodyContains(test.expectedContent))
c.Assert(err, checker.IsNil)
}
}

View File

@@ -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"`
}

View File

@@ -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" 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 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.

View File

@@ -6,6 +6,7 @@ import (
"io"
"net"
"net/http"
"net/textproto"
"net/url"
"os"
"path/filepath"
@@ -100,6 +101,17 @@ func NewHandler(config *types.AccessLog) (*Handler, error) {
Level: logrus.InfoLevel,
}
// Transform headers names in config to a canonical form, to be used as is without further transformations.
if config.Fields != nil && config.Fields.Headers != nil && len(config.Fields.Headers.Names) > 0 {
fields := map[string]string{}
for h, v := range config.Fields.Headers.Names {
fields[textproto.CanonicalMIMEHeaderKey(h)] = v
}
config.Fields.Headers.Names = fields
}
logHandler := &Handler{
config: config,
logger: logger,

View File

@@ -41,11 +41,7 @@ var (
)
func TestLogRotation(t *testing.T) {
tempDir, err := ioutil.TempDir("", "traefik_")
if err != nil {
t.Fatalf("Error setting up temporary directory: %s", err)
}
defer os.RemoveAll(tempDir)
tempDir := createTempDir(t, "traefik_")
fileName := filepath.Join(tempDir, "traefik.log")
rotatedFileName := fileName + ".rotated"
@@ -119,9 +115,106 @@ func lineCount(t *testing.T, fileName string) int {
return count
}
func TestLoggerHeaderFields(t *testing.T) {
tmpDir := createTempDir(t, CommonFormat)
expectedValue := "expectedValue"
testCases := []struct {
desc string
accessLogFields types.AccessLogFields
header string
expected string
}{
{
desc: "with default mode",
header: "User-Agent",
expected: types.AccessLogDrop,
accessLogFields: types.AccessLogFields{
DefaultMode: types.AccessLogDrop,
Headers: &types.FieldHeaders{
DefaultMode: types.AccessLogDrop,
Names: map[string]string{},
},
},
},
{
desc: "with exact header name",
header: "User-Agent",
expected: types.AccessLogKeep,
accessLogFields: types.AccessLogFields{
DefaultMode: types.AccessLogDrop,
Headers: &types.FieldHeaders{
DefaultMode: types.AccessLogDrop,
Names: map[string]string{
"User-Agent": types.AccessLogKeep,
},
},
},
},
{
desc: "with case insensitive match on header name",
header: "User-Agent",
expected: types.AccessLogKeep,
accessLogFields: types.AccessLogFields{
DefaultMode: types.AccessLogDrop,
Headers: &types.FieldHeaders{
DefaultMode: types.AccessLogDrop,
Names: map[string]string{
"user-agent": types.AccessLogKeep,
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
logFile, err := ioutil.TempFile(tmpDir, "*.log")
require.NoError(t, err)
config := &types.AccessLog{
FilePath: logFile.Name(),
Format: CommonFormat,
Fields: &test.accessLogFields,
}
logger, err := NewHandler(config)
require.NoError(t, err)
defer logger.Close()
if config.FilePath != "" {
_, err = os.Stat(config.FilePath)
require.NoError(t, err, fmt.Sprintf("logger should create %s", config.FilePath))
}
req := &http.Request{
Header: map[string][]string{},
URL: &url.URL{
Path: testPath,
},
}
req.Header.Set(test.header, expectedValue)
logger.ServeHTTP(httptest.NewRecorder(), req, http.HandlerFunc(func(writer http.ResponseWriter, r *http.Request) {
writer.WriteHeader(http.StatusOK)
}))
logData, err := ioutil.ReadFile(logFile.Name())
require.NoError(t, err)
if test.expected == types.AccessLogDrop {
assert.NotContains(t, string(logData), expectedValue)
} else {
assert.Contains(t, string(logData), expectedValue)
}
})
}
}
func TestLoggerCLF(t *testing.T) {
tmpDir := createTempDir(t, CommonFormat)
defer os.RemoveAll(tmpDir)
logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat}
@@ -136,7 +229,6 @@ func TestLoggerCLF(t *testing.T) {
func TestAsyncLoggerCLF(t *testing.T) {
tmpDir := createTempDir(t, CommonFormat)
defer os.RemoveAll(tmpDir)
logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat, BufferingSize: 1024}
@@ -358,7 +450,6 @@ func TestLoggerJSON(t *testing.T) {
t.Parallel()
tmpDir := createTempDir(t, JSONFormat)
defer os.RemoveAll(tmpDir)
logFilePath := filepath.Join(tmpDir, logFileNameSuffix)
@@ -642,6 +733,8 @@ func createTempDir(t *testing.T, prefix string) string {
tmpDir, err := ioutil.TempDir("", prefix)
require.NoError(t, err, "failed to create temp dir")
t.Cleanup(func() { _ = os.RemoveAll(tmpDir) })
return tmpDir
}

View File

@@ -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,
}
}

View File

@@ -0,0 +1,30 @@
{
"http": {
"routers": {
"web-to-websecure": {
"entryPoints": [
"web"
],
"middlewares": [
"redirect-web-to-websecure"
],
"service": "noop@internal",
"rule": "HostRegexp(`{host:.+}`)"
}
},
"middlewares": {
"redirect-web-to-websecure": {
"redirectScheme": {
"scheme": "https",
"port": "443",
"permanent": true
}
}
},
"services": {
"noop": {}
}
},
"tcp": {},
"tls": {}
}

View File

@@ -142,7 +142,7 @@ func (i *Provider) getEntryPointPort(name string, def *static.Redirections) (str
return "", fmt.Errorf("'to' entry point field references a non-existing entry point: %s", def.EntryPoint.To)
}
_, port, err := net.SplitHostPort(dst.Address)
_, port, err := net.SplitHostPort(dst.GetAddress())
if err != nil {
return "", fmt.Errorf("invalid entry point %q address %q: %w",
name, i.staticCfg.EntryPoints[def.EntryPoint.To].Address, err)

View File

@@ -232,6 +232,28 @@ func Test_createConfiguration(t *testing.T) {
},
},
},
{
desc: "redirection_with_protocol.json",
staticCfg: static.Configuration{
EntryPoints: map[string]*static.EntryPoint{
"web": {
Address: ":80",
HTTP: static.HTTPConfig{
Redirections: &static.Redirections{
EntryPoint: &static.RedirectEntryPoint{
To: "websecure",
Scheme: "https",
Permanent: true,
},
},
},
},
"websecure": {
Address: ":443/tcp",
},
},
},
},
}
for _, test := range testCases {

View File

@@ -45,7 +45,10 @@ func (f *Builder) Build(ctx context.Context, names []string) func(*http.Response
for _, name := range conf.Chain.Middlewares {
qualifiedNames = append(qualifiedNames, provider.GetQualifiedName(chainCtx, name))
}
modifiers = append(modifiers, f.Build(ctx, qualifiedNames))
if rm := f.Build(ctx, qualifiedNames); rm != nil {
modifiers = append(modifiers, rm)
}
}
}

View File

@@ -169,6 +169,21 @@ func TestBuilderBuild(t *testing.T) {
},
assertResponse: func(t *testing.T, resp *http.Response) {},
},
{
desc: "chain without headers",
middlewares: []string{"chain"},
buildResponse: stubResponse,
conf: map[string]*dynamic.Middleware{
"foo": {IPWhiteList: &dynamic.IPWhiteList{}},
"chain": {
Chain: &dynamic.Chain{
Middlewares: []string{"foo"},
},
},
},
assertResponse: func(t *testing.T, resp *http.Response) {},
},
}
for _, test := range testCases {

View File

@@ -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
}

View File

@@ -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.`)",

View File

@@ -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,

View File

@@ -5,7 +5,9 @@ import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"strings"
"github.com/containous/traefik/v2/pkg/config/runtime"
"github.com/containous/traefik/v2/pkg/log"
@@ -99,14 +101,13 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
}
router.HTTPSHandler(handlerHTTPS, defaultTLSConf)
if len(configsHTTP) > 0 {
router.AddRouteHTTPTLS("*", defaultTLSConf)
}
// Keyed by domain, then by options reference.
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
tlsOptionsForHost := map[string]string{}
for routerHTTPName, routerHTTPConfig := range configsHTTP {
if len(routerHTTPConfig.TLS.Options) == 0 || routerHTTPConfig.TLS.Options == defaultTLSConfigName {
continue
@@ -141,6 +142,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
continue
}
// domain is already in lower case thanks to the domain parsing
if tlsOptionsForHostSNI[domain] == nil {
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
}
@@ -148,10 +150,52 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
routerName: routerHTTPName,
TLSConfig: tlsConf,
}
if _, ok := tlsOptionsForHost[domain]; ok {
// Multiple tlsOptions fallback to default
tlsOptionsForHost[domain] = "default"
} else {
tlsOptionsForHost[domain] = routerHTTPConfig.TLS.Options
}
}
}
}
sniCheck := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.TLS == nil {
handlerHTTPS.ServeHTTP(rw, req)
return
}
host, _, err := net.SplitHostPort(req.Host)
if err != nil {
host = req.Host
}
host = strings.TrimSpace(host)
serverName := strings.TrimSpace(req.TLS.ServerName)
// Domain Fronting
if !strings.EqualFold(host, serverName) {
tlsOptionSNI := findTLSOptionName(tlsOptionsForHost, serverName)
tlsOptionHeader := findTLSOptionName(tlsOptionsForHost, host)
if tlsOptionHeader != tlsOptionSNI {
log.WithoutContext().
WithField("host", host).
WithField("req.Host", req.Host).
WithField("req.TLS.ServerName", req.TLS.ServerName).
Debugf("TLS options difference: SNI=%s, Header:%s", tlsOptionSNI, tlsOptionHeader)
http.Error(rw, http.StatusText(http.StatusMisdirectedRequest), http.StatusMisdirectedRequest)
return
}
}
handlerHTTPS.ServeHTTP(rw, req)
})
router.HTTPSHandler(sniCheck, defaultTLSConf)
logger := log.FromContext(ctx)
for hostSNI, tlsConfigs := range tlsOptionsForHostSNI {
if len(tlsConfigs) == 1 {
@@ -248,3 +292,17 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
return router, nil
}
func findTLSOptionName(tlsOptionsForHost map[string]string, host string) string {
tlsOptions, ok := tlsOptionsForHost[host]
if ok {
return tlsOptions
}
tlsOptions, ok = tlsOptionsForHost[strings.ToLower(host)]
if ok {
return tlsOptions
}
return "default"
}

View File

@@ -11,6 +11,7 @@ import (
"time"
"github.com/containous/traefik/v2/pkg/log"
"github.com/containous/traefik/v2/pkg/types"
)
// Router is a TCP router.
@@ -65,7 +66,7 @@ func (r *Router) ServeTCP(conn WriteCloser) {
}
// FIXME Optimize and test the routing table before helloServerName
serverName = strings.ToLower(serverName)
serverName = types.CanonicalDomain(serverName)
if r.routingTable != nil && serverName != "" {
if target, ok := r.routingTable[serverName]; ok {
target.ServeTCP(r.GetConn(conn, peeked))
@@ -102,7 +103,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{}

View File

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