Add an option to preserve the ForwardAuth Server Location header

This commit is contained in:
Nelson Isioma 2024-12-13 10:38:37 +01:00 committed by GitHub
parent 4974d9e4d7
commit 2302debac2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 115 additions and 5 deletions

View file

@ -258,6 +258,8 @@ type ForwardAuth struct {
ForwardBody bool `json:"forwardBody,omitempty" toml:"forwardBody,omitempty" yaml:"forwardBody,omitempty" export:"true"`
// MaxBodySize defines the maximum body size in bytes allowed to be forwarded to the authentication server.
MaxBodySize *int64 `json:"maxBodySize,omitempty" toml:"maxBodySize,omitempty" yaml:"maxBodySize,omitempty" export:"true"`
// PreserveLocationHeader defines whether to forward the Location header to the client as is or prefix it with the domain name of the authentication server.
PreserveLocationHeader bool `json:"preserveLocationHeader,omitempty" toml:"preserveLocationHeader,omitempty" yaml:"preserveLocationHeader,omitempty" export:"true"`
}
func (f *ForwardAuth) SetDefaults() {

View file

@ -1329,6 +1329,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.InsecureSkipVerify": "true",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key": "foobar",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader": "true",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.PreserveLocationHeader": "false",
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowCredentials": "true",
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowHeaders": "X-foobar, X-fiibar",
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowMethods": "GET, PUT",

View file

@ -8,6 +8,7 @@ import (
"io"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"time"
@ -55,6 +56,7 @@ type forwardAuth struct {
headerField string
forwardBody bool
maxBodySize int64
preserveLocationHeader bool
}
// NewForward creates a forward auth middleware.
@ -78,6 +80,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
headerField: config.HeaderField,
forwardBody: config.ForwardBody,
maxBodySize: dynamic.ForwardAuthDefaultMaxBodySize,
preserveLocationHeader: config.PreserveLocationHeader,
}
if config.MaxBodySize != nil {
@ -222,9 +225,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
utils.CopyHeaders(rw.Header(), forwardResponse.Header)
utils.RemoveHeaders(rw.Header(), hopHeaders...)
// Grab the location header, if any.
redirectURL, err := forwardResponse.Location()
redirectURL, err := fa.redirectURL(forwardResponse)
if err != nil {
if !errors.Is(err, http.ErrNoLocation) {
logger.Debug().Err(err).Msgf("Error reading response location header %s", fa.address)
@ -282,6 +283,18 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
fa.next.ServeHTTP(middlewares.NewResponseModifier(rw, req, fa.buildModifier(authCookies)), req)
}
func (fa *forwardAuth) redirectURL(forwardResponse *http.Response) (*url.URL, error) {
if !fa.preserveLocationHeader {
return forwardResponse.Location()
}
// Preserve the Location header if it exists.
if lv := forwardResponse.Header.Get("Location"); lv != "" {
return url.Parse(lv)
}
return nil, http.ErrNoLocation
}
func (fa *forwardAuth) buildModifier(authCookies []*http.Cookie) func(res *http.Response) error {
return func(res *http.Response) error {
cookies := res.Cookies()

View file

@ -711,6 +711,34 @@ func TestForwardAuthTracing(t *testing.T) {
}
}
func TestForwardAuthPreserveLocationHeader(t *testing.T) {
relativeURL := "/index.html"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", relativeURL)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
}))
t.Cleanup(server.Close)
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
auth := dynamic.ForwardAuth{
Address: server.URL,
PreserveLocationHeader: true,
}
middleware, err := NewForward(context.Background(), next, auth, "authTest")
require.NoError(t, err)
ts := httptest.NewServer(middleware)
t.Cleanup(ts.Close)
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
res, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusUnauthorized, res.StatusCode)
assert.Equal(t, relativeURL, res.Header.Get("Location"))
}
type mockTracer struct {
embedded.Tracer

View file

@ -790,6 +790,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef
AuthRequestHeaders: auth.AuthRequestHeaders,
AddAuthCookiesToResponse: auth.AddAuthCookiesToResponse,
ForwardBody: auth.ForwardBody,
PreserveLocationHeader: auth.PreserveLocationHeader,
}
forwardAuth.SetDefaults()

View file

@ -165,6 +165,8 @@ type ForwardAuth struct {
ForwardBody bool `json:"forwardBody,omitempty"`
// MaxBodySize defines the maximum body size in bytes allowed to be forwarded to the authentication server.
MaxBodySize *int64 `json:"maxBodySize,omitempty"`
// PreserveLocationHeader defines whether to forward the Location header to the client as is or prefix it with the domain name of the authentication server.
PreserveLocationHeader bool `json:"preserveLocationHeader,omitempty"`
}
// ClientTLS holds the client TLS configuration.

View file

@ -90,6 +90,7 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/http/middlewares/Middleware08/forwardAuth/trustForwardHeader": "true",
"traefik/http/middlewares/Middleware08/forwardAuth/forwardBody": "true",
"traefik/http/middlewares/Middleware08/forwardAuth/maxBodySize": "42",
"traefik/http/middlewares/Middleware08/forwardAuth/preserveLocationHeader": "true",
"traefik/http/middlewares/Middleware15/redirectScheme/scheme": "foobar",
"traefik/http/middlewares/Middleware15/redirectScheme/port": "foobar",
"traefik/http/middlewares/Middleware15/redirectScheme/permanent": "true",
@ -442,8 +443,9 @@ func Test_buildConfiguration(t *testing.T) {
"foobar",
"foobar",
},
ForwardBody: true,
MaxBodySize: pointer(int64(42)),
ForwardBody: true,
MaxBodySize: pointer(int64(42)),
PreserveLocationHeader: true,
},
},
"Middleware06": {