Disable domain fronting
Co-authored-by: jbdoumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
parent
416c367778
commit
2b35397169
19 changed files with 532 additions and 58 deletions
|
@ -25,6 +25,7 @@ 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"
|
||||||
|
@ -161,6 +162,8 @@ 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
|
||||||
|
|
|
@ -130,6 +130,20 @@ 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.
|
||||||
|
@ -317,7 +331,7 @@ spec:
|
||||||
### Strict SNI Checking
|
### Strict SNI Checking
|
||||||
|
|
||||||
With strict SNI checking, Traefik won't allow connections from clients connections
|
With strict SNI checking, Traefik won't allow connections from clients connections
|
||||||
that do not specify a server_name extension.
|
that do not specify a server_name extension or don't match any certificate configured on the tlsOption.
|
||||||
|
|
||||||
```toml tab="File (TOML)"
|
```toml tab="File (TOML)"
|
||||||
# Dynamic configuration
|
# Dynamic configuration
|
||||||
|
|
|
@ -1,5 +1,117 @@
|
||||||
# Migration: Steps needed between the versions
|
# Migration: Steps needed between the versions
|
||||||
|
|
||||||
|
## v2.x to v2.2.2
|
||||||
|
|
||||||
|
### Domain fronting
|
||||||
|
|
||||||
|
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`)```.
|
||||||
|
|
||||||
|
!!! 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
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! 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
|
||||||
|
|
||||||
### Kubernetes CRD
|
### Kubernetes CRD
|
||||||
|
|
|
@ -162,6 +162,9 @@ 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```)
|
||||||
|
|
||||||
|
|
|
@ -162,6 +162,9 @@ 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```)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[global]
|
[global]
|
||||||
checkNewVersion = true
|
checkNewVersion = true
|
||||||
sendAnonymousUsage = true
|
sendAnonymousUsage = true
|
||||||
|
insecureSNI = false
|
||||||
|
|
||||||
[serversTransport]
|
[serversTransport]
|
||||||
insecureSkipVerify = true
|
insecureSkipVerify = true
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
global:
|
global:
|
||||||
checkNewVersion: true
|
checkNewVersion: true
|
||||||
sendAnonymousUsage: true
|
sendAnonymousUsage: true
|
||||||
|
insecureSNI: false
|
||||||
|
|
||||||
serversTransport:
|
serversTransport:
|
||||||
insecureSkipVerify: true
|
insecureSkipVerify: true
|
||||||
rootCAs:
|
rootCAs:
|
||||||
|
|
|
@ -228,16 +228,18 @@ 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`, ...)``` | Check if the request domain targets one of the given `domains`. |
|
| ```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. |
|
||||||
| ```HostRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Check if the request domain matches the given `regexp`. |
|
| ```HostHeader(`example.com`, ...)``` | Check if the request domain (host header value) targets one of the given `domains`. |
|
||||||
| ```Method(`GET`, ...)``` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) |
|
| ```HostSNI(`example.com`, ...)``` | Check if the [Server Name Indication](https://en.wikipedia.org/wiki/Server_Name_Indication) corresponds to the given `domains`. |
|
||||||
| ```Path(`/path`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`, ...)``` | Match exact request path. It accepts a sequence of literal and regular expression paths. |
|
| ```HostRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Check if the request domain matches the given `regexp`. |
|
||||||
| ```PathPrefix(`/products/`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`)``` | Match request prefix path. It accepts a sequence of literal and regular expression prefix paths. |
|
| ```Method(`GET`, ...)``` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`) |
|
||||||
| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. |
|
| ```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"
|
!!! important "Regexp Syntax"
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
[http.routers]
|
[http.routers]
|
||||||
[http.routers.router1]
|
[http.routers.router1]
|
||||||
rule = "Host(`127.0.0.1`)"
|
rule = "Host(`localhost`)"
|
||||||
service = "service1"
|
service = "service1"
|
||||||
[http.routers.router1.tls]
|
[http.routers.router1.tls]
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
[http.routers]
|
[http.routers]
|
||||||
[http.routers.router1]
|
[http.routers.router1]
|
||||||
rule = "Host(`127.0.0.1`)"
|
rule = "Host(`localhost`)"
|
||||||
service = "service1"
|
service = "service1"
|
||||||
[http.routers.router1.tls]
|
[http.routers.router1.tls]
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
[http.routers]
|
[http.routers]
|
||||||
[http.routers.router1]
|
[http.routers.router1]
|
||||||
rule = "Host(`127.0.0.1`)"
|
rule = "Host(`localhost`)"
|
||||||
service = "service1"
|
service = "service1"
|
||||||
[http.routers.router1.tls]
|
[http.routers.router1.tls]
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
[http.routers]
|
[http.routers]
|
||||||
[http.routers.router1]
|
[http.routers.router1]
|
||||||
rule = "Host(`127.0.0.1`)"
|
rule = "Host(`localhost`)"
|
||||||
service = "service1"
|
service = "service1"
|
||||||
middlewares = ["retryer"]
|
middlewares = ["retryer"]
|
||||||
[http.routers.router1.tls]
|
[http.routers.router1.tls]
|
||||||
|
|
|
@ -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, "")
|
credsClient := credentials.NewClientTLSFromCert(roots, "localhost")
|
||||||
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(`127.0.0.1`)"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)"))
|
||||||
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(`127.0.0.1`)"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)"))
|
||||||
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(`127.0.0.1`)"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)"))
|
||||||
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(`127.0.0.1`)"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)"))
|
||||||
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(`127.0.0.1`)"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)"))
|
||||||
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(`127.0.0.1`)"))
|
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`localhost`)"))
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
var response string
|
var response string
|
||||||
|
|
|
@ -79,6 +79,7 @@ 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" 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" 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"`
|
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.
|
// ServersTransport options to configure communication between Traefik and the servers.
|
||||||
|
|
|
@ -12,7 +12,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var funcs = map[string]func(*mux.Route, ...string) error{
|
var funcs = map[string]func(*mux.Route, ...string) error{
|
||||||
"Host": host,
|
"Host": hostSecure,
|
||||||
|
"HostHeader": host,
|
||||||
|
"HostSNI": hostSNI,
|
||||||
"HostRegexp": hostRegexp,
|
"HostRegexp": hostRegexp,
|
||||||
"Path": path,
|
"Path": path,
|
||||||
"PathPrefix": pathPrefix,
|
"PathPrefix": pathPrefix,
|
||||||
|
@ -22,6 +24,18 @@ 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
|
||||||
|
@ -98,46 +112,125 @@ 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 {
|
||||||
reqHost := requestdecorator.GetCanonizedHost(req.Context())
|
return matchHost(req, true, hosts...)
|
||||||
if len(reqHost) == 0 {
|
})
|
||||||
log.FromContext(req.Context()).Warnf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
|
return nil
|
||||||
return false
|
}
|
||||||
}
|
|
||||||
|
|
||||||
flatH := requestdecorator.GetCNAMEFlatten(req.Context())
|
func matchHost(req *http.Request, insecureSNI bool, hosts ...string) bool {
|
||||||
if len(flatH) > 0 {
|
logger := log.FromContext(req.Context())
|
||||||
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 reqHost == host {
|
if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
logger.Debugf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host)
|
||||||
// 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 request
|
|
||||||
if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' {
|
|
||||||
h := reqHost[:last]
|
|
||||||
if h == host {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
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 {
|
||||||
|
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 {
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -681,6 +681,18 @@ func TestParseDomains(t *testing.T) {
|
||||||
domain: []string{"foo.bar", "test.bar"},
|
domain: []string{"foo.bar", "test.bar"},
|
||||||
errorExpected: false,
|
errorExpected: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Many host rules upper",
|
||||||
|
expression: "HOST(`foo.bar`,`test.bar`)",
|
||||||
|
domain: []string{"foo.bar", "test.bar"},
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Many host rules lower",
|
||||||
|
expression: "host(`foo.bar`,`test.bar`)",
|
||||||
|
domain: []string{"foo.bar", "test.bar"},
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
description: "No host rule",
|
description: "No host rule",
|
||||||
expression: "Path(`/test`)",
|
expression: "Path(`/test`)",
|
||||||
|
|
|
@ -2,6 +2,7 @@ package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -14,6 +15,7 @@ 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"
|
||||||
|
@ -25,6 +27,8 @@ import (
|
||||||
func TestRouterManager_Get(t *testing.T) {
|
func TestRouterManager_Get(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) {}))
|
||||||
|
|
||||||
|
t.Cleanup(func() { server.Close() })
|
||||||
|
|
||||||
type expectedResult struct {
|
type expectedResult struct {
|
||||||
StatusCode int
|
StatusCode int
|
||||||
RequestHeaders map[string]string
|
RequestHeaders map[string]string
|
||||||
|
@ -310,9 +314,230 @@ 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) {}))
|
||||||
|
|
||||||
|
t.Cleanup(func() { server.Close() })
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
routersConfig map[string]*dynamic.Router
|
routersConfig map[string]*dynamic.Router
|
||||||
|
@ -683,7 +908,6 @@ 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,
|
||||||
|
@ -778,13 +1002,15 @@ type staticTransport struct {
|
||||||
res *http.Response
|
res *http.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *staticTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
func (t *staticTransport) RoundTrip(_ *http.Request) (*http.Response, error) {
|
||||||
return t.res, nil
|
return t.res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkRouterServe(b *testing.B) {
|
func BenchmarkRouterServe(b *testing.B) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||||
|
|
||||||
|
b.Cleanup(func() { server.Close() })
|
||||||
|
|
||||||
res := &http.Response{
|
res := &http.Response{
|
||||||
StatusCode: 200,
|
StatusCode: 200,
|
||||||
Body: ioutil.NopCloser(strings.NewReader("")),
|
Body: ioutil.NopCloser(strings.NewReader("")),
|
||||||
|
|
|
@ -102,7 +102,7 @@ func (r *Router) AddRouteTLS(sniHost string, target Handler, config *tls.Config)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRouteHTTPTLS defines a handler for a given sniHost and sets the matching tlsConfig.
|
// AddRouteHTTPTLS defines the matching tlsConfig for a given sniHost.
|
||||||
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{}
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
[global]
|
[global]
|
||||||
checkNewVersion = true
|
checkNewVersion = true
|
||||||
sendAnonymousUsage = true
|
sendAnonymousUsage = true
|
||||||
|
# Enabling domain fronting
|
||||||
|
# insecureSNI = true
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# Entrypoints configuration
|
# Entrypoints configuration
|
||||||
|
|
Loading…
Reference in a new issue