diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index c0e6dce5d..86078163d 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -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. diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 6c12936ab..f8378ae2e 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -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. diff --git a/pkg/provider/traefik/fixtures/redirection_port.json b/pkg/provider/traefik/fixtures/redirection_port.json new file mode 100644 index 000000000..e113d21b3 --- /dev/null +++ b/pkg/provider/traefik/fixtures/redirection_port.json @@ -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": {} +} \ No newline at end of file diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go index 09bd1e61f..d78c28657 100644 --- a/pkg/provider/traefik/internal.go +++ b/pkg/provider/traefik/internal.go @@ -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 -} diff --git a/pkg/provider/traefik/internal_test.go b/pkg/provider/traefik/internal_test.go index 943f0f5af..ab7c33bf8 100644 --- a/pkg/provider/traefik/internal_test.go +++ b/pkg/provider/traefik/internal_test.go @@ -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)