Add an option to preserve the ForwardAuth Server Location header
This commit is contained in:
parent
4974d9e4d7
commit
2302debac2
15 changed files with 115 additions and 5 deletions
|
@ -705,4 +705,46 @@ http:
|
|||
headerField = "X-WebAuth-User"
|
||||
```
|
||||
|
||||
### `preserveLocationHeader`
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
`preserveLocationHeader` defines whether to forward the `Location` header to the client as is or prefix it with the domain name of the authentication server.
|
||||
|
||||
```yaml tab="Docker & Swarm"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.preserveLocationHeader=true"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-auth
|
||||
spec:
|
||||
forwardAuth:
|
||||
# ...
|
||||
preserveLocationHeader: true
|
||||
```
|
||||
|
||||
```json tab="Consul Catalog"
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.preserveLocationHeader=true"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
middlewares:
|
||||
test-auth:
|
||||
forwardAuth:
|
||||
# ...
|
||||
preserveLocationHeader: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.middlewares.test-auth.forwardAuth]
|
||||
# ...
|
||||
preserveLocationHeader = true
|
||||
```
|
||||
|
||||
|
||||
{!traefik-for-business-applications.md!}
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
- "traefik.http.middlewares.middleware10.forwardauth.forwardbody=true"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.headerfield=foobar"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.maxbodysize=42"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.preservelocationheader=true"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.tls.ca=foobar"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.tls.caoptional=true"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.tls.cert=foobar"
|
||||
|
|
|
@ -184,6 +184,7 @@
|
|||
headerField = "foobar"
|
||||
forwardBody = true
|
||||
maxBodySize = 42
|
||||
preserveLocationHeader = true
|
||||
[http.middlewares.Middleware10.forwardAuth.tls]
|
||||
ca = "foobar"
|
||||
cert = "foobar"
|
||||
|
|
|
@ -211,6 +211,7 @@ http:
|
|||
headerField: foobar
|
||||
forwardBody: true
|
||||
maxBodySize: 42
|
||||
preserveLocationHeader: true
|
||||
Middleware11:
|
||||
grpcWeb:
|
||||
allowOrigins:
|
||||
|
|
|
@ -1243,6 +1243,11 @@ spec:
|
|||
allowed to be forwarded to the authentication server.
|
||||
format: int64
|
||||
type: integer
|
||||
preserveLocationHeader:
|
||||
description: PreserveLocationHeader defines whether to forward
|
||||
the Location header to the client as is or prefix it with the
|
||||
domain name of the authentication server.
|
||||
type: boolean
|
||||
tls:
|
||||
description: TLS defines the configuration used to secure the
|
||||
connection to the authentication server.
|
||||
|
|
|
@ -51,6 +51,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| `traefik/http/middlewares/Middleware10/forwardAuth/forwardBody` | `true` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/headerField` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/maxBodySize` | `42` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/preserveLocationHeader` | `true` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/ca` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/caOptional` | `true` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/cert` | `foobar` |
|
||||
|
|
|
@ -501,6 +501,11 @@ spec:
|
|||
allowed to be forwarded to the authentication server.
|
||||
format: int64
|
||||
type: integer
|
||||
preserveLocationHeader:
|
||||
description: PreserveLocationHeader defines whether to forward
|
||||
the Location header to the client as is or prefix it with the
|
||||
domain name of the authentication server.
|
||||
type: boolean
|
||||
tls:
|
||||
description: TLS defines the configuration used to secure the
|
||||
connection to the authentication server.
|
||||
|
|
|
@ -1243,6 +1243,11 @@ spec:
|
|||
allowed to be forwarded to the authentication server.
|
||||
format: int64
|
||||
type: integer
|
||||
preserveLocationHeader:
|
||||
description: PreserveLocationHeader defines whether to forward
|
||||
the Location header to the client as is or prefix it with the
|
||||
domain name of the authentication server.
|
||||
type: boolean
|
||||
tls:
|
||||
description: TLS defines the configuration used to secure the
|
||||
connection to the authentication server.
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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": {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue