Add option to preserve request method in forwardAuth

This commit is contained in:
Shivam Saxena 2025-01-23 18:58:04 +05:30 committed by GitHub
parent 2b6a04bc1d
commit 2afa03b55c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 139 additions and 5 deletions

View file

@ -746,5 +746,45 @@ http:
preserveLocationHeader = true preserveLocationHeader = true
``` ```
### `preserveRequestMethod`
_Optional, Default=false_
`preserveRequestMethod` defines whether to preserve the original request method while forwarding the request to the authentication server. By default, when this option is set to `false`, incoming requests are always forwarded as `GET` requests to the authentication server.
```yaml tab="Docker & Swarm"
labels:
- "traefik.http.middlewares.test-auth.forwardauth.preserveRequestMethod=true"
```
```yaml tab="Kubernetes"
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: test-auth
spec:
forwardAuth:
# ...
preserveRequestMethod: true
```
```json tab="Consul Catalog"
- "traefik.http.middlewares.test-auth.forwardauth.preserveRequestMethod=true"
```
```yaml tab="File (YAML)"
http:
middlewares:
test-auth:
forwardAuth:
# ...
preserveRequestMethod: true
```
```toml tab="File (TOML)"
[http.middlewares.test-auth.forwardAuth]
# ...
preserveRequestMethod = true
```
{!traefik-for-business-applications.md!} {!traefik-for-business-applications.md!}

View file

@ -42,6 +42,7 @@
- "traefik.http.middlewares.middleware10.forwardauth.headerfield=foobar" - "traefik.http.middlewares.middleware10.forwardauth.headerfield=foobar"
- "traefik.http.middlewares.middleware10.forwardauth.maxbodysize=42" - "traefik.http.middlewares.middleware10.forwardauth.maxbodysize=42"
- "traefik.http.middlewares.middleware10.forwardauth.preservelocationheader=true" - "traefik.http.middlewares.middleware10.forwardauth.preservelocationheader=true"
- "traefik.http.middlewares.middleware10.forwardauth.preserverequestmethod=true"
- "traefik.http.middlewares.middleware10.forwardauth.tls.ca=foobar" - "traefik.http.middlewares.middleware10.forwardauth.tls.ca=foobar"
- "traefik.http.middlewares.middleware10.forwardauth.tls.caoptional=true" - "traefik.http.middlewares.middleware10.forwardauth.tls.caoptional=true"
- "traefik.http.middlewares.middleware10.forwardauth.tls.cert=foobar" - "traefik.http.middlewares.middleware10.forwardauth.tls.cert=foobar"

View file

@ -185,6 +185,7 @@
forwardBody = true forwardBody = true
maxBodySize = 42 maxBodySize = 42
preserveLocationHeader = true preserveLocationHeader = true
preserveRequestMethod = true
[http.middlewares.Middleware10.forwardAuth.tls] [http.middlewares.Middleware10.forwardAuth.tls]
ca = "foobar" ca = "foobar"
cert = "foobar" cert = "foobar"

View file

@ -212,6 +212,7 @@ http:
forwardBody: true forwardBody: true
maxBodySize: 42 maxBodySize: 42
preserveLocationHeader: true preserveLocationHeader: true
preserveRequestMethod: true
Middleware11: Middleware11:
grpcWeb: grpcWeb:
allowOrigins: allowOrigins:

View file

@ -1248,6 +1248,11 @@ spec:
the Location header to the client as is or prefix it with the the Location header to the client as is or prefix it with the
domain name of the authentication server. domain name of the authentication server.
type: boolean type: boolean
preserveRequestMethod:
description: PreserveRequestMethod defines whether to preserve
the original request method while forwarding the request to
the authentication server.
type: boolean
tls: tls:
description: TLS defines the configuration used to secure the description: TLS defines the configuration used to secure the
connection to the authentication server. connection to the authentication server.

View file

@ -52,6 +52,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
| `traefik/http/middlewares/Middleware10/forwardAuth/headerField` | `foobar` | | `traefik/http/middlewares/Middleware10/forwardAuth/headerField` | `foobar` |
| `traefik/http/middlewares/Middleware10/forwardAuth/maxBodySize` | `42` | | `traefik/http/middlewares/Middleware10/forwardAuth/maxBodySize` | `42` |
| `traefik/http/middlewares/Middleware10/forwardAuth/preserveLocationHeader` | `true` | | `traefik/http/middlewares/Middleware10/forwardAuth/preserveLocationHeader` | `true` |
| `traefik/http/middlewares/Middleware10/forwardAuth/preserveRequestMethod` | `true` |
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/ca` | `foobar` | | `traefik/http/middlewares/Middleware10/forwardAuth/tls/ca` | `foobar` |
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/caOptional` | `true` | | `traefik/http/middlewares/Middleware10/forwardAuth/tls/caOptional` | `true` |
| `traefik/http/middlewares/Middleware10/forwardAuth/tls/cert` | `foobar` | | `traefik/http/middlewares/Middleware10/forwardAuth/tls/cert` | `foobar` |

