1
0
Fork 0

Fix SSL redirect middleware to match NGINX behavior

This commit is contained in:
Michael 2025-12-04 13:44:05 +01:00 committed by GitHub
parent 7314f7ddc9
commit d6b127ba91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 70 additions and 33 deletions

View file

@ -2041,8 +2041,9 @@ spec:
More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/ More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/
properties: properties:
permanent: permanent:
description: Permanent defines whether the redirection is permanent description: |-
(308). Permanent defines whether the redirection is permanent.
For HTTP GET requests a 301 is returned, otherwise a 308 is returned.
type: boolean type: boolean
port: port:
description: Port defines the port of the new URL. description: Port defines the port of the new URL.

View file

@ -1211,8 +1211,9 @@ spec:
More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/ More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/
properties: properties:
permanent: permanent:
description: Permanent defines whether the redirection is permanent description: |-
(308). Permanent defines whether the redirection is permanent.
For HTTP GET requests a 301 is returned, otherwise a 308 is returned.
type: boolean type: boolean
port: port:
description: Port defines the port of the new URL. description: Port defines the port of the new URL.

View file

@ -2041,8 +2041,9 @@ spec:
More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/ More info: https://doc.traefik.io/traefik/v3.6/middlewares/http/redirectscheme/
properties: properties:
permanent: permanent:
description: Permanent defines whether the redirection is permanent description: |-
(308). Permanent defines whether the redirection is permanent.
For HTTP GET requests a 301 is returned, otherwise a 308 is returned.
type: boolean type: boolean
port: port:
description: Port defines the port of the new URL. description: Port defines the port of the new URL.

View file

