Fix entry point redirect behavior

This commit is contained in:
Ludovic Fernandez 2020-03-18 15:48:04 +01:00 committed by GitHub
parent 63d7ed74f1
commit 44221fba49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 184 additions and 38 deletions

View file

@ -575,20 +575,23 @@ This whole section is dedicated to options, keyed by entry point, that will appl
#### `entryPoint` #### `entryPoint`
This section is a convenience to enable (permanent) redirecting of all incoming requests on an entry point (e.g. port `80`) to another entry point (e.g. port `443`). This section is a convenience to enable (permanent) redirecting of all incoming requests on an entry point (e.g. port `80`) to another entry point (e.g. port `443`) or an explicit port (`:443`).
??? info "`entryPoint.to`" ??? info "`entryPoint.to`"
_Required_ _Required_
The target entry point. The target element, it can be:
- an entry point name (ex: `websecure`)
- a port (`:443`)
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[entryPoints.foo] [entryPoints.foo]
# ... # ...
[entryPoints.foo.http.redirections] [entryPoints.foo.http.redirections]
[entryPoints.foo.http.redirections.entryPoint] [entryPoints.foo.http.redirections.entryPoint]
to = "bar" to = "websecure"
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
@ -598,7 +601,7 @@ This section is a convenience to enable (permanent) redirecting of all incoming
http: http:
redirections: redirections:
entryPoint: entryPoint:
to: bar to: websecure
``` ```
```bash tab="CLI" ```bash tab="CLI"
@ -607,7 +610,7 @@ This section is a convenience to enable (permanent) redirecting of all incoming
??? info "`entryPoint.scheme`" ??? info "`entryPoint.scheme`"
_Optional, Default="http"_ _Optional, Default="https"_
The redirection target scheme. The redirection target scheme.
@ -635,6 +638,66 @@ This section is a convenience to enable (permanent) redirecting of all incoming
--entrypoints.foo.http.redirections.entryPoint.scheme=https --entrypoints.foo.http.redirections.entryPoint.scheme=https
``` ```
??? info "`entryPoint.permanent`"
_Optional, Default=true_
To apply a permanent redirection.
```toml tab="File (TOML)"
[entryPoints.foo]
# ...
[entryPoints.foo.http.redirections]
[entryPoints.foo.http.redirections.entryPoint]
# ...
permanent = true
```
```yaml tab="File (YAML)"
entryPoints:
foo:
# ...
http:
redirections:
entryPoint:
# ...
permanent: true
```
```bash tab="CLI"
--entrypoints.foo.http.redirections.entrypoint.permanent=true
```
??? info "`entryPoint.priority`"
_Optional, Default=1_
Priority of the generated router.
```toml tab="File (TOML)"
[entryPoints.foo]
# ...
[entryPoints.foo.http.redirections]
[entryPoints.foo.http.redirections.entryPoint]
# ...
priority = 10
```
```yaml tab="File (YAML)"
entryPoints:
foo:
# ...
http:
redirections:
entryPoint:
# ...
priority: 10
```
```bash tab="CLI"
--entrypoints.foo.http.redirections.entrypoint.priority=10
```
### Middlewares ### Middlewares
The list of middlewares that are prepended by default to the list of middlewares of each router associated to the named entry point. The list of middlewares that are prepended by default to the list of middlewares of each router associated to the named entry point.

View file