View file

@ -506,6 +506,11 @@ spec:
the Location header to the client as is or prefix it with the the Location header to the client as is or prefix it with the
domain name of the authentication server. domain name of the authentication server.
type: boolean type: boolean
preserveRequestMethod:
description: PreserveRequestMethod defines whether to preserve
the original request method while forwarding the request to
the authentication server.
type: boolean
tls: tls:
description: TLS defines the configuration used to secure the description: TLS defines the configuration used to secure the
connection to the authentication server. connection to the authentication server.

View file

@ -1248,6 +1248,11 @@ spec:
the Location header to the client as is or prefix it with the the Location header to the client as is or prefix it with the
domain name of the authentication server. domain name of the authentication server.
type: boolean type: boolean
preserveRequestMethod:
description: PreserveRequestMethod defines whether to preserve
the original request method while forwarding the request to
the authentication server.
type: boolean
tls: tls:
description: TLS defines the configuration used to secure the description: TLS defines the configuration used to secure the
connection to the authentication server. connection to the authentication server.

View file

@ -260,6 +260,8 @@ type ForwardAuth struct {
MaxBodySize *int64 `json:"maxBodySize,omitempty" toml:"maxBodySize,omitempty" yaml:"maxBodySize,omitempty" export:"true"` 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 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"` PreserveLocationHeader bool `json:"preserveLocationHeader,omitempty" toml:"preserveLocationHeader,omitempty" yaml:"preserveLocationHeader,omitempty" export:"true"`
// PreserveRequestMethod defines whether to preserve the original request method while forwarding the request to the authentication server.
PreserveRequestMethod bool `json:"preserveRequestMethod,omitempty" toml:"preserveRequestMethod,omitempty" yaml:"preserveRequestMethod,omitempty" export:"true"`
} }
func (f *ForwardAuth) SetDefaults() { func (f *ForwardAuth) SetDefaults() {

View file

@ -53,6 +53,7 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware7.forwardauth.trustforwardheader": "true", "traefik.http.middlewares.Middleware7.forwardauth.trustforwardheader": "true",
"traefik.http.middlewares.Middleware7.forwardauth.forwardbody": "true", "traefik.http.middlewares.Middleware7.forwardauth.forwardbody": "true",
"traefik.http.middlewares.Middleware7.forwardauth.maxbodysize": "42", "traefik.http.middlewares.Middleware7.forwardauth.maxbodysize": "42",
"traefik.http.middlewares.Middleware7.forwardauth.preserveRequestMethod": "true",
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowcredentials": "true", "traefik.http.middlewares.Middleware8.headers.accesscontrolallowcredentials": "true",
"traefik.http.middlewares.Middleware8.headers.allowedhosts": "foobar, fiibar", "traefik.http.middlewares.Middleware8.headers.allowedhosts": "foobar, fiibar",
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowheaders": "X-foobar, X-fiibar", "traefik.http.middlewares.Middleware8.headers.accesscontrolallowheaders": "X-foobar, X-fiibar",
@ -578,8 +579,9 @@ func TestDecodeConfiguration(t *testing.T) {
"foobar", "foobar",
"fiibar", "fiibar",
}, },
ForwardBody: true, ForwardBody: true,
MaxBodySize: pointer(int64(42)), MaxBodySize: pointer(int64(42)),
PreserveRequestMethod: true,
}, },
}, },
"Middleware8": { "Middleware8": {
@ -1126,8 +1128,9 @@ func TestEncodeConfiguration(t *testing.T) {
"foobar", "foobar",
"fiibar", "fiibar",
}, },
ForwardBody: true, ForwardBody: true,
MaxBodySize: pointer(int64(42)), MaxBodySize: pointer(int64(42)),
PreserveRequestMethod: true,
}, },
}, },
"Middleware8": { "Middleware8": {
@ -1342,6 +1345,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key": "foobar", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key": "foobar",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader": "true", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader": "true",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.PreserveLocationHeader": "false", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.PreserveLocationHeader": "false",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.PreserveRequestMethod": "true",
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowCredentials": "true", "traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowCredentials": "true",
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowHeaders": "X-foobar, X-fiibar", "traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowHeaders": "X-foobar, X-fiibar",
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowMethods": "GET, PUT", "traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowMethods": "GET, PUT",

View file

@ -57,6 +57,7 @@ type forwardAuth struct {
forwardBody bool forwardBody bool
maxBodySize int64 maxBodySize int64
preserveLocationHeader bool preserveLocationHeader bool
preserveRequestMethod bool
} }
// NewForward creates a forward auth middleware. // NewForward creates a forward auth middleware.
@ -81,6 +82,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
forwardBody: config.ForwardBody, forwardBody: config.ForwardBody,
maxBodySize: dynamic.ForwardAuthDefaultMaxBodySize, maxBodySize: dynamic.ForwardAuthDefaultMaxBodySize,
preserveLocationHeader: config.PreserveLocationHeader, preserveLocationHeader: config.PreserveLocationHeader,
preserveRequestMethod: config.PreserveRequestMethod,
} }
if config.MaxBodySize != nil { if config.MaxBodySize != nil {
@ -135,7 +137,12 @@ func (fa *forwardAuth) GetTracingInformation() (string, string, trace.SpanKind)
func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
logger := middlewares.GetLogger(req.Context(), fa.name, typeNameForward) logger := middlewares.GetLogger(req.Context(), fa.name, typeNameForward)
forwardReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, fa.address, nil) forwardReqMethod := http.MethodGet
if fa.preserveRequestMethod {
forwardReqMethod = req.Method
}
forwardReq, err := http.NewRequestWithContext(req.Context(), forwardReqMethod, fa.address, nil)
if err != nil { if err != nil {
logger.Debug().Err(err).Msgf("Error calling %s", fa.address) logger.Debug().Err(err).Msgf("Error calling %s", fa.address)
observability.SetStatusErrorf(req.Context(), "Error calling %s. Cause %s", fa.address, err) observability.SetStatusErrorf(req.Context(), "Error calling %s. Cause %s", fa.address, err)

View file

@ -739,6 +739,63 @@ func TestForwardAuthPreserveLocationHeader(t *testing.T) {
assert.Equal(t, relativeURL, res.Header.Get("Location")) assert.Equal(t, relativeURL, res.Header.Get("Location"))
} }
func TestForwardAuthPreserveRequestMethod(t *testing.T) {
testCases := []struct {
name string
preserveRequestMethod bool
originalReqMethod string
expectedReqMethodInAuthServer string
}{
{
name: "preserve request method",
preserveRequestMethod: true,
originalReqMethod: http.MethodPost,
expectedReqMethodInAuthServer: http.MethodPost,
},
{
name: "do not preserve request method",
preserveRequestMethod: false,
originalReqMethod: http.MethodPost,
expectedReqMethodInAuthServer: http.MethodGet,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
reqReachesAuthServer := false
authServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
reqReachesAuthServer = true
assert.Equal(t, test.expectedReqMethodInAuthServer, req.Method)
}))
t.Cleanup(authServer.Close)
reqReachesNextServer := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqReachesNextServer = true
assert.Equal(t, test.originalReqMethod, r.Method)
})
auth := dynamic.ForwardAuth{
Address: authServer.URL,
PreserveRequestMethod: test.preserveRequestMethod,
}
middleware, err := NewForward(context.Background(), next, auth, "authTest")
require.NoError(t, err)
ts := httptest.NewServer(middleware)
t.Cleanup(ts.Close)
req := testhelpers.MustNewRequest(test.originalReqMethod, ts.URL, nil)
res, err := http.DefaultClient.Do(req)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.True(t, reqReachesAuthServer)
assert.True(t, reqReachesNextServer)
})
}
}
type mockTracer struct { type mockTracer struct {
embedded.Tracer embedded.Tracer

View file

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

View file

@ -167,6 +167,8 @@ type ForwardAuth struct {
MaxBodySize *int64 `json:"maxBodySize,omitempty"` 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 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"` PreserveLocationHeader bool `json:"preserveLocationHeader,omitempty"`
// PreserveRequestMethod defines whether to preserve the original request method while forwarding the request to the authentication server.
PreserveRequestMethod bool `json:"preserveRequestMethod,omitempty"`
} }
// ClientTLS holds the client TLS configuration. // ClientTLS holds the client TLS configuration.

View file

@ -91,6 +91,7 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/http/middlewares/Middleware08/forwardAuth/forwardBody": "true", "traefik/http/middlewares/Middleware08/forwardAuth/forwardBody": "true",
"traefik/http/middlewares/Middleware08/forwardAuth/maxBodySize": "42", "traefik/http/middlewares/Middleware08/forwardAuth/maxBodySize": "42",
"traefik/http/middlewares/Middleware08/forwardAuth/preserveLocationHeader": "true", "traefik/http/middlewares/Middleware08/forwardAuth/preserveLocationHeader": "true",
"traefik/http/middlewares/Middleware08/forwardAuth/preserveRequestMethod": "true",
"traefik/http/middlewares/Middleware15/redirectScheme/scheme": "foobar", "traefik/http/middlewares/Middleware15/redirectScheme/scheme": "foobar",
"traefik/http/middlewares/Middleware15/redirectScheme/port": "foobar", "traefik/http/middlewares/Middleware15/redirectScheme/port": "foobar",
"traefik/http/middlewares/Middleware15/redirectScheme/permanent": "true", "traefik/http/middlewares/Middleware15/redirectScheme/permanent": "true",
@ -446,6 +447,7 @@ func Test_buildConfiguration(t *testing.T) {
ForwardBody: true, ForwardBody: true,
MaxBodySize: pointer(int64(42)), MaxBodySize: pointer(int64(42)),
PreserveLocationHeader: true, PreserveLocationHeader: true,
PreserveRequestMethod: true,
}, },
}, },
"Middleware06": { "Middleware06": {