@ -655,8 +655,13 @@ type RedirectScheme struct {
Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty" export:"true"` Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty" export:"true"`
// Port defines the port of the new URL. // Port defines the port of the new URL.
Port string `json:"port,omitempty" toml:"port,omitempty" yaml:"port,omitempty" export:"true"` Port string `json:"port,omitempty" toml:"port,omitempty" yaml:"port,omitempty" export:"true"`
// Permanent defines whether the redirection is permanent (308). // Permanent defines whether the redirection is permanent.
// For HTTP GET requests a 301 is returned, otherwise a 308 is returned.
Permanent bool `json:"permanent,omitempty" toml:"permanent,omitempty" yaml:"permanent,omitempty" export:"true"` Permanent bool `json:"permanent,omitempty" toml:"permanent,omitempty" yaml:"permanent,omitempty" export:"true"`
// 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:"-"`
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true

View file

@ -22,13 +22,14 @@ type redirect struct {
regex *regexp.Regexp regex *regexp.Regexp
replacement string replacement string
permanent bool permanent bool
forcePermanentRedirect bool
errHandler utils.ErrorHandler errHandler utils.ErrorHandler
name string name string
rawURL func(*http.Request) string rawURL func(*http.Request) string
} }
// New creates a Redirect middleware. // New creates a Redirect middleware.
func newRedirect(next http.Handler, regex, replacement string, permanent bool, rawURL func(*http.Request) string, name string) (http.Handler, error) { func newRedirect(next http.Handler, regex, replacement string, permanent bool, forcePermanentRedirect bool, rawURL func(*http.Request) string, name string) (http.Handler, error) {
re, err := regexp.Compile(regex) re, err := regexp.Compile(regex)
if err != nil { if err != nil {
return nil, err return nil, err
@ -38,6 +39,7 @@ func newRedirect(next http.Handler, regex, replacement string, permanent bool, r
regex: re, regex: re,
replacement: replacement, replacement: replacement,
permanent: permanent, permanent: permanent,
forcePermanentRedirect: forcePermanentRedirect,
errHandler: utils.DefaultHandler, errHandler: utils.DefaultHandler,
next: next, next: next,
name: name, name: name,
@ -69,7 +71,7 @@ func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
} }
if newURL != oldURL { if newURL != oldURL {
handler := &moveHandler{location: parsedURL, permanent: r.permanent} handler := &moveHandler{location: parsedURL, permanent: r.permanent, forcePermanentRedirect: r.forcePermanentRedirect}
handler.ServeHTTP(rw, req) handler.ServeHTTP(rw, req)
return return
} }
@ -84,6 +86,7 @@ func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
type moveHandler struct { type moveHandler struct {
location *url.URL location *url.URL
permanent bool permanent bool
forcePermanentRedirect bool
} }
func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@ -100,6 +103,11 @@ func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
status = http.StatusPermanentRedirect status = http.StatusPermanentRedirect
} }
} }
if m.forcePermanentRedirect {
status = http.StatusPermanentRedirect
}
rw.WriteHeader(status) rw.WriteHeader(status)
_, err := rw.Write([]byte(http.StatusText(status))) _, err := rw.Write([]byte(http.StatusText(status)))
if err != nil { if err != nil {

View file

@ -17,7 +17,7 @@ func NewRedirectRegex(ctx context.Context, next http.Handler, conf dynamic.Redir
logger.Debug().Msg("Creating middleware") logger.Debug().Msg("Creating middleware")
logger.Debug().Msgf("Setting up redirection from %s to %s", conf.Regex, conf.Replacement) logger.Debug().Msgf("Setting up redirection from %s to %s", conf.Regex, conf.Replacement)
return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, rawURL, name) return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, false, rawURL, name)
} }
func rawURL(req *http.Request) string { func rawURL(req *http.Request) string {

View file

@ -40,7 +40,7 @@ func NewRedirectScheme(ctx context.Context, next http.Handler, conf dynamic.Redi
rs := &redirectScheme{name: name} rs := &redirectScheme{name: name}
handler, err := newRedirect(next, uriPattern, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, rs.clientRequestURL, name) handler, err := newRedirect(next, uriPattern, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, conf.ForcePermanentRedirect, rs.clientRequestURL, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -165,6 +165,27 @@ func TestRedirectSchemeHandler(t *testing.T) {
expectedURL: "https://foo:8443", expectedURL: "https://foo:8443",
expectedStatus: http.StatusMovedPermanently, expectedStatus: http.StatusMovedPermanently,
}, },
{
desc: "HTTP to HTTPS with explicit 308 status code",
config: dynamic.RedirectScheme{
Scheme: "https",
ForcePermanentRedirect: true,
},
url: "http://foo",
expectedURL: "https://foo",
expectedStatus: http.StatusPermanentRedirect,
},
{
desc: "HTTP to HTTPS with explicit 308 status code for GET request",
method: http.MethodGet,
config: dynamic.RedirectScheme{
Scheme: "https",
ForcePermanentRedirect: true,
},
url: "http://foo",
expectedURL: "https://foo",
expectedStatus: http.StatusPermanentRedirect,
},
{ {
desc: "to HTTP 80", desc: "to HTTP 80",
config: dynamic.RedirectScheme{ config: dynamic.RedirectScheme{

View file

@ -969,7 +969,7 @@ func applySSLRedirectConfiguration(routerName string, ingressConfig ingressConfi
conf.HTTP.Middlewares[redirectMiddlewareName] = &dynamic.Middleware{ conf.HTTP.Middlewares[redirectMiddlewareName] = &dynamic.Middleware{
RedirectScheme: &dynamic.RedirectScheme{ RedirectScheme: &dynamic.RedirectScheme{
Scheme: "https", Scheme: "https",
Permanent: true, ForcePermanentRedirect: true,
}, },
} }
redirectRouter.Middlewares = append(redirectRouter.Middlewares, redirectMiddlewareName) redirectRouter.Middlewares = append(redirectRouter.Middlewares, redirectMiddlewareName)

View file

@ -208,13 +208,13 @@ func TestLoadIngresses(t *testing.T) {
"default-ingress-with-ssl-redirect-rule-0-path-0-redirect-scheme": { "default-ingress-with-ssl-redirect-rule-0-path-0-redirect-scheme": {
RedirectScheme: &dynamic.RedirectScheme{ RedirectScheme: &dynamic.RedirectScheme{
Scheme: "https", Scheme: "https",
Permanent: true, ForcePermanentRedirect: true,
}, },
}, },
"default-ingress-with-force-ssl-redirect-rule-0-path-0-redirect-scheme": { "default-ingress-with-force-ssl-redirect-rule-0-path-0-redirect-scheme": {
RedirectScheme: &dynamic.RedirectScheme{ RedirectScheme: &dynamic.RedirectScheme{
Scheme: "https", Scheme: "https",
Permanent: true, ForcePermanentRedirect: true,
}, },
}, },
}, },