@ -60,13 +60,17 @@ type Redirections struct {
// RedirectEntryPoint is the definition of an entry point redirection. // RedirectEntryPoint is the definition of an entry point redirection.
type RedirectEntryPoint struct { type RedirectEntryPoint struct {
To string `description:"Targeted entry point of the redirection." json:"to,omitempty" toml:"to,omitempty" yaml:"to,omitempty"` To string `description:"Targeted entry point of the redirection." json:"to,omitempty" toml:"to,omitempty" yaml:"to,omitempty"`
Scheme string `description:"Scheme used for the redirection. Defaults to https." json:"https,omitempty" toml:"https,omitempty" yaml:"https,omitempty"` Scheme string `description:"Scheme used for the redirection. Defaults to https." json:"https,omitempty" toml:"https,omitempty" yaml:"https,omitempty"`
Permanent bool `description:"Applied a permanent redirection. Defaults to true." json:"permanent,omitempty" toml:"permanent,omitempty" yaml:"permanent,omitempty"`
Priority int `description:"Priority of the generated router. Defaults to 1." json:"priority,omitempty" toml:"priority,omitempty" yaml:"priority,omitempty"`
} }
// SetDefaults sets the default values. // SetDefaults sets the default values.
func (r *RedirectEntryPoint) SetDefaults() { func (r *RedirectEntryPoint) SetDefaults() {
r.Scheme = "https" r.Scheme = "https"
r.Permanent = true
r.Priority = 1
} }
// TLSConfig is the default TLS configuration for all the routers associated to the concerned entry point. // TLSConfig is the default TLS configuration for all the routers associated to the concerned entry point.

View file

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

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"math" "math"
"net" "net"
"regexp"
"github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/config/static" "github.com/containous/traefik/v2/pkg/config/static"
@ -30,9 +31,11 @@ func New(staticCfg static.Configuration) *Provider {
// Provide allows the provider to provide configurations to traefik using the given configuration channel. // Provide allows the provider to provide configurations to traefik using the given configuration channel.
func (i *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error { func (i *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, "internal"))
configurationChan <- dynamic.Message{ configurationChan <- dynamic.Message{
ProviderName: "internal", ProviderName: "internal",
Configuration: i.createConfiguration(), Configuration: i.createConfiguration(ctx),
} }
return nil return nil
@ -43,7 +46,7 @@ func (i *Provider) Init() error {
return nil return nil
} }
func (i *Provider) createConfiguration() *dynamic.Configuration { func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configuration {
cfg := &dynamic.Configuration{ cfg := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{ HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router), Routers: make(map[string]*dynamic.Router),
@ -66,20 +69,33 @@ func (i *Provider) createConfiguration() *dynamic.Configuration {
i.restConfiguration(cfg) i.restConfiguration(cfg)
i.prometheusConfiguration(cfg) i.prometheusConfiguration(cfg)
i.entryPointModels(cfg) i.entryPointModels(cfg)
i.redirection(cfg) i.redirection(ctx, cfg)
cfg.HTTP.Services["noop"] = &dynamic.Service{} cfg.HTTP.Services["noop"] = &dynamic.Service{}
return cfg return cfg
} }
func (i *Provider) redirection(cfg *dynamic.Configuration) { func (i *Provider) redirection(ctx context.Context, cfg *dynamic.Configuration) {
for name, ep := range i.staticCfg.EntryPoints { for name, ep := range i.staticCfg.EntryPoints {
if ep.HTTP.Redirections == nil || ep.HTTP.Redirections.EntryPoint == nil { if ep.HTTP.Redirections == nil {
continue continue
} }
logger := log.FromContext(log.With(ctx, log.Str(log.EntryPointName, name)))
def := ep.HTTP.Redirections def := ep.HTTP.Redirections
if def.EntryPoint == nil || def.EntryPoint.To == "" {
logger.Error("Unable to create redirection: the entry point or the port is missing")
continue
}
port, err := i.getRedirectPort(name, def)
if err != nil {
logger.Error(err)
continue
}
rtName := provider.Normalize(name + "-to-" + def.EntryPoint.To) rtName := provider.Normalize(name + "-to-" + def.EntryPoint.To)
mdName := "redirect-" + rtName mdName := "redirect-" + rtName
@ -88,12 +104,7 @@ func (i *Provider) redirection(cfg *dynamic.Configuration) {
EntryPoints: []string{name}, EntryPoints: []string{name},
Middlewares: []string{mdName}, Middlewares: []string{mdName},
Service: "noop@internal", Service: "noop@internal",
} Priority: def.EntryPoint.Priority,
port, err := i.getEntryPointPort(name, def)
if err != nil {
log.FromContext(context.Background()).WithField(log.EntryPointName, name).Error(err)
continue
} }
cfg.HTTP.Routers[rtName] = rt cfg.HTTP.Routers[rtName] = rt
@ -102,7 +113,7 @@ func (i *Provider) redirection(cfg *dynamic.Configuration) {
RedirectScheme: &dynamic.RedirectScheme{ RedirectScheme: &dynamic.RedirectScheme{
Scheme: def.EntryPoint.Scheme, Scheme: def.EntryPoint.Scheme,
Port: port, Port: port,
Permanent: true, Permanent: def.EntryPoint.Permanent,
}, },
} }
@ -110,6 +121,36 @@ func (i *Provider) redirection(cfg *dynamic.Configuration) {
} }
} }
func (i *Provider) getRedirectPort(name string, def *static.Redirections) (string, error) {
exp := regexp.MustCompile(`^:(\d+)$`)
if exp.MatchString(def.EntryPoint.To) {
_, port, err := net.SplitHostPort(def.EntryPoint.To)
if err != nil {
return "", fmt.Errorf("invalid port value: %w", err)
}
return port, nil
}
return i.getEntryPointPort(name, def)
}
func (i *Provider) getEntryPointPort(name string, def *static.Redirections) (string, error) {
dst, ok := i.staticCfg.EntryPoints[def.EntryPoint.To]
if !ok {
return "", fmt.Errorf("'to' entry point field references a non-existing entry point: %s", def.EntryPoint.To)
}
_, port, err := net.SplitHostPort(dst.Address)
if err != nil {
return "", fmt.Errorf("invalid entry point %q address %q: %v",
name, i.staticCfg.EntryPoints[def.EntryPoint.To].Address, err)
}
return port, nil
}
func (i *Provider) entryPointModels(cfg *dynamic.Configuration) { func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
for name, ep := range i.staticCfg.EntryPoints { for name, ep := range i.staticCfg.EntryPoints {
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil { if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil {
@ -233,18 +274,3 @@ func (i *Provider) prometheusConfiguration(cfg *dynamic.Configuration) {
cfg.HTTP.Services["prometheus"] = &dynamic.Service{} cfg.HTTP.Services["prometheus"] = &dynamic.Service{}
} }
func (i *Provider) getEntryPointPort(name string, def *static.Redirections) (string, error) {
dst, ok := i.staticCfg.EntryPoints[def.EntryPoint.To]
if !ok {
return "", fmt.Errorf("'to' entry point field references a non-existing entry point: %s", name)
}
_, port, err := net.SplitHostPort(dst.Address)
if err != nil {
return "", fmt.Errorf("invalid entry point %q address %q: %v",
name, i.staticCfg.EntryPoints[def.EntryPoint.To].Address, err)
}
return port, nil
}

View file

@ -1,6 +1,7 @@
package traefik package traefik
import ( import (
"context"
"encoding/json" "encoding/json"
"flag" "flag"
"io/ioutil" "io/ioutil"
@ -196,8 +197,30 @@ func Test_createConfiguration(t *testing.T) {
HTTP: static.HTTPConfig{ HTTP: static.HTTPConfig{
Redirections: &static.Redirections{ Redirections: &static.Redirections{
EntryPoint: &static.RedirectEntryPoint{ EntryPoint: &static.RedirectEntryPoint{
To: "websecure", To: "websecure",
Scheme: "https", Scheme: "https",
Permanent: true,
},
},
},
},
"websecure": {
Address: ":443",
},
},
},
}, {
desc: "redirection_port.json",
staticCfg: static.Configuration{
EntryPoints: map[string]*static.EntryPoint{
"web": {
Address: ":80",
HTTP: static.HTTPConfig{
Redirections: &static.Redirections{
EntryPoint: &static.RedirectEntryPoint{
To: ":443",
Scheme: "https",
Permanent: true,
}, },
}, },
}, },
@ -217,7 +240,7 @@ func Test_createConfiguration(t *testing.T) {
provider := Provider{staticCfg: test.staticCfg} provider := Provider{staticCfg: test.staticCfg}
cfg := provider.createConfiguration() cfg := provider.createConfiguration(context.Background())
filename := filepath.Join("fixtures", test.desc) filename := filepath.Join("fixtures", test.desc)