diff --git a/docs/content/middlewares/http/forwardauth.md b/docs/content/middlewares/http/forwardauth.md index f6e30b827..ecf61ab51 100644 --- a/docs/content/middlewares/http/forwardauth.md +++ b/docs/content/middlewares/http/forwardauth.md @@ -746,5 +746,45 @@ http: 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!} diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 593829571..bf4308237 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -42,6 +42,7 @@ - "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.preserverequestmethod=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" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 1fc5bfebd..a951a0e70 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -185,6 +185,7 @@ forwardBody = true maxBodySize = 42 preserveLocationHeader = true + preserveRequestMethod = true [http.middlewares.Middleware10.forwardAuth.tls] ca = "foobar" cert = "foobar" diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 796209b52..7813d35d9 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -212,6 +212,7 @@ http: forwardBody: true maxBodySize: 42 preserveLocationHeader: true + preserveRequestMethod: true Middleware11: grpcWeb: allowOrigins: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index eeb742b5f..54dfb88a0 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -1248,6 +1248,11 @@ spec: the Location header to the client as is or prefix it with the domain name of the authentication server. type: boolean + preserveRequestMethod: + description: PreserveRequestMethod defines whether to preserve + the original request method while forwarding the request to + the authentication server. + type: boolean tls: description: TLS defines the configuration used to secure the connection to the authentication server. diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index d5711cf03..c2cea9b1b 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -52,6 +52,7 @@ THIS FILE MUST NOT BE EDITED BY HAND | `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/preserveRequestMethod` | `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` | diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index 01da1ba2a..942d893ae 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -506,6 +506,11 @@ spec: the Location header to the client as is or prefix it with the domain name of the authentication server. type: boolean + preserveRequestMethod: + description: PreserveRequestMethod defines whether to preserve + the original request method while forwarding the request to + the authentication server. + type: boolean tls: description: TLS defines the configuration used to secure the connection to the authentication server. diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index eeb742b5f..54dfb88a0 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -1248,6 +1248,11 @@ spec: the Location header to the client as is or prefix it with the domain name of the authentication server. type: boolean + preserveRequestMethod: + description: PreserveRequestMethod defines whether to preserve + the original request method while forwarding the request to + the authentication server. + type: boolean tls: description: TLS defines the configuration used to secure the connection to the authentication server. diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 319ffcbd1..677269268 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -260,6 +260,8 @@ type ForwardAuth struct { 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"` + // 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() { diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index f798e7ff3..b0e60a5ab 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -53,6 +53,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.middlewares.Middleware7.forwardauth.trustforwardheader": "true", "traefik.http.middlewares.Middleware7.forwardauth.forwardbody": "true", "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.allowedhosts": "foobar, fiibar", "traefik.http.middlewares.Middleware8.headers.accesscontrolallowheaders": "X-foobar, X-fiibar", @@ -578,8 +579,9 @@ func TestDecodeConfiguration(t *testing.T) { "foobar", "fiibar", }, - ForwardBody: true, - MaxBodySize: pointer(int64(42)), + ForwardBody: true, + MaxBodySize: pointer(int64(42)), + PreserveRequestMethod: true, }, }, "Middleware8": { @@ -1126,8 +1128,9 @@ func TestEncodeConfiguration(t *testing.T) { "foobar", "fiibar", }, - ForwardBody: true, - MaxBodySize: pointer(int64(42)), + ForwardBody: true, + MaxBodySize: pointer(int64(42)), + PreserveRequestMethod: true, }, }, "Middleware8": { @@ -1342,6 +1345,7 @@ func TestEncodeConfiguration(t *testing.T) { "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.Middleware7.ForwardAuth.PreserveRequestMethod": "true", "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", diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index b8fa9ac62..0ade40b7d 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -57,6 +57,7 @@ type forwardAuth struct { forwardBody bool maxBodySize int64 preserveLocationHeader bool + preserveRequestMethod bool } // NewForward creates a forward auth middleware. @@ -81,6 +82,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu forwardBody: config.ForwardBody, maxBodySize: dynamic.ForwardAuthDefaultMaxBodySize, preserveLocationHeader: config.PreserveLocationHeader, + preserveRequestMethod: config.PreserveRequestMethod, } 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) { 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 { logger.Debug().Err(err).Msgf("Error calling %s", fa.address) observability.SetStatusErrorf(req.Context(), "Error calling %s. Cause %s", fa.address, err) diff --git a/pkg/middlewares/auth/forward_test.go b/pkg/middlewares/auth/forward_test.go index 9d09617f7..f69571cf5 100644 --- a/pkg/middlewares/auth/forward_test.go +++ b/pkg/middlewares/auth/forward_test.go @@ -739,6 +739,63 @@ func TestForwardAuthPreserveLocationHeader(t *testing.T) { 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 { embedded.Tracer diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 7f004d809..7e0e1f164 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -791,6 +791,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef AddAuthCookiesToResponse: auth.AddAuthCookiesToResponse, ForwardBody: auth.ForwardBody, PreserveLocationHeader: auth.PreserveLocationHeader, + PreserveRequestMethod: auth.PreserveRequestMethod, } forwardAuth.SetDefaults() diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go index b2c9a9097..d0f8ed4bc 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go @@ -167,6 +167,8 @@ type ForwardAuth struct { 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"` + // 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. diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index ad87e3ef2..1e8acdca7 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -91,6 +91,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/http/middlewares/Middleware08/forwardAuth/forwardBody": "true", "traefik/http/middlewares/Middleware08/forwardAuth/maxBodySize": "42", "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/port": "foobar", "traefik/http/middlewares/Middleware15/redirectScheme/permanent": "true", @@ -446,6 +447,7 @@ func Test_buildConfiguration(t *testing.T) { ForwardBody: true, MaxBodySize: pointer(int64(42)), PreserveLocationHeader: true, + PreserveRequestMethod: true, }, }, "Middleware06": {