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`
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`"
_Required_
The target entry point.
The target element, it can be:
- an entry point name (ex: `websecure`)
- a port (`:443`)
```toml tab="File (TOML)"
[entryPoints.foo]
# ...
[entryPoints.foo.http.redirections]
[entryPoints.foo.http.redirections.entryPoint]
to = "bar"
to = "websecure"
```
```yaml tab="File (YAML)"
@ -598,7 +601,7 @@ This section is a convenience to enable (permanent) redirecting of all incoming
http:
redirections:
entryPoint:
to: bar
to: websecure
```
```bash tab="CLI"
@ -607,7 +610,7 @@ This section is a convenience to enable (permanent) redirecting of all incoming
??? info "`entryPoint.scheme`"
_Optional, Default="http"_
_Optional, Default="https"_
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
```
??? 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
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.
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. Defaults to https." json:"https,omitempty" toml:"https,omitempty" yaml:"https,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"`
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.
func (r *RedirectEntryPoint) SetDefaults() {
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.

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"
"math"
"net"
"regexp"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"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.
func (i *Provider) Provide(configurationChan chan<- dynamic.Message, _ *safe.Pool) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, "internal"))
configurationChan <- dynamic.Message{
ProviderName: "internal",
Configuration: i.createConfiguration(),
Configuration: i.createConfiguration(ctx),
}
return nil
@ -43,7 +46,7 @@ func (i *Provider) Init() error {
return nil
}
func (i *Provider) createConfiguration() *dynamic.Configuration {
func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configuration {
cfg := &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
@ -66,20 +69,33 @@ func (i *Provider) createConfiguration() *dynamic.Configuration {
i.restConfiguration(cfg)
i.prometheusConfiguration(cfg)
i.entryPointModels(cfg)
i.redirection(cfg)
i.redirection(ctx, cfg)
cfg.HTTP.Services["noop"] = &dynamic.Service{}
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 {
if ep.HTTP.Redirections == nil || ep.HTTP.Redirections.EntryPoint == nil {
if ep.HTTP.Redirections == nil {
continue
}
logger := log.FromContext(log.With(ctx, log.Str(log.EntryPointName, name)))
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)
mdName := "redirect-" + rtName
@ -88,12 +104,7 @@ func (i *Provider) redirection(cfg *dynamic.Configuration) {
EntryPoints: []string{name},
Middlewares: []string{mdName},
Service: "noop@internal",
}
port, err := i.getEntryPointPort(name, def)
if err != nil {
log.FromContext(context.Background()).WithField(log.EntryPointName, name).Error(err)
continue
Priority: def.EntryPoint.Priority,
}
cfg.HTTP.Routers[rtName] = rt
@ -102,7 +113,7 @@ func (i *Provider) redirection(cfg *dynamic.Configuration) {
RedirectScheme: &dynamic.RedirectScheme{
Scheme: def.EntryPoint.Scheme,
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) {
for name, ep := range i.staticCfg.EntryPoints {
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{}
}
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
import (
"context"
"encoding/json"
"flag"
"io/ioutil"
@ -196,8 +197,30 @@ func Test_createConfiguration(t *testing.T) {
HTTP: static.HTTPConfig{
Redirections: &static.Redirections{
EntryPoint: &static.RedirectEntryPoint{
To: "websecure",
Scheme: "https",
To: "websecure",
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}
cfg := provider.createConfiguration()
cfg := provider.createConfiguration(context.Background())
filename := filepath.Join("fixtures", test.desc)