diff --git a/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md b/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md index 7bbee8b69..e1593c1d5 100644 --- a/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md +++ b/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md @@ -318,6 +318,10 @@ The following annotations are organized by category for easier navigation. |-------------------------------------------------------|--------------------------------------------------------------------------------------------| | `nginx.ingress.kubernetes.io/use-regex` | | | `nginx.ingress.kubernetes.io/rewrite-target` | | +| `nginx.ingress.kubernetes.io/permanent-redirect` | Defaults to a 301 Moved Permanently status code. | +| `nginx.ingress.kubernetes.io/permanent-redirect-code` | Only valid 3XX HTTP Status Codes are accepted. | +| `nginx.ingress.kubernetes.io/temporal-redirect` | Takes precedence over the `permanent-redirect` annotation. Defaults to a 302 Found status code. | +| `nginx.ingress.kubernetes.io/temporal-redirect-code` | Only valid 3XX HTTP Status Codes are accepted. | ### IP Whitelist @@ -393,9 +397,6 @@ The following annotations are organized by category for easier navigation. | `nginx.ingress.kubernetes.io/global-rate-limit-window` | | | `nginx.ingress.kubernetes.io/global-rate-limit-key` | | | `nginx.ingress.kubernetes.io/global-rate-limit-ignored-cidrs` | | -| `nginx.ingress.kubernetes.io/permanent-redirect` | | -| `nginx.ingress.kubernetes.io/permanent-redirect-code` | | -| `nginx.ingress.kubernetes.io/temporal-redirect` | | | `nginx.ingress.kubernetes.io/preserve-trailing-slash` | Traefik preserves trailing slash by default. | | `nginx.ingress.kubernetes.io/proxy-cookie-domain` | | | `nginx.ingress.kubernetes.io/proxy-cookie-path` | | diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 5b2157d9b..f2681247e 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -645,6 +645,9 @@ type RedirectRegex struct { Replacement string `json:"replacement,omitempty" toml:"replacement,omitempty" yaml:"replacement,omitempty"` // Permanent defines whether the redirection is permanent (308). Permanent bool `json:"permanent,omitempty" toml:"permanent,omitempty" yaml:"permanent,omitempty" export:"true"` + + // StatusCode is for supporting the NGINX annotations related to redirect. + StatusCode *int `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"` } // +k8s:deepcopy-gen=true @@ -663,7 +666,7 @@ type RedirectScheme struct { // ForcePermanentRedirect is an internal field (not exposed in configuration). // When set to true, this forces the use of permanent redirects 308, regardless of the request method. // Used by the provider ingress-ngin. - ForcePermanentRedirect bool `json:"-" toml:"-" yaml:"-" label:"-"` + ForcePermanentRedirect bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"` } // +k8s:deepcopy-gen=true diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index d78443c48..48065822e 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -918,7 +918,7 @@ func (in *Middleware) DeepCopyInto(out *Middleware) { if in.RedirectRegex != nil { in, out := &in.RedirectRegex, &out.RedirectRegex *out = new(RedirectRegex) - **out = **in + (*in).DeepCopyInto(*out) } if in.RedirectScheme != nil { in, out := &in.RedirectScheme, &out.RedirectScheme @@ -1186,6 +1186,11 @@ func (in *RateLimit) DeepCopy() *RateLimit { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedirectRegex) DeepCopyInto(out *RedirectRegex) { *out = *in + if in.StatusCode != nil { + in, out := &in.StatusCode, &out.StatusCode + *out = new(int) + **out = **in + } return } diff --git a/pkg/middlewares/redirect/redirect.go b/pkg/middlewares/redirect/redirect.go index 503df4419..b7ecdad47 100644 --- a/pkg/middlewares/redirect/redirect.go +++ b/pkg/middlewares/redirect/redirect.go @@ -18,32 +18,32 @@ const typeName = "Redirect" var uriRegexp = regexp.MustCompile(`^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`) type redirect struct { - next http.Handler - regex *regexp.Regexp - replacement string - permanent bool - forcePermanentRedirect bool - errHandler utils.ErrorHandler - name string - rawURL func(*http.Request) string + next http.Handler + regex *regexp.Regexp + replacement string + permanent bool + statusCode *int + errHandler utils.ErrorHandler + name string + rawURL func(*http.Request) string } // New creates a Redirect middleware. -func newRedirect(next http.Handler, regex, replacement string, permanent bool, forcePermanentRedirect bool, rawURL func(*http.Request) string, name string) (http.Handler, error) { +func newRedirect(next http.Handler, regex, replacement string, permanent bool, statusCode *int, rawURL func(*http.Request) string, name string) (http.Handler, error) { re, err := regexp.Compile(regex) if err != nil { return nil, err } return &redirect{ - regex: re, - replacement: replacement, - permanent: permanent, - forcePermanentRedirect: forcePermanentRedirect, - errHandler: utils.DefaultHandler, - next: next, - name: name, - rawURL: rawURL, + regex: re, + replacement: replacement, + permanent: permanent, + statusCode: statusCode, + errHandler: utils.DefaultHandler, + next: next, + name: name, + rawURL: rawURL, }, nil } @@ -71,7 +71,7 @@ func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } if newURL != oldURL { - handler := &moveHandler{location: parsedURL, permanent: r.permanent, forcePermanentRedirect: r.forcePermanentRedirect} + handler := &moveHandler{location: parsedURL, permanent: r.permanent, statusCode: r.statusCode} handler.ServeHTTP(rw, req) return } @@ -84,9 +84,9 @@ func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } type moveHandler struct { - location *url.URL - permanent bool - forcePermanentRedirect bool + location *url.URL + permanent bool + statusCode *int } func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { @@ -104,8 +104,8 @@ func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } - if m.forcePermanentRedirect { - status = http.StatusPermanentRedirect + if m.statusCode != nil { + status = *m.statusCode } rw.WriteHeader(status) diff --git a/pkg/middlewares/redirect/redirect_regex.go b/pkg/middlewares/redirect/redirect_regex.go index 5846d5c0d..f82b52121 100644 --- a/pkg/middlewares/redirect/redirect_regex.go +++ b/pkg/middlewares/redirect/redirect_regex.go @@ -17,7 +17,7 @@ func NewRedirectRegex(ctx context.Context, next http.Handler, conf dynamic.Redir logger.Debug().Msg("Creating middleware") logger.Debug().Msgf("Setting up redirection from %s to %s", conf.Regex, conf.Replacement) - return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, false, rawURL, name) + return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, conf.StatusCode, rawURL, name) } func rawURL(req *http.Request) string { diff --git a/pkg/middlewares/redirect/redirect_scheme.go b/pkg/middlewares/redirect/redirect_scheme.go index b1b1e3c3c..0fd213799 100644 --- a/pkg/middlewares/redirect/redirect_scheme.go +++ b/pkg/middlewares/redirect/redirect_scheme.go @@ -40,7 +40,13 @@ func NewRedirectScheme(ctx context.Context, next http.Handler, conf dynamic.Redi rs := &redirectScheme{name: name} - handler, err := newRedirect(next, uriPattern, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, conf.ForcePermanentRedirect, rs.clientRequestURL, name) + var permanentRedirectCode *int + if conf.ForcePermanentRedirect { + status := http.StatusPermanentRedirect + permanentRedirectCode = &status + } + + handler, err := newRedirect(next, uriPattern, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, permanentRedirectCode, rs.clientRequestURL, name) if err != nil { return nil, err } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index d577fef82..e354565fe 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -871,7 +871,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) { if in.RedirectRegex != nil { in, out := &in.RedirectRegex, &out.RedirectRegex *out = new(dynamic.RedirectRegex) - **out = **in + (*in).DeepCopyInto(*out) } if in.RedirectScheme != nil { in, out := &in.RedirectScheme, &out.RedirectScheme diff --git a/pkg/provider/kubernetes/ingress-nginx/annotations.go b/pkg/provider/kubernetes/ingress-nginx/annotations.go index e37ee659c..c44c632dc 100644 --- a/pkg/provider/kubernetes/ingress-nginx/annotations.go +++ b/pkg/provider/kubernetes/ingress-nginx/annotations.go @@ -26,6 +26,11 @@ type ingressConfig struct { UseRegex *bool `annotation:"nginx.ingress.kubernetes.io/use-regex"` RewriteTarget *string `annotation:"nginx.ingress.kubernetes.io/rewrite-target"` + PermanentRedirect *string `annotation:"nginx.ingress.kubernetes.io/permanent-redirect"` + PermanentRedirectCode *int `annotation:"nginx.ingress.kubernetes.io/permanent-redirect-code"` + TemporalRedirect *string `annotation:"nginx.ingress.kubernetes.io/temporal-redirect"` + TemporalRedirectCode *int `annotation:"nginx.ingress.kubernetes.io/temporal-redirect-code"` + Affinity *string `annotation:"nginx.ingress.kubernetes.io/affinity"` SessionCookieName *string `annotation:"nginx.ingress.kubernetes.io/session-cookie-name"` SessionCookieSecure *bool `annotation:"nginx.ingress.kubernetes.io/session-cookie-secure"` diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/14-ingress-with-permanent-redirect-code-correct-code.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/14-ingress-with-permanent-redirect-code-correct-code.yml new file mode 100644 index 000000000..8ec11e5dd --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/14-ingress-with-permanent-redirect-code-correct-code.yml @@ -0,0 +1,23 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-permanent-redirect + namespace: default + annotations: + nginx.ingress.kubernetes.io/permanent-redirect: "https://www.google.com" + nginx.ingress.kubernetes.io/permanent-redirect-code: "300" + +spec: + ingressClassName: nginx + rules: + - host: permanent-redirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/14-ingress-with-permanent-redirect-code-wrong-code.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/14-ingress-with-permanent-redirect-code-wrong-code.yml new file mode 100644 index 000000000..df918b473 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/14-ingress-with-permanent-redirect-code-wrong-code.yml @@ -0,0 +1,23 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-permanent-redirect + namespace: default + annotations: + nginx.ingress.kubernetes.io/permanent-redirect: "https://www.google.com" + nginx.ingress.kubernetes.io/permanent-redirect-code: "500" + +spec: + ingressClassName: nginx + rules: + - host: permanent-redirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/14-ingress-with-permanent-redirect.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/14-ingress-with-permanent-redirect.yml new file mode 100644 index 000000000..5adaea639 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/14-ingress-with-permanent-redirect.yml @@ -0,0 +1,22 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-permanent-redirect + namespace: default + annotations: + nginx.ingress.kubernetes.io/permanent-redirect: "https://www.google.com" + +spec: + ingressClassName: nginx + rules: + - host: permanent-redirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/15-ingress-with-temporal-redirect.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/15-ingress-with-temporal-redirect.yml new file mode 100644 index 000000000..b21203050 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/15-ingress-with-temporal-redirect.yml @@ -0,0 +1,22 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-temporal-redirect + namespace: default + annotations: + nginx.ingress.kubernetes.io/temporal-redirect: "https://www.google.com" + +spec: + ingressClassName: nginx + rules: + - host: temporal-redirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/16-ingress-with-temporal-and-permanent-redirect.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/16-ingress-with-temporal-and-permanent-redirect.yml new file mode 100644 index 000000000..c51b77243 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/16-ingress-with-temporal-and-permanent-redirect.yml @@ -0,0 +1,23 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-redirect + namespace: default + annotations: + nginx.ingress.kubernetes.io/permanent-redirect: "https://www.traefik.io" + nginx.ingress.kubernetes.io/temporal-redirect: "https://www.google.com" + +spec: + ingressClassName: nginx + rules: + - host: redirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/17-ingress-with-temporal-redirect-code-correct-code.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/17-ingress-with-temporal-redirect-code-correct-code.yml new file mode 100644 index 000000000..f947040ed --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/17-ingress-with-temporal-redirect-code-correct-code.yml @@ -0,0 +1,23 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-temporal-redirect + namespace: default + annotations: + nginx.ingress.kubernetes.io/temporal-redirect: "https://www.google.com" + nginx.ingress.kubernetes.io/temporal-redirect-code: "308" + +spec: + ingressClassName: nginx + rules: + - host: temporal-redirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/17-ingress-with-temporal-redirect-code-wrong-code.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/17-ingress-with-temporal-redirect-code-wrong-code.yml new file mode 100644 index 000000000..49f1cde47 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/17-ingress-with-temporal-redirect-code-wrong-code.yml @@ -0,0 +1,23 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-temporal-redirect + namespace: default + annotations: + nginx.ingress.kubernetes.io/temporal-redirect: "https://www.google.com" + nginx.ingress.kubernetes.io/temporal-redirect-code: "429" + +spec: + ingressClassName: nginx + rules: + - host: temporal-redirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/kubernetes.go b/pkg/provider/kubernetes/ingress-nginx/kubernetes.go index 2da2bef13..3c8834069 100644 --- a/pkg/provider/kubernetes/ingress-nginx/kubernetes.go +++ b/pkg/provider/kubernetes/ingress-nginx/kubernetes.go @@ -7,6 +7,7 @@ import ( "maps" "math" "net" + "net/http" "os" "regexp" "slices" @@ -810,6 +811,8 @@ func (p *Provider) applyMiddlewares(namespace, routerKey, rulePath string, ingre // TODO: check how to remove this, and create the HTTP router elsewhere. p.applySSLRedirectConfiguration(routerKey, ingressConfig, hasTLS, rt, conf) + applyRedirect(routerKey, ingressConfig, rt, conf) + applyUpstreamVhost(routerKey, ingressConfig, rt, conf) if err := p.applyCustomHeaders(routerKey, ingressConfig, rt, conf); err != nil { @@ -819,6 +822,48 @@ func (p *Provider) applyMiddlewares(namespace, routerKey, rulePath string, ingre return nil } +func applyRedirect(routerName string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) { + if ingressConfig.PermanentRedirect == nil && ingressConfig.TemporalRedirect == nil { + return + } + + var ( + redirectURL string + code int + ) + + if ingressConfig.PermanentRedirect != nil { + redirectURL = *ingressConfig.PermanentRedirect + code = ptr.Deref(ingressConfig.PermanentRedirectCode, http.StatusMovedPermanently) + + // NGINX only accepts valid redirect codes and defaults to 301. + if code < 300 || code > 308 { + code = http.StatusMovedPermanently + } + } + + // TemporalRedirect takes precedence over the PermanentRedirect. + if ingressConfig.TemporalRedirect != nil { + redirectURL = *ingressConfig.TemporalRedirect + code = ptr.Deref(ingressConfig.TemporalRedirectCode, http.StatusFound) + + // NGINX only accepts valid redirect codes and defaults to 302. + if code < 300 || code > 308 { + code = http.StatusFound + } + } + + redirectMiddlewareName := routerName + "-redirect" + conf.HTTP.Middlewares[redirectMiddlewareName] = &dynamic.Middleware{ + RedirectRegex: &dynamic.RedirectRegex{ + Regex: ".*", + Replacement: redirectURL, + StatusCode: &code, + }, + } + rt.Middlewares = append(rt.Middlewares, redirectMiddlewareName) +} + func (p *Provider) applyCustomHeaders(routerName string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) error { customHeaders := ptr.Deref(ingressConfig.CustomHeaders, "") if customHeaders == "" { diff --git a/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go b/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go index c48bae016..b419b8db6 100644 --- a/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go @@ -2,6 +2,7 @@ package ingressnginx import ( "math" + "net/http" "os" "path/filepath" "testing" @@ -1063,6 +1064,384 @@ func TestLoadIngresses(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Permanent Redirect", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/14-ingress-with-permanent-redirect.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-permanent-redirect-rule-0-path-0": { + Rule: "Host(`permanent-redirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-ingress-with-permanent-redirect-whoami-80", + Middlewares: []string{"default-ingress-with-permanent-redirect-rule-0-path-0-redirect"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-permanent-redirect-rule-0-path-0-redirect": { + RedirectRegex: &dynamic.RedirectRegex{ + Regex: ".*", + Replacement: "https://www.google.com", + StatusCode: ptr.To(http.StatusMovedPermanently), + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-ingress-with-permanent-redirect-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Permanent Redirect Code - wrong code", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/14-ingress-with-permanent-redirect-code-wrong-code.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-permanent-redirect-rule-0-path-0": { + Rule: "Host(`permanent-redirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-ingress-with-permanent-redirect-whoami-80", + Middlewares: []string{"default-ingress-with-permanent-redirect-rule-0-path-0-redirect"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-permanent-redirect-rule-0-path-0-redirect": { + RedirectRegex: &dynamic.RedirectRegex{ + Regex: ".*", + Replacement: "https://www.google.com", + StatusCode: ptr.To(http.StatusMovedPermanently), + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-ingress-with-permanent-redirect-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Permanent Redirect Code - correct code", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/14-ingress-with-permanent-redirect-code-correct-code.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-permanent-redirect-rule-0-path-0": { + Rule: "Host(`permanent-redirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-ingress-with-permanent-redirect-whoami-80", + Middlewares: []string{"default-ingress-with-permanent-redirect-rule-0-path-0-redirect"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-permanent-redirect-rule-0-path-0-redirect": { + RedirectRegex: &dynamic.RedirectRegex{ + Regex: ".*", + Replacement: "https://www.google.com", + StatusCode: ptr.To(http.StatusMultipleChoices), + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-ingress-with-permanent-redirect-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Temporal Redirect takes precedence over Permanent Redirect", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/16-ingress-with-temporal-and-permanent-redirect.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-redirect-rule-0-path-0": { + Rule: "Host(`redirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-ingress-with-redirect-whoami-80", + Middlewares: []string{"default-ingress-with-redirect-rule-0-path-0-redirect"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-redirect-rule-0-path-0-redirect": { + RedirectRegex: &dynamic.RedirectRegex{ + Regex: ".*", + Replacement: "https://www.google.com", + StatusCode: ptr.To(http.StatusFound), + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-ingress-with-redirect-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Temporal Redirect", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/15-ingress-with-temporal-redirect.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-temporal-redirect-rule-0-path-0": { + Rule: "Host(`temporal-redirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-ingress-with-temporal-redirect-whoami-80", + Middlewares: []string{"default-ingress-with-temporal-redirect-rule-0-path-0-redirect"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-temporal-redirect-rule-0-path-0-redirect": { + RedirectRegex: &dynamic.RedirectRegex{ + Regex: ".*", + Replacement: "https://www.google.com", + StatusCode: ptr.To(http.StatusFound), + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-ingress-with-temporal-redirect-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Temporal Redirect Code - wrong code", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/17-ingress-with-temporal-redirect-code-wrong-code.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-temporal-redirect-rule-0-path-0": { + Rule: "Host(`temporal-redirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-ingress-with-temporal-redirect-whoami-80", + Middlewares: []string{"default-ingress-with-temporal-redirect-rule-0-path-0-redirect"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-temporal-redirect-rule-0-path-0-redirect": { + RedirectRegex: &dynamic.RedirectRegex{ + Regex: ".*", + Replacement: "https://www.google.com", + StatusCode: ptr.To(http.StatusFound), + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-ingress-with-temporal-redirect-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Temporal Redirect Code - correct code", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/17-ingress-with-temporal-redirect-code-correct-code.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-temporal-redirect-rule-0-path-0": { + Rule: "Host(`temporal-redirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-ingress-with-temporal-redirect-whoami-80", + Middlewares: []string{"default-ingress-with-temporal-redirect-rule-0-path-0-redirect"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-temporal-redirect-rule-0-path-0-redirect": { + RedirectRegex: &dynamic.RedirectRegex{ + Regex: ".*", + Replacement: "https://www.google.com", + StatusCode: ptr.To(http.StatusPermanentRedirect), + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-ingress-with-temporal-redirect-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases {